@drakkar.software/starfish-client 3.0.0-alpha.35 → 3.0.0-alpha.37
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/bindings/zustand.d.ts +2 -0
- package/dist/bindings/zustand.js +43 -17
- package/dist/bindings/zustand.js.map +2 -2
- package/dist/client.d.ts +6 -0
- package/dist/index.js +38 -16
- package/dist/index.js.map +2 -2
- package/dist/sync.d.ts +14 -4
- package/package.json +7 -2
|
@@ -119,6 +119,8 @@ export declare function deriveSyncStatus(state: StarfishState): SyncStatus;
|
|
|
119
119
|
export declare function aggregateSyncStatus(statuses: SyncStatus[]): SyncStatus;
|
|
120
120
|
/** Use the full Starfish store state and actions. */
|
|
121
121
|
export declare function useStarfish(store: StoreApi<StarfishStore>): StarfishStore;
|
|
122
|
+
/** Subscribe to a fine-grained slice of Starfish store state. Avoids re-renders on unrelated field changes. */
|
|
123
|
+
export declare function useStarfishState<T>(store: StoreApi<StarfishStore>, selector: (state: StarfishState) => T): T;
|
|
122
124
|
/** Use only the synced data, with an optional selector for fine-grained subscriptions. */
|
|
123
125
|
export declare function useStarfishData<T = Record<string, unknown>>(store: StoreApi<StarfishStore>, selector?: (data: Record<string, unknown>) => T): T;
|
|
124
126
|
/** Use the derived sync status (synced | syncing | pending | error | offline). */
|
package/dist/bindings/zustand.js
CHANGED
|
@@ -335,6 +335,12 @@ var StarfishClient = class {
|
|
|
335
335
|
cacheFallbackStatuses;
|
|
336
336
|
onRevalidated;
|
|
337
337
|
revalidating = /* @__PURE__ */ new Set();
|
|
338
|
+
/**
|
|
339
|
+
* In-memory mirror of the latest document timestamp written to each cache
|
|
340
|
+
* key via {@link writeCache}. Updated synchronously so {@link revalidateLoop}
|
|
341
|
+
* can guard against stale overwrites without an extra async cache read.
|
|
342
|
+
*/
|
|
343
|
+
latestCacheTimestamp = /* @__PURE__ */ new Map();
|
|
338
344
|
/**
|
|
339
345
|
* Installed client-side plugins. Currently stored as inert data; no
|
|
340
346
|
* hooks fire yet. Extensions can inspect this list if needed.
|
|
@@ -545,6 +551,9 @@ var StarfishClient = class {
|
|
|
545
551
|
*/
|
|
546
552
|
writeCache(cacheKey, result) {
|
|
547
553
|
if (!this.cache) return;
|
|
554
|
+
if (result.timestamp > (this.latestCacheTimestamp.get(cacheKey) ?? -1)) {
|
|
555
|
+
this.latestCacheTimestamp.set(cacheKey, result.timestamp);
|
|
556
|
+
}
|
|
548
557
|
const snapshot = {
|
|
549
558
|
data: result.data,
|
|
550
559
|
hash: result.hash,
|
|
@@ -606,8 +615,11 @@ var StarfishClient = class {
|
|
|
606
615
|
const res = await this.revalidateFetch(pathAndQuery);
|
|
607
616
|
if (res.ok) {
|
|
608
617
|
const result = await res.json();
|
|
609
|
-
this.
|
|
610
|
-
|
|
618
|
+
const latestTs = this.latestCacheTimestamp.get(cacheKey) ?? -1;
|
|
619
|
+
if (result.timestamp >= latestTs) {
|
|
620
|
+
this.writeCache(cacheKey, result);
|
|
621
|
+
this.onRevalidated?.(pathAndQuery, result);
|
|
622
|
+
}
|
|
611
623
|
return;
|
|
612
624
|
}
|
|
613
625
|
if (!fallbackSet?.has(res.status)) {
|
|
@@ -1012,20 +1024,32 @@ var SyncManager = class {
|
|
|
1012
1024
|
* action to absorb a background revalidation result (delivered via
|
|
1013
1025
|
* {@link StarfishClientOptions.onRevalidated}) into the store.
|
|
1014
1026
|
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1027
|
+
* Like {@link pull}, `ingest` conflict-merges the snapshot against the
|
|
1028
|
+
* established baseline via `this.onConflict` when a checkpoint exists — so a
|
|
1029
|
+
* union-merge store does not lose array items when a revalidation result
|
|
1030
|
+
* (e.g. a stale cache-fallback on 429/5xx) is a shorter snapshot. The first
|
|
1031
|
+
* ingest (no checkpoint yet) takes the snapshot wholesale. Sets
|
|
1032
|
+
* `lastFromCache = false` (a revalidation is a live response) so the binding
|
|
1033
|
+
* can clear its `stale` flag.
|
|
1034
|
+
*
|
|
1035
|
+
* **Staleness guard**: if a `push()` advanced `lastCheckpoint` between the
|
|
1036
|
+
* time the revalidation request was sent and the time it resolves, the
|
|
1037
|
+
* result is from an older document version. Ingesting it would clobber the
|
|
1038
|
+
* user's just-saved edit and reset `lastHash` to a stale server hash
|
|
1039
|
+
* (causing a spurious 409 on the next push). We silently drop the result in
|
|
1040
|
+
* that case — the store's post-push state is already correct.
|
|
1019
1041
|
*/
|
|
1020
1042
|
async ingest(result) {
|
|
1021
1043
|
if (this.aborted) return;
|
|
1044
|
+
if (result.timestamp < this.lastCheckpoint) return;
|
|
1045
|
+
let incoming;
|
|
1022
1046
|
if (this.encryptor) {
|
|
1023
|
-
|
|
1047
|
+
incoming = await this.encryptor.decrypt(result.data);
|
|
1024
1048
|
if (this.aborted) return;
|
|
1025
|
-
this.localData = decrypted;
|
|
1026
1049
|
} else {
|
|
1027
|
-
|
|
1050
|
+
incoming = result.data;
|
|
1028
1051
|
}
|
|
1052
|
+
this.localData = this.lastCheckpoint > 0 ? this.onConflict(this.localData, incoming) : incoming;
|
|
1029
1053
|
this.lastHash = result.hash;
|
|
1030
1054
|
this.lastCheckpoint = result.timestamp;
|
|
1031
1055
|
this.lastFromCache = false;
|
|
@@ -1038,17 +1062,15 @@ var SyncManager = class {
|
|
|
1038
1062
|
const result = await this.client.pull(this.pullPath, this.lastCheckpoint);
|
|
1039
1063
|
if (this.aborted) throw new AbortError();
|
|
1040
1064
|
this.lastFromCache = pullWasFromCache(result);
|
|
1065
|
+
let incoming;
|
|
1041
1066
|
if (this.encryptor) {
|
|
1042
|
-
|
|
1067
|
+
incoming = await this.encryptor.decrypt(result.data);
|
|
1043
1068
|
if (this.aborted) throw new AbortError();
|
|
1044
|
-
this.localData = decrypted;
|
|
1045
|
-
result.data = decrypted;
|
|
1046
|
-
} else if (this.lastCheckpoint > 0) {
|
|
1047
|
-
this.localData = deepMerge(this.localData, result.data);
|
|
1048
|
-
result.data = this.localData;
|
|
1049
1069
|
} else {
|
|
1050
|
-
|
|
1070
|
+
incoming = result.data;
|
|
1051
1071
|
}
|
|
1072
|
+
this.localData = this.lastCheckpoint > 0 ? this.onConflict(this.localData, incoming) : incoming;
|
|
1073
|
+
result.data = this.localData;
|
|
1052
1074
|
this.lastHash = result.hash;
|
|
1053
1075
|
this.lastCheckpoint = result.timestamp;
|
|
1054
1076
|
this.logger?.pullSuccess(this.loggerName, Math.round(performance.now() - start));
|
|
@@ -1343,6 +1365,9 @@ function aggregateSyncStatus(statuses) {
|
|
|
1343
1365
|
function useStarfish(store) {
|
|
1344
1366
|
return useStore(store);
|
|
1345
1367
|
}
|
|
1368
|
+
function useStarfishState(store, selector) {
|
|
1369
|
+
return useStore(store, selector);
|
|
1370
|
+
}
|
|
1346
1371
|
function useStarfishData(store, selector) {
|
|
1347
1372
|
return useStore(
|
|
1348
1373
|
store,
|
|
@@ -1403,7 +1428,7 @@ function useLastSynced(store) {
|
|
|
1403
1428
|
}, [store, computeLabel]);
|
|
1404
1429
|
useEffect(() => {
|
|
1405
1430
|
const timer = setInterval(() => {
|
|
1406
|
-
setLabel(computeLabel());
|
|
1431
|
+
if (!document.hidden) setLabel(computeLabel());
|
|
1407
1432
|
}, 5e3);
|
|
1408
1433
|
return () => clearInterval(timer);
|
|
1409
1434
|
}, [computeLabel]);
|
|
@@ -1648,6 +1673,7 @@ export {
|
|
|
1648
1673
|
useStarfishData,
|
|
1649
1674
|
useStarfishLog,
|
|
1650
1675
|
useStarfishLogItems,
|
|
1676
|
+
useStarfishState,
|
|
1651
1677
|
useSyncInit,
|
|
1652
1678
|
useSyncStatus
|
|
1653
1679
|
};
|