@taladb/react 0.10.2 → 0.11.1
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/LICENSE-MIT +21 -0
- package/README.md +187 -0
- package/dist/chunk-RAGFVMZN.mjs +164 -0
- package/dist/index.d.mts +40 -309
- package/dist/index.d.ts +40 -309
- package/dist/index.js +67 -584
- package/dist/index.mjs +32 -707
- package/dist/query/index.d.mts +1649 -0
- package/dist/query/index.d.ts +1649 -0
- package/dist/query/index.js +2252 -0
- package/dist/query/index.mjs +2117 -0
- package/package.json +14 -5
- /package/{LICENSE → LICENSE-APACHE} +0 -0
package/dist/index.js
CHANGED
|
@@ -31,20 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
|
-
ReplicationProvider: () => ReplicationProvider,
|
|
35
34
|
TalaDBProvider: () => TalaDBProvider,
|
|
36
35
|
useAggregate: () => useAggregate,
|
|
37
36
|
useCollection: () => useCollection,
|
|
38
37
|
useCollectionOptions: () => useCollectionOptions,
|
|
39
|
-
useCoverage: () => useCoverage,
|
|
40
38
|
useFind: () => useFind,
|
|
41
39
|
useFindOne: () => useFindOne,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
useQueries: () => useQueries,
|
|
45
|
-
useQuery: () => useQuery,
|
|
46
|
-
useReplicationConfig: () => useReplicationConfig,
|
|
47
|
-
useTalaDB: () => useTalaDB
|
|
40
|
+
useTalaDB: () => useTalaDB,
|
|
41
|
+
useWrite: () => useWrite
|
|
48
42
|
});
|
|
49
43
|
module.exports = __toCommonJS(index_exports);
|
|
50
44
|
|
|
@@ -53,7 +47,8 @@ var import_react = require("react");
|
|
|
53
47
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
54
48
|
var TalaDBContext = (0, import_react.createContext)(null);
|
|
55
49
|
var CollectionOptionsContext = (0, import_react.createContext)({
|
|
56
|
-
get: () => void 0
|
|
50
|
+
get: () => void 0,
|
|
51
|
+
names: () => []
|
|
57
52
|
});
|
|
58
53
|
function useCollectionOptions() {
|
|
59
54
|
return (0, import_react.useContext)(CollectionOptionsContext);
|
|
@@ -66,7 +61,10 @@ function CollectionOptionsProvider({
|
|
|
66
61
|
latest.current = collections;
|
|
67
62
|
const resolver = (0, import_react.useMemo)(
|
|
68
63
|
() => ({
|
|
69
|
-
get: (name) => latest.current?.[name]
|
|
64
|
+
get: (name) => latest.current?.[name],
|
|
65
|
+
// Read through the same ref as `get`, so a registry passed as an inline
|
|
66
|
+
// object stays current without giving the resolver a new identity.
|
|
67
|
+
names: () => Object.keys(latest.current ?? {})
|
|
70
68
|
}),
|
|
71
69
|
[]
|
|
72
70
|
);
|
|
@@ -78,6 +76,47 @@ function TalaDBProvider(props) {
|
|
|
78
76
|
}
|
|
79
77
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NamedProvider, { ...props });
|
|
80
78
|
}
|
|
79
|
+
var openDatabases = /* @__PURE__ */ new Map();
|
|
80
|
+
function databaseKey(name, optionsKey) {
|
|
81
|
+
return `${name}\0${optionsKey}`;
|
|
82
|
+
}
|
|
83
|
+
function acquireDatabase(name, optionsKey, options) {
|
|
84
|
+
const key = databaseKey(name, optionsKey);
|
|
85
|
+
let entry = openDatabases.get(key);
|
|
86
|
+
if (entry) {
|
|
87
|
+
if (entry.closing !== void 0) {
|
|
88
|
+
clearTimeout(entry.closing);
|
|
89
|
+
entry.closing = void 0;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
entry = {
|
|
93
|
+
// Dynamic import so `taladb`'s runtime never loads during SSR module
|
|
94
|
+
// evaluation (its Node entry pulls in the native binding, which a web
|
|
95
|
+
// app's server bundle does not ship).
|
|
96
|
+
promise: import("taladb").then(({ openDB }) => openDB(name, options)),
|
|
97
|
+
refs: 0
|
|
98
|
+
};
|
|
99
|
+
openDatabases.set(key, entry);
|
|
100
|
+
}
|
|
101
|
+
entry.refs++;
|
|
102
|
+
return entry.promise;
|
|
103
|
+
}
|
|
104
|
+
function releaseDatabase(name, optionsKey) {
|
|
105
|
+
const key = databaseKey(name, optionsKey);
|
|
106
|
+
const entry = openDatabases.get(key);
|
|
107
|
+
if (!entry) return;
|
|
108
|
+
entry.refs--;
|
|
109
|
+
if (entry.refs > 0 || entry.closing !== void 0) return;
|
|
110
|
+
entry.closing = setTimeout(() => {
|
|
111
|
+
if (entry.refs > 0) {
|
|
112
|
+
entry.closing = void 0;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
openDatabases.delete(key);
|
|
116
|
+
void entry.promise.then((db) => db.close()).catch(() => {
|
|
117
|
+
});
|
|
118
|
+
}, 0);
|
|
119
|
+
}
|
|
81
120
|
function NamedProvider({
|
|
82
121
|
name,
|
|
83
122
|
options,
|
|
@@ -91,20 +130,14 @@ function NamedProvider({
|
|
|
91
130
|
(0, import_react.useEffect)(() => {
|
|
92
131
|
setError(null);
|
|
93
132
|
let cancelled = false;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (cancelled) {
|
|
97
|
-
void instance.close();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
opened = instance;
|
|
101
|
-
setDb(instance);
|
|
133
|
+
acquireDatabase(name, optionsKey, options).then((instance) => {
|
|
134
|
+
if (!cancelled) setDb(instance);
|
|
102
135
|
}).catch((e) => {
|
|
103
136
|
if (!cancelled) setError(e);
|
|
104
137
|
});
|
|
105
138
|
return () => {
|
|
106
139
|
cancelled = true;
|
|
107
|
-
|
|
140
|
+
releaseDatabase(name, optionsKey);
|
|
108
141
|
setDb(null);
|
|
109
142
|
};
|
|
110
143
|
}, [name, optionsKey]);
|
|
@@ -207,542 +240,14 @@ function useAggregate(collection, pipeline) {
|
|
|
207
240
|
return (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
208
241
|
}
|
|
209
242
|
|
|
210
|
-
// src/
|
|
211
|
-
var import_react7 = require("react");
|
|
212
|
-
|
|
213
|
-
// src/replication/engine.ts
|
|
214
|
-
var import_taladb = require("taladb");
|
|
215
|
-
function replicationTarget(endpoint, collection) {
|
|
216
|
-
return `${endpoint}::${collection}`;
|
|
217
|
-
}
|
|
218
|
-
async function buildAdapter(config) {
|
|
219
|
-
const headers = config.getAuth ? await config.getAuth() : void 0;
|
|
220
|
-
return new import_taladb.HttpSyncAdapter({
|
|
221
|
-
endpoint: config.endpoint,
|
|
222
|
-
headers,
|
|
223
|
-
fetch: config.fetch,
|
|
224
|
-
paths: config.paths
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
var inflight = /* @__PURE__ */ new Map();
|
|
228
|
-
function inflightKey(endpoint, collection, direction) {
|
|
229
|
-
return `${endpoint}::${collection}::${direction}`;
|
|
230
|
-
}
|
|
231
|
-
function replicate(db, config, collection, direction) {
|
|
232
|
-
const key = inflightKey(config.endpoint, collection, direction);
|
|
233
|
-
const existing = inflight.get(key);
|
|
234
|
-
if (existing) return existing;
|
|
235
|
-
const pass = (async () => {
|
|
236
|
-
const adapter = await buildAdapter(config);
|
|
237
|
-
await db.sync(adapter, {
|
|
238
|
-
collections: [collection],
|
|
239
|
-
direction,
|
|
240
|
-
target: replicationTarget(config.endpoint, collection)
|
|
241
|
-
});
|
|
242
|
-
})().finally(() => {
|
|
243
|
-
inflight.delete(key);
|
|
244
|
-
});
|
|
245
|
-
inflight.set(key, pass);
|
|
246
|
-
return pass;
|
|
247
|
-
}
|
|
248
|
-
var BACKOFFS_MS = [200, 400, 800];
|
|
249
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
250
|
-
async function replicateWithRetry(db, config, collection, direction) {
|
|
251
|
-
let lastError;
|
|
252
|
-
for (let attempt = 0; attempt <= BACKOFFS_MS.length; attempt++) {
|
|
253
|
-
try {
|
|
254
|
-
await replicate(db, config, collection, direction);
|
|
255
|
-
return;
|
|
256
|
-
} catch (error) {
|
|
257
|
-
lastError = error;
|
|
258
|
-
if (attempt < BACKOFFS_MS.length) await sleep(BACKOFFS_MS[attempt]);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
throw lastError;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// src/replication/provider.tsx
|
|
243
|
+
// src/useWrite.ts
|
|
265
244
|
var import_react6 = require("react");
|
|
266
|
-
|
|
267
|
-
|
|
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,
|
|
390
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
391
|
-
[key]
|
|
392
|
-
);
|
|
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,
|
|
395
|
-
children
|
|
396
|
-
] });
|
|
397
|
-
return replicate2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReplicationScopes, { replicate: replicate2, children: inner }) : inner;
|
|
398
|
-
}
|
|
399
|
-
function resolveReplicationConfig(base, overrides) {
|
|
400
|
-
const endpoint = overrides?.endpoint ?? base?.endpoint;
|
|
401
|
-
const pollMs = overrides?.pollMs ?? base?.pollMs ?? 0;
|
|
402
|
-
if (!endpoint) return { config: null, pollMs };
|
|
403
|
-
return {
|
|
404
|
-
config: {
|
|
405
|
-
endpoint,
|
|
406
|
-
getAuth: overrides?.getAuth ?? base?.getAuth,
|
|
407
|
-
fetch: overrides?.fetch ?? base?.fetch,
|
|
408
|
-
paths: overrides?.paths ?? base?.paths
|
|
409
|
-
},
|
|
410
|
-
pollMs
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
function useReplicationBase() {
|
|
414
|
-
return (0, import_react7.useContext)(ReplicationContext2);
|
|
415
|
-
}
|
|
416
|
-
function useReplicationConfig(overrides) {
|
|
417
|
-
return resolveReplicationConfig((0, import_react7.useContext)(ReplicationContext2), overrides);
|
|
418
|
-
}
|
|
419
|
-
var CURSOR_COLLECTION = "__taladb_sync";
|
|
420
|
-
function normalizePrefetch(entries) {
|
|
421
|
-
return (entries ?? []).map((e) => typeof e === "string" ? { collection: e } : e);
|
|
422
|
-
}
|
|
423
|
-
var idleScheduler = (fn) => {
|
|
424
|
-
const g = globalThis;
|
|
425
|
-
if (typeof g.requestIdleCallback === "function") {
|
|
426
|
-
const id2 = g.requestIdleCallback(fn, { timeout: 2e3 });
|
|
427
|
-
return () => g.cancelIdleCallback?.(id2);
|
|
428
|
-
}
|
|
429
|
-
const id = setTimeout(fn, 0);
|
|
430
|
-
return () => clearTimeout(id);
|
|
431
|
-
};
|
|
432
|
-
var schedule = idleScheduler;
|
|
433
|
-
async function hasSynced(db, target) {
|
|
434
|
-
try {
|
|
435
|
-
const doc = await db.collection(CURSOR_COLLECTION).findOne({ target });
|
|
436
|
-
return doc != null;
|
|
437
|
-
} catch {
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function PrefetchRunner() {
|
|
442
|
-
const db = useTalaDB();
|
|
443
|
-
const base = useReplicationBase();
|
|
444
|
-
const slices = normalizePrefetch(base?.prefetch);
|
|
445
|
-
const mode = base?.prefetchMode ?? "once";
|
|
446
|
-
const concurrency = Math.max(1, base?.prefetchConcurrency ?? 2);
|
|
447
|
-
const baseRef = (0, import_react7.useRef)(base);
|
|
448
|
-
baseRef.current = base;
|
|
449
|
-
const sig = JSON.stringify({ slices, mode, concurrency, endpoint: base?.endpoint ?? null });
|
|
450
|
-
(0, import_react7.useEffect)(() => {
|
|
451
|
-
if (slices.length === 0) return void 0;
|
|
452
|
-
let cancelled = false;
|
|
453
|
-
const cancelSchedule = schedule(() => {
|
|
454
|
-
void run();
|
|
455
|
-
});
|
|
456
|
-
async function run() {
|
|
457
|
-
const b = baseRef.current;
|
|
458
|
-
const queue = normalizePrefetch(b?.prefetch);
|
|
459
|
-
const worker = async () => {
|
|
460
|
-
while (!cancelled) {
|
|
461
|
-
const slice = queue.shift();
|
|
462
|
-
if (!slice) return;
|
|
463
|
-
const { config } = resolveReplicationConfig(b, { endpoint: slice.endpoint });
|
|
464
|
-
if (!config) continue;
|
|
465
|
-
const target = replicationTarget(config.endpoint, slice.collection);
|
|
466
|
-
if (mode === "once" && await hasSynced(db, target)) continue;
|
|
467
|
-
if (cancelled) return;
|
|
468
|
-
try {
|
|
469
|
-
await replicate(db, config, slice.collection, "pull");
|
|
470
|
-
} catch {
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
const lanes = Math.min(concurrency, queue.length);
|
|
475
|
-
await Promise.all(Array.from({ length: lanes }, () => worker()));
|
|
476
|
-
}
|
|
477
|
-
return () => {
|
|
478
|
-
cancelled = true;
|
|
479
|
-
cancelSchedule();
|
|
480
|
-
};
|
|
481
|
-
}, [db, sig]);
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
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
|
-
|
|
501
|
-
// src/useQuery.ts
|
|
502
|
-
var import_react8 = require("react");
|
|
503
|
-
function useQuery(options) {
|
|
504
|
-
const { collection, filter, sort, page, limit, skip, enabled = true } = options;
|
|
245
|
+
function useWrite(options) {
|
|
246
|
+
const { collection } = options;
|
|
505
247
|
const col = useCollection(collection);
|
|
506
|
-
const
|
|
507
|
-
const
|
|
508
|
-
const
|
|
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;
|
|
520
|
-
setSyncing(true);
|
|
521
|
-
setSyncError(null);
|
|
522
|
-
try {
|
|
523
|
-
await replicate(db, cfg, collection, "pull");
|
|
524
|
-
} catch (error) {
|
|
525
|
-
setSyncError(error);
|
|
526
|
-
} finally {
|
|
527
|
-
setSyncing(false);
|
|
528
|
-
setFirstSyncDone(true);
|
|
529
|
-
}
|
|
530
|
-
}, [db, collection, legacyNetworked, legacyConfig?.endpoint]);
|
|
531
|
-
(0, import_react8.useEffect)(() => {
|
|
532
|
-
if (!enabled || !legacyNetworked || !legacyConfig) return;
|
|
533
|
-
void legacyRefetch();
|
|
534
|
-
if (pollMs > 0) {
|
|
535
|
-
const timer = setInterval(() => void legacyRefetch(), pollMs);
|
|
536
|
-
return () => clearInterval(timer);
|
|
537
|
-
}
|
|
538
|
-
return void 0;
|
|
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) {
|
|
589
|
-
throw new Error(
|
|
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.`
|
|
591
|
-
);
|
|
592
|
-
}
|
|
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
|
-
};
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// src/useQueries.ts
|
|
608
|
-
var import_react9 = require("react");
|
|
609
|
-
function useQueries(queries) {
|
|
610
|
-
const db = useTalaDB();
|
|
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(
|
|
617
|
-
queries.map((q) => ({
|
|
618
|
-
collection: q.collection,
|
|
619
|
-
filter: q.filter ?? null,
|
|
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
|
|
625
|
-
}))
|
|
626
|
-
);
|
|
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
|
-
}))
|
|
635
|
-
);
|
|
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 () => {
|
|
641
|
-
};
|
|
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
|
-
);
|
|
670
|
-
});
|
|
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,
|
|
716
|
-
syncing: false,
|
|
717
|
-
syncError: null,
|
|
718
|
-
refetch: async () => {
|
|
719
|
-
await replication?.coordinators.get(q.collection)?.refresh();
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
}),
|
|
723
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
724
|
-
[results, signature, replication]
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// src/useMutation.ts
|
|
729
|
-
var import_react10 = require("react");
|
|
730
|
-
function useMutation(options) {
|
|
731
|
-
const { collection, direction = "push", drainOnMount = true } = options;
|
|
732
|
-
const db = useTalaDB();
|
|
733
|
-
const col = useCollection(collection);
|
|
734
|
-
const { config } = useReplicationConfig({
|
|
735
|
-
endpoint: options.endpoint,
|
|
736
|
-
getAuth: options.getAuth,
|
|
737
|
-
fetch: options.fetch,
|
|
738
|
-
paths: options.paths
|
|
739
|
-
});
|
|
740
|
-
const configRef = (0, import_react10.useRef)(config);
|
|
741
|
-
configRef.current = config;
|
|
742
|
-
const [pending, setPending] = (0, import_react10.useState)(false);
|
|
743
|
-
const [error, setError] = (0, import_react10.useState)(null);
|
|
744
|
-
const endpoint = config?.endpoint;
|
|
745
|
-
const applyLocal = (0, import_react10.useCallback)(
|
|
248
|
+
const [pending, setPending] = (0, import_react6.useState)(false);
|
|
249
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
250
|
+
const apply = (0, import_react6.useCallback)(
|
|
746
251
|
async (op) => {
|
|
747
252
|
switch (op.type) {
|
|
748
253
|
case "insert":
|
|
@@ -758,18 +263,12 @@ function useMutation(options) {
|
|
|
758
263
|
},
|
|
759
264
|
[col]
|
|
760
265
|
);
|
|
761
|
-
const
|
|
762
|
-
const cfg = configRef.current;
|
|
763
|
-
if (!cfg) return;
|
|
764
|
-
await replicateWithRetry(db, cfg, collection, direction);
|
|
765
|
-
}, [db, collection, direction, endpoint]);
|
|
766
|
-
const mutateAsync = (0, import_react10.useCallback)(
|
|
266
|
+
const writeAsync = (0, import_react6.useCallback)(
|
|
767
267
|
async (op) => {
|
|
768
268
|
setPending(true);
|
|
769
269
|
setError(null);
|
|
770
270
|
try {
|
|
771
|
-
await
|
|
772
|
-
await drain();
|
|
271
|
+
await apply(op);
|
|
773
272
|
} catch (e) {
|
|
774
273
|
setError(e);
|
|
775
274
|
throw e;
|
|
@@ -777,41 +276,25 @@ function useMutation(options) {
|
|
|
777
276
|
setPending(false);
|
|
778
277
|
}
|
|
779
278
|
},
|
|
780
|
-
[
|
|
279
|
+
[apply]
|
|
781
280
|
);
|
|
782
|
-
const
|
|
281
|
+
const write = (0, import_react6.useCallback)(
|
|
783
282
|
(op) => {
|
|
784
|
-
void
|
|
283
|
+
void writeAsync(op).catch(() => {
|
|
785
284
|
});
|
|
786
285
|
},
|
|
787
|
-
[
|
|
286
|
+
[writeAsync]
|
|
788
287
|
);
|
|
789
|
-
|
|
790
|
-
if (!drainOnMount || !configRef.current) return;
|
|
791
|
-
void drain().catch(() => {
|
|
792
|
-
});
|
|
793
|
-
}, [drain, drainOnMount]);
|
|
794
|
-
if (!config) {
|
|
795
|
-
throw new Error(
|
|
796
|
-
`useMutation({ collection: '${collection}' }) needs an endpoint. Wrap the tree in <ReplicationProvider endpoint="\u2026"> or pass { endpoint }.`
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
return { mutate, mutateAsync, pending, error };
|
|
288
|
+
return { write, writeAsync, pending, error };
|
|
800
289
|
}
|
|
801
290
|
// Annotate the CommonJS export names for ESM import in node:
|
|
802
291
|
0 && (module.exports = {
|
|
803
|
-
ReplicationProvider,
|
|
804
292
|
TalaDBProvider,
|
|
805
293
|
useAggregate,
|
|
806
294
|
useCollection,
|
|
807
295
|
useCollectionOptions,
|
|
808
|
-
useCoverage,
|
|
809
296
|
useFind,
|
|
810
297
|
useFindOne,
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
useQueries,
|
|
814
|
-
useQuery,
|
|
815
|
-
useReplicationConfig,
|
|
816
|
-
useTalaDB
|
|
298
|
+
useTalaDB,
|
|
299
|
+
useWrite
|
|
817
300
|
});
|