cry-synced-db-client 0.1.144 → 0.1.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/dist/index.js +22 -0
- package/dist/src/db/SyncedDb.d.ts +9 -0
- package/dist/src/db/types/managers.d.ts +1 -0
- package/dist/src/types/I_SyncedDb.d.ts +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -108,6 +108,67 @@ Signature is identical. No other callback changes.
|
|
|
108
108
|
- Noop when offline or on writeOnly collections.
|
|
109
109
|
- Ignored on `find` / `findOne` (use `referToServer` there).
|
|
110
110
|
|
|
111
|
+
## 0.1.146 (2026-04-25)
|
|
112
|
+
|
|
113
|
+
### Fix: INITIAL SYNC re-fetched whole dataset on every reload (cursor race)
|
|
114
|
+
|
|
115
|
+
`syncMetaCache` was populated lazily inside `loadCollectionToInMem` — one
|
|
116
|
+
collection at a time, only after that collection's records finished hydrating
|
|
117
|
+
into in-memory state. When `ConnectionManager.tryGoOnline` fired
|
|
118
|
+
`sync("INITIAL SYNC")` on WS connect (or `setSyncOnlyTheseCollections`
|
|
119
|
+
expanded the allowed set under a fresh login), the sync engine read
|
|
120
|
+
`syncMetaCache.get(collection)?.lastSyncTs` for each collection — and for
|
|
121
|
+
collections whose hydration hadn't completed yet, it found nothing and fell
|
|
122
|
+
back to `timestamp: 0` (`SyncEngine.ts:94`). The server then returned every
|
|
123
|
+
matching row since epoch, not a delta.
|
|
124
|
+
|
|
125
|
+
In one observed reproducer with 62 sync'd collections, 58 of them arrived at
|
|
126
|
+
the server with `timestamp: 0` and the server replied with 60 131 rows on a
|
|
127
|
+
session that already had 58 745 rows cached locally — i.e. ~full re-fetch
|
|
128
|
+
every reload, regardless of how recently the client had synced.
|
|
129
|
+
|
|
130
|
+
`init()` now eagerly preloads sync cursors for every registered collection
|
|
131
|
+
into `syncMetaCache` before `connectionManager.startTimers()` runs, via a
|
|
132
|
+
parallel fan-out of `getSyncMeta` reads. Cursor cache availability is
|
|
133
|
+
decoupled from in-mem record hydration: a sync triggered the moment WS comes
|
|
134
|
+
up reads a fully populated cache, regardless of where hydration is. One Dexie
|
|
135
|
+
point-lookup per registered collection — cheap.
|
|
136
|
+
|
|
137
|
+
The lazy populate inside `loadCollectionToInMem` is retained as a defensive
|
|
138
|
+
overwrite for collections registered after init (e.g. via dynamic
|
|
139
|
+
`addCollection`).
|
|
140
|
+
|
|
141
|
+
## 0.1.145 (2026-04-25)
|
|
142
|
+
|
|
143
|
+
### Fix: `onSyncProgress` back-track during initial sync
|
|
144
|
+
|
|
145
|
+
`onSyncProgress` is fired from two distinct phases that can run **concurrently**
|
|
146
|
+
during initial sync — Dexie → in-mem hydration (`SyncedDb.loadCollectionsToInMem`)
|
|
147
|
+
and server → Dexie download (`SyncEngine.findNewerManyStream`). Each phase
|
|
148
|
+
carries its own `loaded`/`total`, so consumers wiring the callback into a single
|
|
149
|
+
progress bar saw the percentage back-track every time a tick from the other
|
|
150
|
+
phase arrived (e.g. dexie 9/58 → server 1/62 → dexie 10/58 → server 2/62 …).
|
|
151
|
+
|
|
152
|
+
The payload now carries a `phase: 'dexie' | 'server'` discriminator so consumers
|
|
153
|
+
can attribute each tick to its source and either filter or render the two
|
|
154
|
+
streams separately. **Non-breaking**: consumers that destructure
|
|
155
|
+
`{ collection, loaded, total }` and ignore `phase` keep working unchanged.
|
|
156
|
+
|
|
157
|
+
Type change in `I_SyncedDb.SyncedDbConfig` and internal `SyncEngineCallbacks`:
|
|
158
|
+
```ts
|
|
159
|
+
onSyncProgress?: (info: {
|
|
160
|
+
phase: 'dexie' | 'server';
|
|
161
|
+
collection: string;
|
|
162
|
+
loaded: number;
|
|
163
|
+
total: number;
|
|
164
|
+
items: number;
|
|
165
|
+
}) => void;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The JSDoc on `onSyncProgress` previously claimed "Fires only during
|
|
169
|
+
init/setSyncOnlyTheseCollections" — that was wrong; it also fires during server
|
|
170
|
+
sync. Doc corrected.
|
|
171
|
+
|
|
111
172
|
## 0.1.136 (2026-04-20)
|
|
112
173
|
|
|
113
174
|
- `DexieDb.saveMany` is now fail-safe:
|
package/dist/index.js
CHANGED
|
@@ -2502,6 +2502,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2502
2502
|
if (!completedCollections.has(collection)) {
|
|
2503
2503
|
completedCollections.add(collection);
|
|
2504
2504
|
this.callbackSafe(this.callbacks.onSyncProgress, {
|
|
2505
|
+
phase: "server",
|
|
2505
2506
|
collection,
|
|
2506
2507
|
loaded: completedCollections.size,
|
|
2507
2508
|
total: syncSpecs.length,
|
|
@@ -3800,6 +3801,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
3800
3801
|
}
|
|
3801
3802
|
}
|
|
3802
3803
|
await this.pendingChanges.recoverPendingWrites();
|
|
3804
|
+
await this.preloadAllSyncMetas();
|
|
3803
3805
|
const allowedColls = [...this.collections.keys()].filter((n) => this.isSyncAllowed(n));
|
|
3804
3806
|
await this.loadCollectionsToInMem(allowedColls, "init");
|
|
3805
3807
|
this.leaderElection.init();
|
|
@@ -4983,6 +4985,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4983
4985
|
totalItems += items;
|
|
4984
4986
|
loaded++;
|
|
4985
4987
|
this.safeCallback(this.onSyncProgress, {
|
|
4988
|
+
phase: "dexie",
|
|
4986
4989
|
collection: name,
|
|
4987
4990
|
loaded,
|
|
4988
4991
|
total: names.length,
|
|
@@ -5017,6 +5020,25 @@ var _SyncedDb = class _SyncedDb {
|
|
|
5017
5020
|
}
|
|
5018
5021
|
return allItems.length;
|
|
5019
5022
|
}
|
|
5023
|
+
/**
|
|
5024
|
+
* Bulk-read sync cursors for every registered collection into syncMetaCache.
|
|
5025
|
+
* Called once during init() before sync can fire. Decouples cursor cache
|
|
5026
|
+
* availability from in-mem record hydration, eliminating the race where a
|
|
5027
|
+
* sync triggered by ConnectionManager.tryGoOnline (or by setSyncOnlyTheseCollections
|
|
5028
|
+
* expanding the allowed set) reads an unpopulated cache and sends timestamp:0
|
|
5029
|
+
* for un-hydrated collections.
|
|
5030
|
+
*/
|
|
5031
|
+
async preloadAllSyncMetas() {
|
|
5032
|
+
const names = [...this.collections.keys()];
|
|
5033
|
+
const results = await Promise.all(
|
|
5034
|
+
names.map(
|
|
5035
|
+
(name) => this.dexieDb.getSyncMeta(name).then((meta) => ({ name, meta }))
|
|
5036
|
+
)
|
|
5037
|
+
);
|
|
5038
|
+
for (const { name, meta } of results) {
|
|
5039
|
+
if (meta) this.syncMetaCache.set(name, meta);
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5020
5042
|
assertCollection(name) {
|
|
5021
5043
|
if (!this.collections.has(name)) {
|
|
5022
5044
|
throw new Error(`SyncedDb: Collection "${(name == null ? void 0 : name.toString()) || "?"}" not configured`);
|
|
@@ -291,6 +291,15 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
291
291
|
*/
|
|
292
292
|
private loadCollectionsToInMem;
|
|
293
293
|
private loadCollectionToInMem;
|
|
294
|
+
/**
|
|
295
|
+
* Bulk-read sync cursors for every registered collection into syncMetaCache.
|
|
296
|
+
* Called once during init() before sync can fire. Decouples cursor cache
|
|
297
|
+
* availability from in-mem record hydration, eliminating the race where a
|
|
298
|
+
* sync triggered by ConnectionManager.tryGoOnline (or by setSyncOnlyTheseCollections
|
|
299
|
+
* expanding the allowed set) reads an unpopulated cache and sends timestamp:0
|
|
300
|
+
* for un-hydrated collections.
|
|
301
|
+
*/
|
|
302
|
+
private preloadAllSyncMetas;
|
|
294
303
|
private assertCollection;
|
|
295
304
|
private static readonly STRINGIFIED_FALSY;
|
|
296
305
|
/** Stringify an Id parameter (ObjectId → hex string). */
|
|
@@ -345,8 +345,15 @@ export interface SyncedDbConfig {
|
|
|
345
345
|
totalItems: number;
|
|
346
346
|
durationMs: number;
|
|
347
347
|
}) => void;
|
|
348
|
-
/**
|
|
348
|
+
/**
|
|
349
|
+
* Callback after each collection completes loading. Fires from two distinct phases that can run
|
|
350
|
+
* concurrently — `phase` discriminates the source so consumers can render progress coherently:
|
|
351
|
+
* - `'dexie'` — Dexie → in-memory hydration during init/setSyncOnlyTheseCollections
|
|
352
|
+
* - `'server'` — server → Dexie download during full/initial sync (findNewerManyStream)
|
|
353
|
+
* `loaded`/`total` are scoped to the phase that emitted the event.
|
|
354
|
+
*/
|
|
349
355
|
onSyncProgress?: (info: {
|
|
356
|
+
phase: 'dexie' | 'server';
|
|
350
357
|
collection: string;
|
|
351
358
|
loaded: number;
|
|
352
359
|
total: number;
|