@drakkar.software/starfish-client 3.0.0-alpha.37 → 3.0.0-alpha.39
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.js +105 -11
- package/dist/bindings/zustand.js.map +2 -2
- package/dist/client.d.ts +47 -2
- package/dist/index.js +105 -11
- package/dist/index.js.map +2 -2
- package/dist/sync.d.ts +40 -7
- package/package.json +2 -2
package/dist/bindings/zustand.js
CHANGED
|
@@ -671,8 +671,7 @@ var StarfishClient = class {
|
|
|
671
671
|
*
|
|
672
672
|
* For the common "many docs of one collection" case prefer {@link batchPullMany}.
|
|
673
673
|
*
|
|
674
|
-
*
|
|
675
|
-
* `pull(path, { since })` (or `AppendLogCursor`) per collection.
|
|
674
|
+
* Pass `appendParams` per entry for append-only bounded-tail reads (see {@link batchPullManyAppend}).
|
|
676
675
|
*/
|
|
677
676
|
async batchPull(collections, opts = {}) {
|
|
678
677
|
const search = new URLSearchParams();
|
|
@@ -680,6 +679,27 @@ var StarfishClient = class {
|
|
|
680
679
|
if (opts.params && Object.keys(opts.params).length > 0) {
|
|
681
680
|
search.set("params", JSON.stringify(opts.params));
|
|
682
681
|
}
|
|
682
|
+
if (opts.appendParams && Object.keys(opts.appendParams).length > 0) {
|
|
683
|
+
for (const [col, optsArr] of Object.entries(opts.appendParams)) {
|
|
684
|
+
for (const ap of optsArr) {
|
|
685
|
+
if (ap.full) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
`batchPull: appendParams["${col}"] contains full:true \u2014 full is not supported in batch pull`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
if (ap.since != null && (!Number.isInteger(ap.since) || ap.since < 0)) {
|
|
691
|
+
throw new Error(`batchPull: appendParams["${col}"].since must be a non-negative integer`);
|
|
692
|
+
}
|
|
693
|
+
if (ap.last != null && (!Number.isInteger(ap.last) || ap.last < 0)) {
|
|
694
|
+
throw new Error(`batchPull: appendParams["${col}"].last must be a non-negative integer`);
|
|
695
|
+
}
|
|
696
|
+
if (ap.limit != null && (!Number.isInteger(ap.limit) || ap.limit < 0)) {
|
|
697
|
+
throw new Error(`batchPull: appendParams["${col}"].limit must be a non-negative integer`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
search.set("appendParams", JSON.stringify(opts.appendParams));
|
|
702
|
+
}
|
|
683
703
|
const pathAndQuery = `${this.applyNamespace("/batch/pull")}?${search.toString()}`;
|
|
684
704
|
const url = `${this.baseUrl}${pathAndQuery}`;
|
|
685
705
|
const authHeaders = await this.buildAuthHeaders("GET", pathAndQuery, void 0);
|
|
@@ -704,6 +724,44 @@ var StarfishClient = class {
|
|
|
704
724
|
const res = await this.batchPull([collection], { params: { [collection]: paramsList } });
|
|
705
725
|
return res.collections[collection] ?? [];
|
|
706
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Convenience over {@link batchPull} for reading append-only bounded tails from
|
|
729
|
+
* MANY entries of ONE collection in a single round-trip.
|
|
730
|
+
*
|
|
731
|
+
* Each request in `requests` carries optional `params` (path params) and
|
|
732
|
+
* `options` (append bounds: `since`/`last`/`limit`/`appendField`). An empty
|
|
733
|
+
* `requests` issues no request and returns `[]`.
|
|
734
|
+
*
|
|
735
|
+
* Returns an array aligned to `requests` by index. Each element is either:
|
|
736
|
+
* - the filtered array `T[]` extracted from `entry.data[appendField]`, or
|
|
737
|
+
* - `{ error: string }` if the server returned a per-entry error.
|
|
738
|
+
*
|
|
739
|
+
* The `appendField` used for extraction defaults to `"items"` and can be
|
|
740
|
+
* overridden per request via `options.appendField`.
|
|
741
|
+
*
|
|
742
|
+
* The `appendField` option is client-side only (used for result extraction, not sent to the server).
|
|
743
|
+
* It must match the collection's server-configured append field and defaults to `"items"`.
|
|
744
|
+
*
|
|
745
|
+
* Note: `full: true` is not supported in batch and is rejected client-side
|
|
746
|
+
* before the request is sent.
|
|
747
|
+
*/
|
|
748
|
+
async batchPullManyAppend(collection, requests) {
|
|
749
|
+
if (requests.length === 0) return [];
|
|
750
|
+
const paramsList = requests.map((r) => r.params ?? {});
|
|
751
|
+
const appendParamsList = requests.map(({ options: { appendField: _af, ...wireOpts } }) => wireOpts);
|
|
752
|
+
const res = await this.batchPull([collection], {
|
|
753
|
+
params: { [collection]: paramsList },
|
|
754
|
+
appendParams: { [collection]: appendParamsList }
|
|
755
|
+
});
|
|
756
|
+
const entries = res.collections[collection] ?? [];
|
|
757
|
+
return entries.map((entry, i) => {
|
|
758
|
+
if (entry.error) return { error: entry.error };
|
|
759
|
+
const appendField = requests[i]?.options.appendField ?? APPEND_DEFAULT_FIELD;
|
|
760
|
+
const data = entry.data;
|
|
761
|
+
const items = data?.[appendField];
|
|
762
|
+
return Array.isArray(items) ? items : [];
|
|
763
|
+
});
|
|
764
|
+
}
|
|
707
765
|
/**
|
|
708
766
|
* Push synced data to the server.
|
|
709
767
|
* @param path - The push endpoint path (e.g. "/push/users/abc/settings")
|
|
@@ -941,6 +999,8 @@ var SyncManager = class {
|
|
|
941
999
|
localData = {};
|
|
942
1000
|
aborted = false;
|
|
943
1001
|
lastFromCache = false;
|
|
1002
|
+
/** True once {@link seedFromCache} has successfully seeded localData from the cache. */
|
|
1003
|
+
seeded = false;
|
|
944
1004
|
constructor(options) {
|
|
945
1005
|
this.client = options.client;
|
|
946
1006
|
this.pullPath = options.pullPath;
|
|
@@ -962,6 +1022,24 @@ var SyncManager = class {
|
|
|
962
1022
|
getData() {
|
|
963
1023
|
return { ...this.localData };
|
|
964
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Returns true when `pull()` / `ingest()` should merge against the current
|
|
1027
|
+
* `localData` rather than replace it wholesale.
|
|
1028
|
+
*
|
|
1029
|
+
* Two situations establish a merge baseline:
|
|
1030
|
+
* - A successful prior pull/ingest advanced `lastCheckpoint` beyond 0 (the
|
|
1031
|
+
* normal steady-state case, unchanged since alpha.36).
|
|
1032
|
+
* - A cache seed painted `localData` via {@link seedFromCache} AND the store
|
|
1033
|
+
* uses a custom conflict resolver (i.e. NOT the default `deepMerge`). For a
|
|
1034
|
+
* union/custom resolver the seeded snapshot is a real baseline that must not
|
|
1035
|
+
* be clobbered by a short first live response (a cache-fallback on 429/5xx
|
|
1036
|
+
* or a momentarily-short concurrent server snapshot). For the default
|
|
1037
|
+
* `deepMerge` resolver we keep the pre-fix wholesale-replace behaviour so
|
|
1038
|
+
* non-union stores are byte-identical to alpha.36.
|
|
1039
|
+
*/
|
|
1040
|
+
hasMergeBaseline() {
|
|
1041
|
+
return this.lastCheckpoint > 0 || this.seeded && this.onConflict !== deepMerge;
|
|
1042
|
+
}
|
|
965
1043
|
/**
|
|
966
1044
|
* Merge a remote snapshot with local (optimistic) data using this manager's
|
|
967
1045
|
* conflict resolver — the same resolver the push-conflict path uses. A plain
|
|
@@ -996,7 +1074,19 @@ var SyncManager = class {
|
|
|
996
1074
|
* WITHOUT touching the network, decrypting in memory for E2E collections.
|
|
997
1075
|
* Returns whether anything was seeded (false on a miss, an expired entry, or
|
|
998
1076
|
* a decrypt failure — e.g. keyring skew). Call once on store creation before
|
|
999
|
-
* the initial live {@link pull}
|
|
1077
|
+
* the initial live {@link pull}.
|
|
1078
|
+
*
|
|
1079
|
+
* `lastCheckpoint` is intentionally left at 0 so the first live pull sends a
|
|
1080
|
+
* full (re)sync request to the server, not a delta. However, for stores with
|
|
1081
|
+
* a custom conflict resolver (e.g. `createUnionMerge`) the seeded snapshot is
|
|
1082
|
+
* treated as a merge baseline: {@link hasMergeBaseline} returns true, so the
|
|
1083
|
+
* first pull/ingest merges against the seed rather than replacing it wholesale.
|
|
1084
|
+
* This closes the bootstrap window where a short first-pull response (a cache-
|
|
1085
|
+
* fallback on 429/5xx or a momentarily-short concurrent snapshot) would
|
|
1086
|
+
* silently drop items the resolver was configured to preserve. For the default
|
|
1087
|
+
* `deepMerge` resolver the first pull still takes the snapshot wholesale —
|
|
1088
|
+
* behaviour is byte-identical to alpha.36.
|
|
1089
|
+
*
|
|
1000
1090
|
* Requires the client to have been built with a `cache`.
|
|
1001
1091
|
*/
|
|
1002
1092
|
async seedFromCache() {
|
|
@@ -1012,6 +1102,7 @@ var SyncManager = class {
|
|
|
1012
1102
|
if (this.aborted) return false;
|
|
1013
1103
|
this.localData = data;
|
|
1014
1104
|
this.lastHash = cached.hash;
|
|
1105
|
+
this.seeded = true;
|
|
1015
1106
|
this.lastFromCache = true;
|
|
1016
1107
|
return true;
|
|
1017
1108
|
}
|
|
@@ -1025,12 +1116,15 @@ var SyncManager = class {
|
|
|
1025
1116
|
* {@link StarfishClientOptions.onRevalidated}) into the store.
|
|
1026
1117
|
*
|
|
1027
1118
|
* Like {@link pull}, `ingest` conflict-merges the snapshot against the
|
|
1028
|
-
* established baseline via `this.onConflict` when a
|
|
1029
|
-
* union-merge store does not lose array
|
|
1030
|
-
* (e.g. a stale cache-fallback on 429/5xx)
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1119
|
+
* established baseline via `this.onConflict` when a merge baseline exists
|
|
1120
|
+
* ({@link hasMergeBaseline}) — so a union-merge store does not lose array
|
|
1121
|
+
* items when a revalidation result (e.g. a stale cache-fallback on 429/5xx)
|
|
1122
|
+
* is a shorter snapshot. The baseline is established by either a prior
|
|
1123
|
+
* pull/ingest that advanced `lastCheckpoint`, or by a successful
|
|
1124
|
+
* {@link seedFromCache} for a store with a custom resolver. The first ingest
|
|
1125
|
+
* without such a baseline takes the snapshot wholesale (default `deepMerge`
|
|
1126
|
+
* stores are byte-identical to alpha.36). Sets `lastFromCache = false` (a
|
|
1127
|
+
* revalidation is a live response) so the binding can clear its `stale` flag.
|
|
1034
1128
|
*
|
|
1035
1129
|
* **Staleness guard**: if a `push()` advanced `lastCheckpoint` between the
|
|
1036
1130
|
* time the revalidation request was sent and the time it resolves, the
|
|
@@ -1049,7 +1143,7 @@ var SyncManager = class {
|
|
|
1049
1143
|
} else {
|
|
1050
1144
|
incoming = result.data;
|
|
1051
1145
|
}
|
|
1052
|
-
this.localData = this.
|
|
1146
|
+
this.localData = this.hasMergeBaseline() ? this.onConflict(this.localData, incoming) : incoming;
|
|
1053
1147
|
this.lastHash = result.hash;
|
|
1054
1148
|
this.lastCheckpoint = result.timestamp;
|
|
1055
1149
|
this.lastFromCache = false;
|
|
@@ -1069,7 +1163,7 @@ var SyncManager = class {
|
|
|
1069
1163
|
} else {
|
|
1070
1164
|
incoming = result.data;
|
|
1071
1165
|
}
|
|
1072
|
-
this.localData = this.
|
|
1166
|
+
this.localData = this.hasMergeBaseline() ? this.onConflict(this.localData, incoming) : incoming;
|
|
1073
1167
|
result.data = this.localData;
|
|
1074
1168
|
this.lastHash = result.hash;
|
|
1075
1169
|
this.lastCheckpoint = result.timestamp;
|