@syncular/client 0.25.1 → 0.25.2
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/dist/reactive-store.js +32 -17
- package/package.json +3 -3
- package/src/reactive-store.ts +37 -17
package/dist/reactive-store.js
CHANGED
|
@@ -240,6 +240,27 @@ class ObservationCache {
|
|
|
240
240
|
this.#active.clear();
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
const EMPTY_ROWS = Object.freeze([]);
|
|
244
|
+
const unreadQuerySnapshots = new Map();
|
|
245
|
+
/** One frozen snapshot per phase and availability for entries that have no
|
|
246
|
+
* successful read, so switching between unread entries keeps the snapshot and
|
|
247
|
+
* `rows` identity. */
|
|
248
|
+
function unreadQuerySnapshot(phase, availability) {
|
|
249
|
+
const key = canonicalValue([phase, availability]);
|
|
250
|
+
let snapshot = unreadQuerySnapshots.get(key);
|
|
251
|
+
if (snapshot === undefined) {
|
|
252
|
+
snapshot = Object.freeze({
|
|
253
|
+
rows: EMPTY_ROWS,
|
|
254
|
+
phase,
|
|
255
|
+
revision: undefined,
|
|
256
|
+
error: undefined,
|
|
257
|
+
isRefreshing: false,
|
|
258
|
+
availability,
|
|
259
|
+
});
|
|
260
|
+
unreadQuerySnapshots.set(key, snapshot);
|
|
261
|
+
}
|
|
262
|
+
return snapshot;
|
|
263
|
+
}
|
|
243
264
|
class QueryEntry {
|
|
244
265
|
store;
|
|
245
266
|
spec;
|
|
@@ -247,14 +268,9 @@ class QueryEntry {
|
|
|
247
268
|
cache;
|
|
248
269
|
#owner = Symbol('query-window-claim');
|
|
249
270
|
#listeners = new Set();
|
|
250
|
-
#state = {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
revision: undefined,
|
|
254
|
-
error: undefined,
|
|
255
|
-
isRefreshing: false,
|
|
256
|
-
availability: { state: 'ready' },
|
|
257
|
-
};
|
|
271
|
+
#state = unreadQuerySnapshot('loading', {
|
|
272
|
+
state: 'ready',
|
|
273
|
+
});
|
|
258
274
|
#delegate;
|
|
259
275
|
#generation = 0;
|
|
260
276
|
#scheduled = false;
|
|
@@ -351,14 +367,7 @@ class QueryEntry {
|
|
|
351
367
|
this.#desiredRevision = 0n;
|
|
352
368
|
this.#claimError = undefined;
|
|
353
369
|
this.#claimPending = false;
|
|
354
|
-
this.#state = {
|
|
355
|
-
rows: [],
|
|
356
|
-
phase: 'loading',
|
|
357
|
-
revision: undefined,
|
|
358
|
-
error: undefined,
|
|
359
|
-
isRefreshing: false,
|
|
360
|
-
availability: { state: 'ready' },
|
|
361
|
-
};
|
|
370
|
+
this.#state = unreadQuerySnapshot('loading', { state: 'ready' });
|
|
362
371
|
}
|
|
363
372
|
dispose() {
|
|
364
373
|
this.#listeners.clear();
|
|
@@ -385,7 +394,13 @@ class QueryEntry {
|
|
|
385
394
|
canonicalValue(this.#state.availability)) {
|
|
386
395
|
return;
|
|
387
396
|
}
|
|
388
|
-
this.#state =
|
|
397
|
+
this.#state =
|
|
398
|
+
next.revision === undefined &&
|
|
399
|
+
next.rows.length === 0 &&
|
|
400
|
+
next.error === undefined &&
|
|
401
|
+
!next.isRefreshing
|
|
402
|
+
? unreadQuerySnapshot(next.phase, next.availability)
|
|
403
|
+
: next;
|
|
389
404
|
for (const listener of this.#listeners)
|
|
390
405
|
listener();
|
|
391
406
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"description": "Syncular TypeScript client core — offline-first sync over SQLite (WASM/OPFS, Bun, Node)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
92
|
-
"@syncular/core": "0.25.
|
|
92
|
+
"@syncular/core": "0.25.2"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@syncular/server": "0.25.
|
|
95
|
+
"@syncular/server": "0.25.2",
|
|
96
96
|
"playwright": "1.63.0"
|
|
97
97
|
}
|
|
98
98
|
}
|
package/src/reactive-store.ts
CHANGED
|
@@ -360,17 +360,38 @@ class ObservationCache {
|
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
const EMPTY_ROWS: readonly never[] = Object.freeze([]);
|
|
364
|
+
const unreadQuerySnapshots = new Map<string, LiveQueryResult<never>>();
|
|
365
|
+
|
|
366
|
+
/** One frozen snapshot per phase and availability for entries that have no
|
|
367
|
+
* successful read, so switching between unread entries keeps the snapshot and
|
|
368
|
+
* `rows` identity. */
|
|
369
|
+
function unreadQuerySnapshot(
|
|
370
|
+
phase: LiveQueryPhase,
|
|
371
|
+
availability: SyncAvailability,
|
|
372
|
+
): LiveQueryResult<never> {
|
|
373
|
+
const key = canonicalValue([phase, availability]);
|
|
374
|
+
let snapshot = unreadQuerySnapshots.get(key);
|
|
375
|
+
if (snapshot === undefined) {
|
|
376
|
+
snapshot = Object.freeze({
|
|
377
|
+
rows: EMPTY_ROWS,
|
|
378
|
+
phase,
|
|
379
|
+
revision: undefined,
|
|
380
|
+
error: undefined,
|
|
381
|
+
isRefreshing: false,
|
|
382
|
+
availability,
|
|
383
|
+
});
|
|
384
|
+
unreadQuerySnapshots.set(key, snapshot);
|
|
385
|
+
}
|
|
386
|
+
return snapshot;
|
|
387
|
+
}
|
|
388
|
+
|
|
363
389
|
class QueryEntry<Row> implements ExternalStoreEntry<LiveQueryResult<Row>> {
|
|
364
390
|
readonly #owner = Symbol('query-window-claim');
|
|
365
391
|
readonly #listeners = new Set<() => void>();
|
|
366
|
-
#state: LiveQueryResult<Row> = {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
revision: undefined,
|
|
370
|
-
error: undefined,
|
|
371
|
-
isRefreshing: false,
|
|
372
|
-
availability: { state: 'ready' },
|
|
373
|
-
};
|
|
392
|
+
#state: LiveQueryResult<Row> = unreadQuerySnapshot('loading', {
|
|
393
|
+
state: 'ready',
|
|
394
|
+
});
|
|
374
395
|
#delegate: QueryEntry<Row> | undefined;
|
|
375
396
|
#generation = 0;
|
|
376
397
|
#scheduled = false;
|
|
@@ -476,14 +497,7 @@ class QueryEntry<Row> implements ExternalStoreEntry<LiveQueryResult<Row>> {
|
|
|
476
497
|
this.#desiredRevision = 0n;
|
|
477
498
|
this.#claimError = undefined;
|
|
478
499
|
this.#claimPending = false;
|
|
479
|
-
this.#state = {
|
|
480
|
-
rows: [],
|
|
481
|
-
phase: 'loading',
|
|
482
|
-
revision: undefined,
|
|
483
|
-
error: undefined,
|
|
484
|
-
isRefreshing: false,
|
|
485
|
-
availability: { state: 'ready' },
|
|
486
|
-
};
|
|
500
|
+
this.#state = unreadQuerySnapshot('loading', { state: 'ready' });
|
|
487
501
|
}
|
|
488
502
|
|
|
489
503
|
dispose(): void {
|
|
@@ -513,7 +527,13 @@ class QueryEntry<Row> implements ExternalStoreEntry<LiveQueryResult<Row>> {
|
|
|
513
527
|
) {
|
|
514
528
|
return;
|
|
515
529
|
}
|
|
516
|
-
this.#state =
|
|
530
|
+
this.#state =
|
|
531
|
+
next.revision === undefined &&
|
|
532
|
+
next.rows.length === 0 &&
|
|
533
|
+
next.error === undefined &&
|
|
534
|
+
!next.isRefreshing
|
|
535
|
+
? unreadQuerySnapshot(next.phase, next.availability)
|
|
536
|
+
: next;
|
|
517
537
|
for (const listener of this.#listeners) listener();
|
|
518
538
|
}
|
|
519
539
|
|