@taladb/react 0.9.3 → 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 +175 -41
- package/dist/index.d.ts +175 -41
- package/dist/index.js +383 -146
- package/dist/index.mjs +393 -149
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -33,10 +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,
|
|
37
38
|
useCollectionOptions: () => useCollectionOptions,
|
|
39
|
+
useCoverage: () => useCoverage,
|
|
38
40
|
useFind: () => useFind,
|
|
39
41
|
useFindOne: () => useFindOne,
|
|
42
|
+
useHydrationProgress: () => useHydrationProgress,
|
|
40
43
|
useMutation: () => useMutation,
|
|
41
44
|
useQueries: () => useQueries,
|
|
42
45
|
useQuery: () => useQuery,
|
|
@@ -177,8 +180,35 @@ function useFindOne(collection, filter) {
|
|
|
177
180
|
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
// src/
|
|
183
|
+
// src/useAggregate.ts
|
|
181
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");
|
|
182
212
|
|
|
183
213
|
// src/replication/engine.ts
|
|
184
214
|
var import_taladb = require("taladb");
|
|
@@ -231,20 +261,140 @@ async function replicateWithRetry(db, config, collection, direction) {
|
|
|
231
261
|
throw lastError;
|
|
232
262
|
}
|
|
233
263
|
|
|
234
|
-
// src/replication/
|
|
264
|
+
// src/replication/provider.tsx
|
|
265
|
+
var import_react6 = require("react");
|
|
266
|
+
var import_taladb2 = require("taladb");
|
|
235
267
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
236
|
-
var ReplicationContext = (0,
|
|
237
|
-
function
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
(
|
|
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,
|
|
241
390
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
242
391
|
[key]
|
|
243
392
|
);
|
|
244
|
-
|
|
245
|
-
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,
|
|
246
395
|
children
|
|
247
396
|
] });
|
|
397
|
+
return replicate2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReplicationScopes, { replicate: replicate2, children: inner }) : inner;
|
|
248
398
|
}
|
|
249
399
|
function resolveReplicationConfig(base, overrides) {
|
|
250
400
|
const endpoint = overrides?.endpoint ?? base?.endpoint;
|
|
@@ -261,10 +411,10 @@ function resolveReplicationConfig(base, overrides) {
|
|
|
261
411
|
};
|
|
262
412
|
}
|
|
263
413
|
function useReplicationBase() {
|
|
264
|
-
return (0,
|
|
414
|
+
return (0, import_react7.useContext)(ReplicationContext2);
|
|
265
415
|
}
|
|
266
416
|
function useReplicationConfig(overrides) {
|
|
267
|
-
return resolveReplicationConfig((0,
|
|
417
|
+
return resolveReplicationConfig((0, import_react7.useContext)(ReplicationContext2), overrides);
|
|
268
418
|
}
|
|
269
419
|
var CURSOR_COLLECTION = "__taladb_sync";
|
|
270
420
|
function normalizePrefetch(entries) {
|
|
@@ -294,10 +444,10 @@ function PrefetchRunner() {
|
|
|
294
444
|
const slices = normalizePrefetch(base?.prefetch);
|
|
295
445
|
const mode = base?.prefetchMode ?? "once";
|
|
296
446
|
const concurrency = Math.max(1, base?.prefetchConcurrency ?? 2);
|
|
297
|
-
const baseRef = (0,
|
|
447
|
+
const baseRef = (0, import_react7.useRef)(base);
|
|
298
448
|
baseRef.current = base;
|
|
299
449
|
const sig = JSON.stringify({ slices, mode, concurrency, endpoint: base?.endpoint ?? null });
|
|
300
|
-
(0,
|
|
450
|
+
(0, import_react7.useEffect)(() => {
|
|
301
451
|
if (slices.length === 0) return void 0;
|
|
302
452
|
let cancelled = false;
|
|
303
453
|
const cancelSchedule = schedule(() => {
|
|
@@ -332,167 +482,251 @@ function PrefetchRunner() {
|
|
|
332
482
|
return null;
|
|
333
483
|
}
|
|
334
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
|
+
|
|
335
501
|
// src/useQuery.ts
|
|
336
|
-
var
|
|
502
|
+
var import_react8 = require("react");
|
|
337
503
|
function useQuery(options) {
|
|
338
|
-
const { collection, filter,
|
|
339
|
-
const networked = source !== "local-only";
|
|
340
|
-
const db = useTalaDB();
|
|
504
|
+
const { collection, filter, sort, page, limit, skip, enabled = true } = options;
|
|
341
505
|
const col = useCollection(collection);
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
const [
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const refetch = (0, import_react6.useCallback)(async () => {
|
|
357
|
-
const cfg = configRef.current;
|
|
358
|
-
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;
|
|
359
520
|
setSyncing(true);
|
|
360
521
|
setSyncError(null);
|
|
361
522
|
try {
|
|
362
523
|
await replicate(db, cfg, collection, "pull");
|
|
363
|
-
} catch (
|
|
364
|
-
setSyncError(
|
|
524
|
+
} catch (error) {
|
|
525
|
+
setSyncError(error);
|
|
365
526
|
} finally {
|
|
366
527
|
setSyncing(false);
|
|
367
528
|
setFirstSyncDone(true);
|
|
368
529
|
}
|
|
369
|
-
}, [db, collection,
|
|
370
|
-
(0,
|
|
371
|
-
if (!
|
|
372
|
-
void
|
|
530
|
+
}, [db, collection, legacyNetworked, legacyConfig?.endpoint]);
|
|
531
|
+
(0, import_react8.useEffect)(() => {
|
|
532
|
+
if (!enabled || !legacyNetworked || !legacyConfig) return;
|
|
533
|
+
void legacyRefetch();
|
|
373
534
|
if (pollMs > 0) {
|
|
374
|
-
const
|
|
375
|
-
return () => clearInterval(
|
|
535
|
+
const timer = setInterval(() => void legacyRefetch(), pollMs);
|
|
536
|
+
return () => clearInterval(timer);
|
|
376
537
|
}
|
|
377
538
|
return void 0;
|
|
378
|
-
}, [
|
|
379
|
-
|
|
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) {
|
|
380
589
|
throw new Error(
|
|
381
|
-
`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.`
|
|
382
591
|
);
|
|
383
592
|
}
|
|
384
|
-
|
|
385
|
-
|
|
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
|
+
};
|
|
386
605
|
}
|
|
387
606
|
|
|
388
607
|
// src/useQueries.ts
|
|
389
|
-
var
|
|
390
|
-
var NOOP_REFETCH = async () => {
|
|
391
|
-
};
|
|
392
|
-
function emptyResult() {
|
|
393
|
-
return { data: [], loading: true, error: null, syncing: false, syncError: null, refetch: NOOP_REFETCH };
|
|
394
|
-
}
|
|
608
|
+
var import_react9 = require("react");
|
|
395
609
|
function useQueries(queries) {
|
|
396
610
|
const db = useTalaDB();
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
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(
|
|
407
617
|
queries.map((q) => ({
|
|
408
618
|
collection: q.collection,
|
|
409
619
|
filter: q.filter ?? null,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
413
625
|
}))
|
|
414
626
|
);
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
+
}))
|
|
421
635
|
);
|
|
422
|
-
(0,
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
setResults((prev) => {
|
|
428
|
-
if (i >= prev.length) return prev;
|
|
429
|
-
const copy = prev.slice();
|
|
430
|
-
copy[i] = fn(copy[i]);
|
|
431
|
-
return copy;
|
|
432
|
-
});
|
|
433
|
-
};
|
|
434
|
-
const resolved = qs.map((q, i) => {
|
|
435
|
-
const { config } = resolveReplicationConfig(b, {
|
|
436
|
-
endpoint: q.endpoint,
|
|
437
|
-
getAuth: q.getAuth,
|
|
438
|
-
fetch: q.fetch,
|
|
439
|
-
paths: q.paths,
|
|
440
|
-
pollMs: q.pollMs
|
|
441
|
-
});
|
|
442
|
-
const networked = (q.source ?? "local-first") !== "local-only";
|
|
443
|
-
const pollMs = q.pollMs ?? b?.pollMs ?? 0;
|
|
444
|
-
const refetch = async () => {
|
|
445
|
-
if (!networked || !config) return;
|
|
446
|
-
setAt(i, (r) => ({ ...r, syncing: true, syncError: null }));
|
|
447
|
-
try {
|
|
448
|
-
await replicate(db, config, q.collection, "pull");
|
|
449
|
-
} catch (e) {
|
|
450
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, syncError: e }));
|
|
451
|
-
} finally {
|
|
452
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, syncing: false }));
|
|
453
|
-
}
|
|
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 () => {
|
|
454
641
|
};
|
|
455
|
-
|
|
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
|
+
);
|
|
456
670
|
});
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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,
|
|
462
716
|
syncing: false,
|
|
463
717
|
syncError: null,
|
|
464
|
-
refetch:
|
|
465
|
-
|
|
466
|
-
);
|
|
467
|
-
const unsubs = qs.map((q, i) => {
|
|
468
|
-
const col = db.collection(q.collection);
|
|
469
|
-
return col.subscribe(
|
|
470
|
-
q.filter ?? {},
|
|
471
|
-
(docs) => {
|
|
472
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, data: docs, loading: false, error: null }));
|
|
473
|
-
},
|
|
474
|
-
(error) => {
|
|
475
|
-
if (!cancelled) setAt(i, (r) => ({ ...r, loading: false, error }));
|
|
718
|
+
refetch: async () => {
|
|
719
|
+
await replication?.coordinators.get(q.collection)?.refresh();
|
|
476
720
|
}
|
|
477
|
-
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
void res.refetch();
|
|
483
|
-
if (res.pollMs > 0) intervals.push(setInterval(() => void res.refetch(), res.pollMs));
|
|
484
|
-
});
|
|
485
|
-
return () => {
|
|
486
|
-
cancelled = true;
|
|
487
|
-
unsubs.forEach((u) => u());
|
|
488
|
-
intervals.forEach((id) => clearInterval(id));
|
|
489
|
-
};
|
|
490
|
-
}, [db, sig]);
|
|
491
|
-
return queries.map((_q, i) => results[i] ?? emptyResult());
|
|
721
|
+
};
|
|
722
|
+
}),
|
|
723
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
724
|
+
[results, signature, replication]
|
|
725
|
+
);
|
|
492
726
|
}
|
|
493
727
|
|
|
494
728
|
// src/useMutation.ts
|
|
495
|
-
var
|
|
729
|
+
var import_react10 = require("react");
|
|
496
730
|
function useMutation(options) {
|
|
497
731
|
const { collection, direction = "push", drainOnMount = true } = options;
|
|
498
732
|
const db = useTalaDB();
|
|
@@ -503,12 +737,12 @@ function useMutation(options) {
|
|
|
503
737
|
fetch: options.fetch,
|
|
504
738
|
paths: options.paths
|
|
505
739
|
});
|
|
506
|
-
const configRef = (0,
|
|
740
|
+
const configRef = (0, import_react10.useRef)(config);
|
|
507
741
|
configRef.current = config;
|
|
508
|
-
const [pending, setPending] = (0,
|
|
509
|
-
const [error, setError] = (0,
|
|
742
|
+
const [pending, setPending] = (0, import_react10.useState)(false);
|
|
743
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
510
744
|
const endpoint = config?.endpoint;
|
|
511
|
-
const applyLocal = (0,
|
|
745
|
+
const applyLocal = (0, import_react10.useCallback)(
|
|
512
746
|
async (op) => {
|
|
513
747
|
switch (op.type) {
|
|
514
748
|
case "insert":
|
|
@@ -524,12 +758,12 @@ function useMutation(options) {
|
|
|
524
758
|
},
|
|
525
759
|
[col]
|
|
526
760
|
);
|
|
527
|
-
const drain = (0,
|
|
761
|
+
const drain = (0, import_react10.useCallback)(async () => {
|
|
528
762
|
const cfg = configRef.current;
|
|
529
763
|
if (!cfg) return;
|
|
530
764
|
await replicateWithRetry(db, cfg, collection, direction);
|
|
531
765
|
}, [db, collection, direction, endpoint]);
|
|
532
|
-
const mutateAsync = (0,
|
|
766
|
+
const mutateAsync = (0, import_react10.useCallback)(
|
|
533
767
|
async (op) => {
|
|
534
768
|
setPending(true);
|
|
535
769
|
setError(null);
|
|
@@ -545,14 +779,14 @@ function useMutation(options) {
|
|
|
545
779
|
},
|
|
546
780
|
[applyLocal, drain]
|
|
547
781
|
);
|
|
548
|
-
const mutate = (0,
|
|
782
|
+
const mutate = (0, import_react10.useCallback)(
|
|
549
783
|
(op) => {
|
|
550
784
|
void mutateAsync(op).catch(() => {
|
|
551
785
|
});
|
|
552
786
|
},
|
|
553
787
|
[mutateAsync]
|
|
554
788
|
);
|
|
555
|
-
(0,
|
|
789
|
+
(0, import_react10.useEffect)(() => {
|
|
556
790
|
if (!drainOnMount || !configRef.current) return;
|
|
557
791
|
void drain().catch(() => {
|
|
558
792
|
});
|
|
@@ -568,10 +802,13 @@ function useMutation(options) {
|
|
|
568
802
|
0 && (module.exports = {
|
|
569
803
|
ReplicationProvider,
|
|
570
804
|
TalaDBProvider,
|
|
805
|
+
useAggregate,
|
|
571
806
|
useCollection,
|
|
572
807
|
useCollectionOptions,
|
|
808
|
+
useCoverage,
|
|
573
809
|
useFind,
|
|
574
810
|
useFindOne,
|
|
811
|
+
useHydrationProgress,
|
|
575
812
|
useMutation,
|
|
576
813
|
useQueries,
|
|
577
814
|
useQuery,
|