Cloud sync for object pools and collections using Firebase Storage.
Strvct's local persistence stores serialized object graphs in IndexedDB via SvPersistentObjectPool. Cloud sync extends this with two complementary strategies because different data has fundamentally different sync characteristics. A collection of independent items (e.g. characters or campaigns) benefits from per-item files — you can lazy-load, show thumbnails before downloading, and a change to one item doesn't require re-uploading everything. But an interconnected object graph (e.g. a game session with dozens of cross-referencing objects) can't be split into individual files because objects reference each other by ID — they must move as a unit. The two strategies reflect this distinction:
Both strategies require the synced object graph to be self-contained — the rest of the system holds only a reference to the graph's root, and objects within the graph hold no persistent references to objects outside it. This isolation is what makes it possible to serialize, upload, and reconstruct the graph independently.
Both strategies use Firebase Storage as the backend and are coordinated by SvCloudSyncSource.
Each syncable collection maintains a folder in cloud storage at /users/{userId}/{collectionName}/. The folder contains one JSON file per item plus a manifest file that tracks ordering and metadata.
Manifest structure:
{
"subnodeIds": ["id1", "id2"],
"items": {
"id1": {
"type": "MyItemClass",
"title": "Item Title",
"subtitle": "Item subtitle",
"thumbnailUrl": "https://...",
"lastModified": 1707123456789
}
}
}The manifest enables lazy loading — the client can display item titles and thumbnails without downloading the full item data. Full item JSON is fetched on demand when the user navigates to an item.
Collections can be nested. SvCloudSyncSource supports a subPath slot, allowing hierarchical storage structures like /users/{userId}/{collectionName}/{itemId}/{subCollectionName}/.
Push to cloud:
asCloudJson() which calls serializeToJson("Cloud", [])/users/{userId}/{collectionName}/{itemId}.jsonPull from cloud:
deserializeFromJson()Collection sync uses a timestamp-based strategy:
cloudLastModified and localLastModifiedThis is a "local wins" strategy — local changes always take priority.
For complex object graphs where many interrelated objects need to be synced together, SvSubObjectPool serializes the entire pool as a single JSON document. The pool maps persistent unique IDs (puuids) to serialized object records:
{
"{puuid1}": "{serialized_object_json}",
"{puuid2}": "{serialized_object_json}"
}This is stored at /users/{userId}/{collectionName}/{poolId}/pool.json.
To avoid uploading the entire pool on every save, SvSubObjectPool tracks changes since the last sync and produces incremental deltas:
{
"timestamp": 1707123456789,
"writes": {
"{puuid1}": "{updated_object_json}",
"{puuid3}": "{new_object_json}"
},
"deletes": ["{puuid2}"]
}Deltas are stored as separate timestamped files alongside the main pool. On load, the client fetches pool.json and applies all deltas in order to reconstruct the current state.
Upload strategy:
Pool sync uses lock-based concurrency to prevent simultaneous edits:
asyncAcquireOrRefreshLock() acquires an exclusive lock before writingasyncReleaseLock() releases the lock when the session endsThe primary cloud-aware sync class. Configured with a user ID, folder name, and Firebase Storage reference. Handles:
An in-memory SvObjectPool (not backed by IndexedDB) designed for cloud sync. Provides:
asyncSaveToCloud() — saves with delta or full upload optimizationcollectDelta() — produces incremental changes vs. last synced snapshotasyncCompactToCloud() — consolidates deltas into a single pool filefromCloudJson(json) — reconstructs the pool from cloud dataasJson() — serializes the complete pool for uploadAbstract base class for collection syncing. Defines the interface for:
asyncSyncFromSource() — pull from cloudasyncLazySyncFromSource() — create stubs for lazy loadingasyncSyncToSource() — push to cloud| Component | Backing Store | Cloud Sync | Purpose |
|---|---|---|---|
SvPersistentObjectPool | IndexedDB | No | Local app state (singleton, never synced directly) |
SvSubObjectPool | In-memory | Yes | Session-level cloud sync with delta support |
SvCloudSyncSource | Firebase Storage | Yes | Collection-level cloud sync with manifests |
The local SvPersistentObjectPool is the ground truth for the running application. Cloud sync operates alongside it — collections push individual items, while sessions create a SvSubObjectPool snapshot for upload.
Cloud sync is triggered automatically on:
beforeunload event