@taladb/react 0.9.2 → 0.9.4
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/index.d.mts +227 -48
- package/dist/index.d.ts +227 -48
- package/dist/index.js +416 -150
- package/dist/index.mjs +439 -160
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -33,9 +33,13 @@ var index_exports = {};
|
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
ReplicationProvider: () => ReplicationProvider,
|
|
35
35
|
TalaDBProvider: () => TalaDBProvider,
|
|
36
|
+
useAggregate: () => useAggregate,
|
|
36
37
|
useCollection: () => useCollection,
|
|
38
|
+
useCollectionOptions: () => useCollectionOptions,
|
|
39
|
+
useCoverage: () => useCoverage,
|
|
37
40
|
useFind: () => useFind,
|
|
38
41
|
useFindOne: () => useFindOne,
|
|
42
|
+
useHydrationProgress: () => useHydrationProgress,
|
|
39
43
|
useMutation: () => useMutation,
|
|
40
44
|
useQueries: () => useQueries,
|
|
41
45
|
useQuery: () => useQuery,
|
|
@@ -48,9 +52,29 @@ module.exports = __toCommonJS(index_exports);
|
|
|
48
52
|
var import_react = require("react");
|
|
49
53
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
50
54
|
var TalaDBContext = (0, import_react.createContext)(null);
|
|
55
|
+
var CollectionOptionsContext = (0, import_react.createContext)({
|
|
56
|
+
get: () => void 0
|
|
57
|
+
});
|
|
58
|
+
function useCollectionOptions() {
|
|
59
|
+
return (0, import_react.useContext)(CollectionOptionsContext);
|
|
60
|
+
}
|
|
61
|
+
function CollectionOptionsProvider({
|
|
62
|
+
collections,
|
|
63
|
+
children
|
|
64
|
+
}) {
|
|
65
|
+
const latest = (0, import_react.useRef)(collections);
|
|
66
|
+
latest.current = collections;
|
|
67
|
+
const resolver = (0, import_react.useMemo)(
|
|
68
|
+
() => ({
|
|
69
|
+
get: (name) => latest.current?.[name]
|
|
70
|
+
}),
|
|
71
|
+
[]
|
|
72
|
+
);
|
|
73
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollectionOptionsContext.Provider, { value: resolver, children });
|
|
74
|
+
}
|
|
51
75
|
function TalaDBProvider(props) {
|
|
52
76
|
if ("db" in props && props.db) {
|
|
53
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TalaDBContext.Provider, { value: props.db, children: props.children });
|
|
77
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TalaDBContext.Provider, { value: props.db, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollectionOptionsProvider, { collections: props.collections, children: props.children }) });
|
|
54
78
|
}
|
|
55
79
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NamedProvider, { ...props });
|
|
56
80
|
}
|
|
@@ -58,6 +82,7 @@ function NamedProvider({
|
|
|
58
82
|
name,
|
|
59
83
|
options,
|
|
60
84
|
fallback = null,
|
|
85
|
+
collections,
|
|
61
86
|
children
|
|
62
87
|
}) {
|
|
63
88
|
const [db, setDb] = (0, import_react.useState)(null);
|
|
@@ -85,7 +110,7 @@ function NamedProvider({
|
|
|
85
110
|
}, [name, optionsKey]);
|
|
86
111
|
if (error !== null) throw error;
|
|
87
112
|
if (db === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
88
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TalaDBContext.Provider, { value: db, children });
|
|
113
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TalaDBContext.Provider, { value: db, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollectionOptionsProvider, { collections, children }) });
|
|
89
114
|
}
|
|
90
115
|
function useTalaDB() {
|
|
91
116
|
const db = (0, import_react.useContext)(TalaDBContext);
|
|
@@ -97,9 +122,15 @@ function useTalaDB() {
|
|
|
97
122
|
|
|
98
123
|
// src/useCollection.ts
|
|
99
124
|
var import_react2 = require("react");
|
|
100
|
-
function useCollection(name) {
|
|
125
|
+
function useCollection(name, options) {
|
|
101
126
|
const db = useTalaDB();
|
|
102
|
-
|
|
127
|
+
const registry = useCollectionOptions();
|
|
128
|
+
const explicit = (0, import_react2.useRef)(options);
|
|
129
|
+
explicit.current = options;
|
|
130
|
+
return (0, import_react2.useMemo)(
|
|
131
|
+
() => db.collection(name, explicit.current ?? registry.get(name)),
|
|
132
|
+
[db, name, registry]
|
|
133
|
+
);
|
|
103
134
|
}
|
|
104
135
|
|
|
105
136
|
// src/useFind.ts
|
|
@@ -149,8 +180,35 @@ function useFindOne(collection, filter) {
|
|
|
149
180
|
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
150
181
|
}
|
|
151
182
|
|
|
152
|
-
// src/
|
|
183
|
+
// src/useAggregate.ts
|
|
153
184
|
var import_react5 = require("react");
|
|
185
|
+
function useAggregate(collection, pipeline) {
|
|
186
|
+
const snapshotRef = (0, import_react5.useRef)({ data: [], loading: true, error: null });
|
|
187
|
+
const pipelineKey = JSON.stringify(pipeline);
|
|
188
|
+
const subscribe = (0, import_react5.useCallback)(
|
|
189
|
+
(notify) => {
|
|
190
|
+
snapshotRef.current = { data: snapshotRef.current.data, loading: true, error: null };
|
|
191
|
+
return collection.subscribeAggregate(
|
|
192
|
+
pipeline,
|
|
193
|
+
(docs) => {
|
|
194
|
+
snapshotRef.current = { data: docs, loading: false, error: null };
|
|
195
|
+
notify();
|
|
196
|
+
},
|
|
197
|
+
(error) => {
|
|
198
|
+
snapshotRef.current = { ...snapshotRef.current, loading: false, error };
|
|
199
|
+
notify();
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
},
|
|
203
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
204
|
+
[collection, pipelineKey]
|
|
205
|
+
);
|
|
206
|
+
const getSnapshot = (0, import_react5.useCallback)(() => snapshotRef.current, []);
|
|
207
|
+
return (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/replication/config.tsx
|
|
211
|
+
var import_react7 = require("react");
|
|
154
212
|
|
|
155
213
|
// src/replication/engine.ts
|
|
156
214
|
var import_taladb = require("taladb");
|
|
@@ -203,20 +261,140 @@ async function replicateWithRetry(db, config, collection, direction) {
|
|
|
203
261
|
throw lastError;
|
|
204
262
|
}
|
|
205
263
|
|
|
206
|
-
// src/replication/
|
|
264
|
+
// src/replication/provider.tsx
|
|
265
|
+
var import_react6 = require("react");
|
|
266
|
+
var import_taladb2 = require("taladb");
|
|
207
267
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
208
|
-
var ReplicationContext = (0,
|
|
209
|
-
function
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
(
|
|
268
|
+
var ReplicationContext = (0, import_react6.createContext)(null);
|
|
269
|
+
function whenIdle(fn) {
|
|
270
|
+
const ric = globalThis.requestIdleCallback;
|
|
271
|
+
if (typeof ric === "function") {
|
|
272
|
+
const handle = ric(fn, { timeout: 2e3 });
|
|
273
|
+
return () => {
|
|
274
|
+
const cic = globalThis.cancelIdleCallback;
|
|
275
|
+
cic?.(handle);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const t = setTimeout(fn, 0);
|
|
279
|
+
return () => clearTimeout(t);
|
|
280
|
+
}
|
|
281
|
+
var yieldToUi = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
282
|
+
function ReplicationScopes({ replicate: replicate2, children }) {
|
|
283
|
+
const db = useTalaDB();
|
|
284
|
+
const collectionOptions = useCollectionOptions();
|
|
285
|
+
const [coverage, setCoverage] = (0, import_react6.useState)({});
|
|
286
|
+
const registryKey = JSON.stringify(
|
|
287
|
+
Object.fromEntries(
|
|
288
|
+
Object.entries(replicate2).map(([name, s]) => [
|
|
289
|
+
name,
|
|
290
|
+
{
|
|
291
|
+
endpoint: s.endpoint,
|
|
292
|
+
origin: s.origin,
|
|
293
|
+
scope: s.scope,
|
|
294
|
+
projectionVersion: s.projectionVersion,
|
|
295
|
+
schemaVersion: s.schemaVersion,
|
|
296
|
+
key: s.key,
|
|
297
|
+
hydrate: s.hydrate,
|
|
298
|
+
pageSize: s.pageSize,
|
|
299
|
+
refreshMs: s.refreshMs,
|
|
300
|
+
bridge: s.bridge,
|
|
301
|
+
source: s.source ? {
|
|
302
|
+
origin: s.source.origin,
|
|
303
|
+
collection: s.source.collection,
|
|
304
|
+
scope: s.source.scope,
|
|
305
|
+
projectionVersion: s.source.projectionVersion,
|
|
306
|
+
schemaVersion: s.source.schemaVersion,
|
|
307
|
+
configVersion: s.source.configVersion
|
|
308
|
+
} : null
|
|
309
|
+
}
|
|
310
|
+
])
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
const latest = (0, import_react6.useRef)(replicate2);
|
|
314
|
+
latest.current = replicate2;
|
|
315
|
+
const coordinators = (0, import_react6.useMemo)(() => {
|
|
316
|
+
const map = /* @__PURE__ */ new Map();
|
|
317
|
+
for (const [collection, scope] of Object.entries(latest.current)) {
|
|
318
|
+
const source = scope.source ?? (0, import_taladb2.createRestSource)({ ...scope, collection });
|
|
319
|
+
map.set(
|
|
320
|
+
collection,
|
|
321
|
+
new import_taladb2.ReplicationCoordinator(db, source, {
|
|
322
|
+
pageSize: scope.pageSize,
|
|
323
|
+
yieldFn: yieldToUi,
|
|
324
|
+
onProgress: (state) => setCoverage((prev) => ({ ...prev, [collection]: state })),
|
|
325
|
+
collectionOptions: collectionOptions.get(collection)
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
return map;
|
|
330
|
+
}, [db, registryKey, collectionOptions]);
|
|
331
|
+
(0, import_react6.useEffect)(() => {
|
|
332
|
+
let cancelled = false;
|
|
333
|
+
void (async () => {
|
|
334
|
+
const seeded = {};
|
|
335
|
+
for (const [collection, coord] of coordinators) {
|
|
336
|
+
seeded[collection] = await coord.getCoverage();
|
|
337
|
+
}
|
|
338
|
+
if (!cancelled) setCoverage(seeded);
|
|
339
|
+
})();
|
|
340
|
+
return () => {
|
|
341
|
+
cancelled = true;
|
|
342
|
+
};
|
|
343
|
+
}, [coordinators]);
|
|
344
|
+
(0, import_react6.useEffect)(() => {
|
|
345
|
+
const cancels = [];
|
|
346
|
+
for (const [collection, coord] of coordinators) {
|
|
347
|
+
const mode = latest.current[collection]?.hydrate ?? "idle";
|
|
348
|
+
if (mode === "manual") continue;
|
|
349
|
+
const start = () => {
|
|
350
|
+
void coord.hydrate().catch(() => {
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
if (mode === "eager") start();
|
|
354
|
+
else cancels.push(whenIdle(start));
|
|
355
|
+
}
|
|
356
|
+
return () => cancels.forEach((c) => c());
|
|
357
|
+
}, [coordinators]);
|
|
358
|
+
(0, import_react6.useEffect)(() => {
|
|
359
|
+
const timers = [];
|
|
360
|
+
for (const [collection, coord] of coordinators) {
|
|
361
|
+
const ms = latest.current[collection]?.refreshMs ?? 0;
|
|
362
|
+
if (ms > 0) {
|
|
363
|
+
timers.push(setInterval(() => void coord.refresh().catch(() => {
|
|
364
|
+
}), ms));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return () => timers.forEach(clearInterval);
|
|
368
|
+
}, [coordinators]);
|
|
369
|
+
const value = (0, import_react6.useMemo)(
|
|
370
|
+
() => ({ coordinators, scopes: latest.current, coverage }),
|
|
371
|
+
[coordinators, coverage]
|
|
372
|
+
);
|
|
373
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReplicationContext.Provider, { value, children });
|
|
374
|
+
}
|
|
375
|
+
function useReplication() {
|
|
376
|
+
return (0, import_react6.useContext)(ReplicationContext);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/replication/config.tsx
|
|
380
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
381
|
+
var ReplicationContext2 = (0, import_react7.createContext)(null);
|
|
382
|
+
function ReplicationProvider({
|
|
383
|
+
children,
|
|
384
|
+
replicate: replicate2,
|
|
385
|
+
...config
|
|
386
|
+
}) {
|
|
387
|
+
const key = `${config.endpoint ?? ""}|${config.pollMs ?? ""}|${JSON.stringify(config.paths ?? null)}|${JSON.stringify(config.prefetch ?? null)}|${config.prefetchMode ?? ""}|${config.prefetchConcurrency ?? ""}`;
|
|
388
|
+
const value = (0, import_react7.useMemo)(
|
|
389
|
+
() => config.endpoint ? config : null,
|
|
213
390
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
214
391
|
[key]
|
|
215
392
|
);
|
|
216
|
-
|
|
217
|
-
value
|
|
393
|
+
const inner = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(ReplicationContext2.Provider, { value, children: [
|
|
394
|
+
value?.prefetch && value.prefetch.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PrefetchRunner, {}) : null,
|
|
218
395
|
children
|
|
219
396
|
] });
|
|
397
|
+
return replicate2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReplicationScopes, { replicate: replicate2, children: inner }) : inner;
|
|
220
398
|
}
|
|
221
399
|
function resolveReplicationConfig(base, overrides) {
|
|
222
400
|
const endpoint = overrides?.endpoint ?? base?.endpoint;
|
|
@@ -233,10 +411,10 @@ function resolveReplicationConfig(base, overrides) {
|
|
|
233
411
|
};
|
|
234
412
|
}
|
|
235
413
|
function useReplicationBase() {
|
|
236
|
-
return (0,
|
|
414
|
+
return (0, import_react7.useContext)(ReplicationContext2);
|
|
237
415
|
}
|
|
238
416
|
function useReplicationConfig(overrides) {
|
|
239
|
-
return resolveReplicationConfig((0,
|
|
417
|
+
return resolveReplicationConfig((0, import_react7.useContext)(ReplicationContext2), overrides);
|
|
240
418
|
}
|
|
241
419
|
var CURSOR_COLLECTION = "__taladb_sync";
|
|
242
420
|
function normalizePrefetch(entries) {
|
|
@@ -266,10 +444,10 @@ function PrefetchRunner() {
|
|
|
266
444
|
const slices = normalizePrefetch(base?.prefetch);
|
|
267
445
|
const mode = base?.prefetchMode ?? "once";
|
|
268
446
|
const concurrency = Math.max(1, base?.prefetchConcurrency ?? 2);
|
|
269
|
-
const baseRef = (0,
|
|
447
|
+
const baseRef = (0, import_react7.useRef)(base);
|
|
270
448
|
baseRef.current = base;
|
|
271
449
|
const sig = JSON.stringify({ slices, mode, concurrency, endpoint: base?.endpoint ?? null });
|
|
272
|
-
(0,
|
|
450
|
+
(0, import_react7.useEffect)(() => {
|
|
273
451
|
if (slices.length === 0) return void 0;
|
|
274
452
|
let cancelled = false;
|
|
275
453
|
const cancelSchedule = schedule(() => {
|
|
@@ -304,167 +482,251 @@ function PrefetchRunner() {
|
|
|
304
482
|
return null;
|
|
305
483
|
}
|
|
306
484
|
|
|
485
|
+
// src/useCoverage.ts
|
|
486
|
+
var import_taladb3 = require("taladb");
|
|
487
|
+
function useCoverage(collection) {
|
|
488
|
+
const replication = useReplication();
|
|
489
|
+
const state = replication?.coverage[collection] ?? { status: "empty" };
|
|
490
|
+
return {
|
|
491
|
+
status: state.status,
|
|
492
|
+
ready: (0, import_taladb3.isAuthoritative)(state),
|
|
493
|
+
rows: (0, import_taladb3.rowsApplied)(state),
|
|
494
|
+
total: "total" in state ? state.total : void 0,
|
|
495
|
+
progress: (0, import_taladb3.progress)(state),
|
|
496
|
+
reason: state.status === "error" ? state.error : state.status === "best-effort" || state.status === "stale" ? state.reason : void 0
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
var useHydrationProgress = useCoverage;
|
|
500
|
+
|
|
307
501
|
// src/useQuery.ts
|
|
308
|
-
var
|
|
502
|
+
var import_react8 = require("react");
|
|
309
503
|
function useQuery(options) {
|
|
310
|
-
const { collection, filter,
|
|
311
|
-
const networked = source !== "local-only";
|
|
312
|
-
const db = useTalaDB();
|
|
504
|
+
const { collection, filter, sort, page, limit, skip, enabled = true } = options;
|
|
313
505
|
const col = useCollection(collection);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
const [
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const refetch = (0, import_react6.useCallback)(async () => {
|
|
329
|
-
const cfg = configRef.current;
|
|
330
|
-
if (!networked || !cfg) return;
|
|
506
|
+
const db = useTalaDB();
|
|
507
|
+
const coverage = useCoverage(collection);
|
|
508
|
+
const replication = useReplication();
|
|
509
|
+
const coord = replication?.coordinators.get(collection);
|
|
510
|
+
const legacyNetworked = !coord && options.source !== "local-only";
|
|
511
|
+
const { config: legacyConfig, pollMs } = useReplicationConfig(options);
|
|
512
|
+
const legacyConfigRef = (0, import_react8.useRef)(legacyConfig);
|
|
513
|
+
legacyConfigRef.current = legacyConfig;
|
|
514
|
+
const [syncing, setSyncing] = (0, import_react8.useState)(false);
|
|
515
|
+
const [syncError, setSyncError] = (0, import_react8.useState)(null);
|
|
516
|
+
const [firstSyncDone, setFirstSyncDone] = (0, import_react8.useState)(false);
|
|
517
|
+
const legacyRefetch = (0, import_react8.useCallback)(async () => {
|
|
518
|
+
const cfg = legacyConfigRef.current;
|
|
519
|
+
if (!legacyNetworked || !cfg) return;
|
|
331
520
|
setSyncing(true);
|
|
332
521
|
setSyncError(null);
|
|
333
522
|
try {
|
|
334
523
|
await replicate(db, cfg, collection, "pull");
|
|
335
|
-
} catch (
|
|
336
|
-
setSyncError(
|
|
524
|
+
} catch (error) {
|
|
525
|
+
setSyncError(error);
|
|
337
526
|
} finally {
|
|
338
527
|
setSyncing(false);
|
|
339
528
|
setFirstSyncDone(true);
|
|
340
529
|
}
|
|
341
|
-
}, [db, collection,
|
|
342
|
-
(0,
|
|
343
|
-
if (!
|
|
344
|
-
void
|
|
530
|
+
}, [db, collection, legacyNetworked, legacyConfig?.endpoint]);
|
|
531
|
+
(0, import_react8.useEffect)(() => {
|
|
532
|
+
if (!enabled || !legacyNetworked || !legacyConfig) return;
|
|
533
|
+
void legacyRefetch();
|
|
345
534
|
if (pollMs > 0) {
|
|
346
|
-
const
|
|
347
|
-
return () => clearInterval(
|
|
535
|
+
const timer = setInterval(() => void legacyRefetch(), pollMs);
|
|
536
|
+
return () => clearInterval(timer);
|
|
348
537
|
}
|
|
349
538
|
return void 0;
|
|
350
|
-
}, [
|
|
351
|
-
|
|
539
|
+
}, [enabled, legacyNetworked, legacyConfig?.endpoint, pollMs, legacyRefetch]);
|
|
540
|
+
const offset = page !== void 0 && limit !== void 0 ? (page - 1) * limit : skip ?? 0;
|
|
541
|
+
const filterKey = JSON.stringify(filter ?? null);
|
|
542
|
+
const sortKey = JSON.stringify(sort ?? null);
|
|
543
|
+
const [bridgeIds, setBridgeIds] = (0, import_react8.useState)([]);
|
|
544
|
+
const [fetchError, setFetchError] = (0, import_react8.useState)(null);
|
|
545
|
+
const scopeValue = coord?.replicaScope;
|
|
546
|
+
const bridgeIdKey = (bridgeIds ?? []).join("|");
|
|
547
|
+
const pipeline = (0, import_react8.useMemo)(() => {
|
|
548
|
+
const stages = [];
|
|
549
|
+
const scoped = scopeValue ? { _replica_scope: scopeValue } : void 0;
|
|
550
|
+
const bridgeOnly = !coverage.ready ? { _id: { $in: bridgeIds ?? [] } } : void 0;
|
|
551
|
+
const matches = [scoped, bridgeOnly, filter].filter(Boolean);
|
|
552
|
+
if (matches.length === 1) stages.push({ $match: matches[0] });
|
|
553
|
+
else if (matches.length > 1) stages.push({ $match: { $and: matches } });
|
|
554
|
+
if (sort) stages.push({ $sort: sort });
|
|
555
|
+
if (coverage.ready && offset > 0) stages.push({ $skip: offset });
|
|
556
|
+
if (limit !== void 0) stages.push({ $limit: limit });
|
|
557
|
+
return stages;
|
|
558
|
+
}, [filterKey, sortKey, offset, limit, coverage.ready, scopeValue, bridgeIdKey]);
|
|
559
|
+
const read = useAggregate(col, enabled ? pipeline : [{ $limit: 0 }]);
|
|
560
|
+
const [fetching, setFetching] = (0, import_react8.useState)(false);
|
|
561
|
+
const bridgeKey = `${collection}|${filterKey}|${sortKey}|${offset}|${limit}`;
|
|
562
|
+
const canBridge = replication?.scopes[collection]?.bridge !== false;
|
|
563
|
+
(0, import_react8.useEffect)(() => {
|
|
564
|
+
if (!enabled || coverage.ready || !canBridge) return;
|
|
565
|
+
if (!coord) return;
|
|
566
|
+
let cancelled = false;
|
|
567
|
+
setFetching(true);
|
|
568
|
+
setFetchError(null);
|
|
569
|
+
setBridgeIds([]);
|
|
570
|
+
void coord.bridge({
|
|
571
|
+
filter,
|
|
572
|
+
sort,
|
|
573
|
+
page,
|
|
574
|
+
limit
|
|
575
|
+
}).then((result) => setBridgeIds(result.ids ?? [])).catch((error) => {
|
|
576
|
+
if (!cancelled) setFetchError(error);
|
|
577
|
+
}).finally(() => {
|
|
578
|
+
if (!cancelled) setFetching(false);
|
|
579
|
+
});
|
|
580
|
+
return () => {
|
|
581
|
+
cancelled = true;
|
|
582
|
+
};
|
|
583
|
+
}, [bridgeKey, coverage.ready, canBridge, enabled, coord]);
|
|
584
|
+
const refetch = async () => {
|
|
585
|
+
if (coord) await coord.refresh();
|
|
586
|
+
else await legacyRefetch();
|
|
587
|
+
};
|
|
588
|
+
if (enabled && legacyNetworked && !legacyConfig) {
|
|
352
589
|
throw new Error(
|
|
353
|
-
`useQuery({ collection: '${collection}' }) needs
|
|
590
|
+
`useQuery({ collection: '${collection}' }) needs either a coverage-first replicate scope or a legacy sync endpoint. Use source: 'local-only' for a purely local query.`
|
|
354
591
|
);
|
|
355
592
|
}
|
|
356
|
-
|
|
357
|
-
|
|
593
|
+
return {
|
|
594
|
+
data: read.data,
|
|
595
|
+
total: coverage.total,
|
|
596
|
+
loading: options.source === "remote-first" && legacyNetworked ? read.loading || !firstSyncDone : read.loading,
|
|
597
|
+
error: read.error ?? fetchError,
|
|
598
|
+
fetchError,
|
|
599
|
+
coverage,
|
|
600
|
+
fetching,
|
|
601
|
+
syncing,
|
|
602
|
+
syncError,
|
|
603
|
+
refetch
|
|
604
|
+
};
|
|
358
605
|
}
|
|
359
606
|
|
|
360
607
|
// src/useQueries.ts
|
|
361
|
-
var
|
|
362
|
-
var NOOP_REFETCH = async () => {
|
|
363
|
-
};
|
|
364
|
-
function emptyResult() {
|
|
365
|
-
return { data: [], loading: true, error: null, syncing: false, syncError: null, refetch: NOOP_REFETCH };
|
|
366
|
-
}
|
|
608
|
+
var import_react9 = require("react");
|
|
367
609
|
function useQueries(queries) {
|
|
368
610
|
const db = useTalaDB();
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
const sig = JSON.stringify(
|
|
611
|
+
const registry = useCollectionOptions();
|
|
612
|
+
const replication = useReplication();
|
|
613
|
+
const [results, setResults] = (0, import_react9.useState)(() => queries.map(() => ({ data: [], loading: true, error: null })));
|
|
614
|
+
const [bridgeIds, setBridgeIds] = (0, import_react9.useState)({});
|
|
615
|
+
const [fetchErrors, setFetchErrors] = (0, import_react9.useState)({});
|
|
616
|
+
const signature = JSON.stringify(
|
|
379
617
|
queries.map((q) => ({
|
|
380
618
|
collection: q.collection,
|
|
381
619
|
filter: q.filter ?? null,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
620
|
+
sort: q.sort ?? null,
|
|
621
|
+
page: q.page ?? null,
|
|
622
|
+
limit: q.limit ?? null,
|
|
623
|
+
skip: q.skip ?? null,
|
|
624
|
+
enabled: q.enabled ?? true
|
|
385
625
|
}))
|
|
386
626
|
);
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
627
|
+
const latest = (0, import_react9.useRef)(queries);
|
|
628
|
+
latest.current = queries;
|
|
629
|
+
const bridgeManifestKey = JSON.stringify(bridgeIds);
|
|
630
|
+
const replicationReadKey = JSON.stringify(
|
|
631
|
+
queries.map((q) => ({
|
|
632
|
+
scope: replication?.coordinators.get(q.collection)?.replicaScope ?? null,
|
|
633
|
+
ready: replication?.coverage[q.collection]?.status === "complete"
|
|
634
|
+
}))
|
|
393
635
|
);
|
|
394
|
-
(0,
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
setResults((prev) => {
|
|
400
|
-
if (i >= prev.length) return prev;
|
|
401
|
-
const copy = prev.slice();
|
|
402
|
-
copy[i] = fn(copy[i]);
|
|
403
|
-
return copy;
|
|
404
|
-
});
|
|
405
|
-
};
|
|
406
|
-
const resolved = qs.map((q, i) => {
|
|
407
|
-
const { config } = resolveReplicationConfig(b, {
|
|
408
|
-
endpoint: q.endpoint,
|
|
409
|
-
getAuth: q.getAuth,
|
|
410
|
-
fetch: q.fetch,
|
|
411
|
-
paths: q.paths,
|
|
412
|
-
pollMs: q.pollMs
|
|
413
|
-
});
|
|
414
|
-
const networked = (q.source ?? "local-first") !== "local-only";
|
|
415
|
-
const pollMs = q.pollMs ?? b?.pollMs ?? 0;
|
|
416
|
-
const refetch = async () => {
|
|
417
|
-
if (!networked || !config) return;
|
|
418
|
-
setAt(i, (r) => ({ ...r, syncing: true, syncError: null }));
|
|
419
|
-
try {
|
|
420
|
-
await replicate(db, config, q.collection, "pull");
|
|
421
|
-
} catch (e) {
|
|
422
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, syncError: e }));
|
|
423
|
-
} finally {
|
|
424
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, syncing: false }));
|
|
425
|
-
}
|
|
636
|
+
(0, import_react9.useEffect)(() => {
|
|
637
|
+
const current = latest.current;
|
|
638
|
+
setResults(current.map(() => ({ data: [], loading: true, error: null })));
|
|
639
|
+
const unsubs = current.map((q, i) => {
|
|
640
|
+
if (q.enabled === false) return () => {
|
|
426
641
|
};
|
|
427
|
-
|
|
642
|
+
const col = db.collection(q.collection, registry.get(q.collection));
|
|
643
|
+
const offset = q.page !== void 0 && q.limit !== void 0 ? (q.page - 1) * q.limit : q.skip ?? 0;
|
|
644
|
+
const pipeline = [];
|
|
645
|
+
const coord = replication?.coordinators.get(q.collection);
|
|
646
|
+
const covered = replication?.coverage[q.collection]?.status === "complete";
|
|
647
|
+
const matches = [
|
|
648
|
+
coord ? { _replica_scope: coord.replicaScope } : void 0,
|
|
649
|
+
!covered ? { _id: { $in: bridgeIds[i] ?? [] } } : void 0,
|
|
650
|
+
q.filter
|
|
651
|
+
].filter(Boolean);
|
|
652
|
+
if (matches.length === 1) pipeline.push({ $match: matches[0] });
|
|
653
|
+
else if (matches.length > 1) pipeline.push({ $match: { $and: matches } });
|
|
654
|
+
if (q.sort) pipeline.push({ $sort: q.sort });
|
|
655
|
+
if (covered && offset > 0) pipeline.push({ $skip: offset });
|
|
656
|
+
if (q.limit !== void 0) pipeline.push({ $limit: q.limit });
|
|
657
|
+
return col.subscribeAggregate(
|
|
658
|
+
pipeline,
|
|
659
|
+
(docs) => setResults((prev) => {
|
|
660
|
+
const next = [...prev];
|
|
661
|
+
next[i] = { data: docs, loading: false, error: null };
|
|
662
|
+
return next;
|
|
663
|
+
}),
|
|
664
|
+
(error) => setResults((prev) => {
|
|
665
|
+
const next = [...prev];
|
|
666
|
+
next[i] = { ...next[i], loading: false, error };
|
|
667
|
+
return next;
|
|
668
|
+
})
|
|
669
|
+
);
|
|
428
670
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
671
|
+
return () => unsubs.forEach((u) => u());
|
|
672
|
+
}, [db, registry, signature, replicationReadKey, bridgeManifestKey]);
|
|
673
|
+
(0, import_react9.useEffect)(() => {
|
|
674
|
+
for (const [i, q] of latest.current.entries()) {
|
|
675
|
+
if (q.enabled === false) continue;
|
|
676
|
+
const coord = replication?.coordinators.get(q.collection);
|
|
677
|
+
if (!coord || replication?.scopes[q.collection]?.bridge === false) continue;
|
|
678
|
+
void coord.getCoverage().then((state) => {
|
|
679
|
+
if (state.status === "complete") return;
|
|
680
|
+
return coord.bridge({
|
|
681
|
+
filter: q.filter,
|
|
682
|
+
sort: q.sort,
|
|
683
|
+
page: q.page,
|
|
684
|
+
limit: q.limit
|
|
685
|
+
}).then((result) => {
|
|
686
|
+
setBridgeIds((prev) => ({ ...prev, [i]: result.ids }));
|
|
687
|
+
setFetchErrors((prev) => {
|
|
688
|
+
const next = { ...prev };
|
|
689
|
+
delete next[i];
|
|
690
|
+
return next;
|
|
691
|
+
});
|
|
692
|
+
}).catch((error) => setFetchErrors((prev) => ({ ...prev, [i]: error })));
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}, [replication, signature]);
|
|
696
|
+
return (0, import_react9.useMemo)(
|
|
697
|
+
() => latest.current.map((q, i) => {
|
|
698
|
+
const state = replication?.coverage[q.collection] ?? { status: "empty" };
|
|
699
|
+
const coverage = {
|
|
700
|
+
status: state.status,
|
|
701
|
+
// Only `complete` licenses a local-only read — see `useCoverage`.
|
|
702
|
+
ready: state.status === "complete",
|
|
703
|
+
rows: "rowsApplied" in state ? state.rowsApplied ?? 0 : 0,
|
|
704
|
+
total: "total" in state ? state.total : void 0,
|
|
705
|
+
progress: state.status === "complete" ? 1 : void 0,
|
|
706
|
+
reason: state.status === "error" ? state.error : state.status === "best-effort" || state.status === "stale" ? state.reason : void 0
|
|
707
|
+
};
|
|
708
|
+
return {
|
|
709
|
+
data: results[i]?.data ?? [],
|
|
710
|
+
total: coverage.total,
|
|
711
|
+
loading: results[i]?.loading ?? true,
|
|
712
|
+
error: results[i]?.error ?? fetchErrors[i] ?? null,
|
|
713
|
+
fetchError: fetchErrors[i] ?? null,
|
|
714
|
+
coverage,
|
|
715
|
+
fetching: false,
|
|
434
716
|
syncing: false,
|
|
435
717
|
syncError: null,
|
|
436
|
-
refetch:
|
|
437
|
-
|
|
438
|
-
);
|
|
439
|
-
const unsubs = qs.map((q, i) => {
|
|
440
|
-
const col = db.collection(q.collection);
|
|
441
|
-
return col.subscribe(
|
|
442
|
-
q.filter ?? {},
|
|
443
|
-
(docs) => {
|
|
444
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, data: docs, loading: false, error: null }));
|
|
445
|
-
},
|
|
446
|
-
(error) => {
|
|
447
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, loading: false, error }));
|
|
718
|
+
refetch: async () => {
|
|
719
|
+
await replication?.coordinators.get(q.collection)?.refresh();
|
|
448
720
|
}
|
|
449
|
-
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
void res.refetch();
|
|
455
|
-
if (res.pollMs > 0) intervals.push(setInterval(() => void res.refetch(), res.pollMs));
|
|
456
|
-
});
|
|
457
|
-
return () => {
|
|
458
|
-
cancelled = true;
|
|
459
|
-
unsubs.forEach((u) => u());
|
|
460
|
-
intervals.forEach((id) => clearInterval(id));
|
|
461
|
-
};
|
|
462
|
-
}, [db, sig]);
|
|
463
|
-
return queries.map((_q, i) => results[i] ?? emptyResult());
|
|
721
|
+
};
|
|
722
|
+
}),
|
|
723
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
724
|
+
[results, signature, replication]
|
|
725
|
+
);
|
|
464
726
|
}
|
|
465
727
|
|
|
466
728
|
// src/useMutation.ts
|
|
467
|
-
var
|
|
729
|
+
var import_react10 = require("react");
|
|
468
730
|
function useMutation(options) {
|
|
469
731
|
const { collection, direction = "push", drainOnMount = true } = options;
|
|
470
732
|
const db = useTalaDB();
|
|
@@ -475,12 +737,12 @@ function useMutation(options) {
|
|
|
475
737
|
fetch: options.fetch,
|
|
476
738
|
paths: options.paths
|
|
477
739
|
});
|
|
478
|
-
const configRef = (0,
|
|
740
|
+
const configRef = (0, import_react10.useRef)(config);
|
|
479
741
|
configRef.current = config;
|
|
480
|
-
const [pending, setPending] = (0,
|
|
481
|
-
const [error, setError] = (0,
|
|
742
|
+
const [pending, setPending] = (0, import_react10.useState)(false);
|
|
743
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
482
744
|
const endpoint = config?.endpoint;
|
|
483
|
-
const applyLocal = (0,
|
|
745
|
+
const applyLocal = (0, import_react10.useCallback)(
|
|
484
746
|
async (op) => {
|
|
485
747
|
switch (op.type) {
|
|
486
748
|
case "insert":
|
|
@@ -496,12 +758,12 @@ function useMutation(options) {
|
|
|
496
758
|
},
|
|
497
759
|
[col]
|
|
498
760
|
);
|
|
499
|
-
const drain = (0,
|
|
761
|
+
const drain = (0, import_react10.useCallback)(async () => {
|
|
500
762
|
const cfg = configRef.current;
|
|
501
763
|
if (!cfg) return;
|
|
502
764
|
await replicateWithRetry(db, cfg, collection, direction);
|
|
503
765
|
}, [db, collection, direction, endpoint]);
|
|
504
|
-
const mutateAsync = (0,
|
|
766
|
+
const mutateAsync = (0, import_react10.useCallback)(
|
|
505
767
|
async (op) => {
|
|
506
768
|
setPending(true);
|
|
507
769
|
setError(null);
|
|
@@ -517,14 +779,14 @@ function useMutation(options) {
|
|
|
517
779
|
},
|
|
518
780
|
[applyLocal, drain]
|
|
519
781
|
);
|
|
520
|
-
const mutate = (0,
|
|
782
|
+
const mutate = (0, import_react10.useCallback)(
|
|
521
783
|
(op) => {
|
|
522
784
|
void mutateAsync(op).catch(() => {
|
|
523
785
|
});
|
|
524
786
|
},
|
|
525
787
|
[mutateAsync]
|
|
526
788
|
);
|
|
527
|
-
(0,
|
|
789
|
+
(0, import_react10.useEffect)(() => {
|
|
528
790
|
if (!drainOnMount || !configRef.current) return;
|
|
529
791
|
void drain().catch(() => {
|
|
530
792
|
});
|
|
@@ -540,9 +802,13 @@ function useMutation(options) {
|
|
|
540
802
|
0 && (module.exports = {
|
|
541
803
|
ReplicationProvider,
|
|
542
804
|
TalaDBProvider,
|
|
805
|
+
useAggregate,
|
|
543
806
|
useCollection,
|
|
807
|
+
useCollectionOptions,
|
|
808
|
+
useCoverage,
|
|
544
809
|
useFind,
|
|
545
810
|
useFindOne,
|
|
811
|
+
useHydrationProgress,
|
|
546
812
|
useMutation,
|
|
547
813
|
useQueries,
|
|
548
814
|
useQuery,
|