@peerbit/react 0.0.33 → 0.0.35

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.
Files changed (71) hide show
  1. package/dist/src/index.d.ts +5 -0
  2. package/dist/src/index.d.ts.map +1 -0
  3. package/dist/src/index.js +5 -0
  4. package/dist/src/index.js.map +1 -0
  5. package/{lib/esm → dist/src}/lockstorage.d.ts +1 -0
  6. package/dist/src/lockstorage.d.ts.map +1 -0
  7. package/{lib/esm → dist/src}/lockstorage.js +2 -5
  8. package/dist/src/lockstorage.js.map +1 -0
  9. package/{lib/esm → dist/src}/useMount.d.ts +1 -0
  10. package/dist/src/useMount.d.ts.map +1 -0
  11. package/dist/src/useMount.js.map +1 -0
  12. package/{lib/esm → dist/src}/usePeer.d.ts +7 -3
  13. package/dist/src/usePeer.d.ts.map +1 -0
  14. package/{lib/esm → dist/src}/usePeer.js +25 -20
  15. package/dist/src/usePeer.js.map +1 -0
  16. package/{lib/esm → dist/src}/utils.d.ts +2 -4
  17. package/dist/src/utils.d.ts.map +1 -0
  18. package/{lib/esm → dist/src}/utils.js +17 -69
  19. package/dist/src/utils.js.map +1 -0
  20. package/package.json +59 -56
  21. package/src/index.ts +4 -14
  22. package/src/lockstorage.ts +224 -233
  23. package/src/useMount.ts +15 -0
  24. package/src/usePeer.tsx +406 -419
  25. package/src/utils.ts +99 -168
  26. package/README.md +0 -24
  27. package/lib/esm/__tests__/lockstorage.test.d.ts +0 -1
  28. package/lib/esm/__tests__/lockstorage.test.js +0 -237
  29. package/lib/esm/__tests__/lockstorage.test.js.map +0 -1
  30. package/lib/esm/__tests__/singletonLock.test.d.ts +0 -1
  31. package/lib/esm/__tests__/singletonLock.test.js +0 -71
  32. package/lib/esm/__tests__/singletonLock.test.js.map +0 -1
  33. package/lib/esm/__tests__/useQuery.dom.test.d.ts +0 -1
  34. package/lib/esm/__tests__/useQuery.dom.test.js +0 -433
  35. package/lib/esm/__tests__/useQuery.dom.test.js.map +0 -1
  36. package/lib/esm/__tests__/utils.test.d.ts +0 -1
  37. package/lib/esm/__tests__/utils.test.js +0 -66
  38. package/lib/esm/__tests__/utils.test.js.map +0 -1
  39. package/lib/esm/index.d.ts +0 -8
  40. package/lib/esm/index.js +0 -9
  41. package/lib/esm/index.js.map +0 -1
  42. package/lib/esm/lockstorage.js.map +0 -1
  43. package/lib/esm/useCount.d.ts +0 -11
  44. package/lib/esm/useCount.js +0 -43
  45. package/lib/esm/useCount.js.map +0 -1
  46. package/lib/esm/useLocal.d.ts +0 -20
  47. package/lib/esm/useLocal.js +0 -73
  48. package/lib/esm/useLocal.js.map +0 -1
  49. package/lib/esm/useMount.js.map +0 -1
  50. package/lib/esm/useOnline.d.ts +0 -11
  51. package/lib/esm/useOnline.js +0 -65
  52. package/lib/esm/useOnline.js.map +0 -1
  53. package/lib/esm/usePeer.js.map +0 -1
  54. package/lib/esm/useProgram.d.ts +0 -16
  55. package/lib/esm/useProgram.js +0 -114
  56. package/lib/esm/useProgram.js.map +0 -1
  57. package/lib/esm/useQuery.d.ts +0 -49
  58. package/lib/esm/useQuery.js +0 -418
  59. package/lib/esm/useQuery.js.map +0 -1
  60. package/lib/esm/utils.js.map +0 -1
  61. package/src/__tests__/lockstorage.test.ts +0 -285
  62. package/src/__tests__/singletonLock.test.ts +0 -85
  63. package/src/__tests__/useQuery.dom.test.ts +0 -518
  64. package/src/__tests__/utils.test.ts +0 -90
  65. package/src/useCount.tsx +0 -63
  66. package/src/useLocal.tsx +0 -125
  67. package/src/useMount.tsx +0 -15
  68. package/src/useOnline.tsx +0 -85
  69. package/src/useProgram.tsx +0 -148
  70. package/src/useQuery.tsx +0 -548
  71. /package/{lib/esm → dist/src}/useMount.js +0 -0
package/src/useQuery.tsx DELETED
@@ -1,548 +0,0 @@
1
- import { useState, useEffect, useRef, useMemo } from "react";
2
- import {
3
- AbstractSearchRequest,
4
- AbstractSearchResult,
5
- ClosedError,
6
- Context,
7
- Documents,
8
- RemoteQueryOptions,
9
- ResultsIterator,
10
- WithContext,
11
- } from "@peerbit/document";
12
- import * as indexerTypes from "@peerbit/indexer-interface";
13
- import { v4 as uuid } from "uuid";
14
- import { WithIndexedContext } from "@peerbit/document";
15
- import { UpdateOptions } from "@peerbit/document";
16
-
17
- type QueryOptions = { query: QueryLike; id?: string };
18
-
19
- /* ────────────── helper types ────────────── */
20
- export type QueryLike = {
21
- /** Mongo-style selector or array of selectors */
22
- query?: indexerTypes.QueryLike | indexerTypes.Query[];
23
- /** Sort definition compatible with `@peerbit/indexer-interface` */
24
- sort?: indexerTypes.SortLike | indexerTypes.Sort | indexerTypes.Sort[];
25
- };
26
-
27
- /**
28
- * All the non-DB-specific options supported by the original single-DB hook.
29
- * They stay fully backward-compatible.
30
- */
31
- export type UseQuerySharedOptions<
32
- T,
33
- I,
34
- R extends boolean | undefined,
35
- RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>,
36
- > = {
37
- /* original behavioural flags */
38
- resolve?: R;
39
- transform?: (r: RT) => Promise<RT>;
40
- debounce?: number;
41
- debug?: boolean | string;
42
- reverse?: boolean;
43
- batchSize?: number;
44
- prefetch?: boolean;
45
- /* onChange?: {
46
- merge?:
47
- | boolean
48
- | ((
49
- c: DocumentsChange<T, I>
50
- ) =>
51
- | DocumentsChange<T, I>
52
- | Promise<DocumentsChange<T, I>>
53
- | undefined);
54
- update?: (
55
- prev: RT[],
56
- change: DocumentsChange<T, I>
57
- ) => RT[] | Promise<RT[]>;
58
- }; */
59
- updates?: UpdateOptions<T, I, R>;
60
- local?: boolean;
61
- remote?:
62
- | boolean
63
- | RemoteQueryOptions<AbstractSearchRequest, AbstractSearchResult, any>;
64
- } & QueryOptions;
65
-
66
- /* ────────────────────────── Main Hook ────────────────────────── */
67
- /**
68
- * `useQuery` – unified hook that accepts **either**
69
- * 1. a single `Documents` instance
70
- * 2. an array of `Documents` instances
71
- * 3. *or* omits the first argument and provides `dbs` inside the `options` object.
72
- *
73
- * It supersedes the original single-DB version as well as the experimental
74
- * `useMultiQuery` so callers never have to choose between two APIs.
75
- */
76
- export const useQuery = <
77
- T extends Record<string, any>,
78
- I extends Record<string, any>,
79
- R extends boolean | undefined = true,
80
- RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>,
81
- >(
82
- /** Single DB or list of DBs. 100 % backward-compatible with the old single param. */
83
- dbOrDbs: Documents<T, I> | Documents<T, I>[] | undefined,
84
- options: UseQuerySharedOptions<T, I, R, RT>
85
- ) => {
86
- /* ─────── internal type alias for convenience ─────── */
87
- type Item = RT;
88
- type IteratorRef = {
89
- id: string;
90
- db: Documents<T, I>;
91
- iterator: ResultsIterator<Item>;
92
- itemsConsumed: number;
93
- };
94
-
95
- /* ────────────── normalise DBs input ────────────── */
96
- const dbs = useMemo<(Documents<T, I> | undefined)[]>(() => {
97
- if (Array.isArray(dbOrDbs)) return dbOrDbs;
98
- if (dbOrDbs) return [dbOrDbs];
99
- return [];
100
- }, [dbOrDbs]);
101
-
102
- /* ────────────── state & refs ────────────── */
103
- const [all, setAll] = useState<Item[]>([]);
104
- const allRef = useRef<Item[]>([]);
105
- const [isLoading, setIsLoading] = useState(false);
106
- const iteratorRefs = useRef<IteratorRef[]>([]);
107
- const itemIdRef = useRef(new WeakMap<object, string>());
108
- const emptyResultsRef = useRef(false);
109
- const closeControllerRef = useRef<AbortController | null>(null);
110
- const waitedOnceRef = useRef(false);
111
-
112
- /* keep an id mostly for debugging – mirrors original behaviour */
113
- const [id, setId] = useState<string | undefined>(options.id);
114
-
115
- const reverseRef = useRef(options.reverse);
116
- useEffect(() => {
117
- reverseRef.current = options.reverse;
118
- }, [options.reverse]);
119
-
120
- /* ────────────── utilities ────────────── */
121
- const log = (...a: any[]) => {
122
- if (!options.debug) return;
123
- if (typeof options.debug === "boolean") console.log(...a);
124
- else console.log(options.debug, ...a);
125
- };
126
-
127
- const updateAll = (combined: Item[]) => {
128
- allRef.current = combined;
129
- setAll(combined);
130
- };
131
-
132
- const reset = () => {
133
- iteratorRefs.current?.forEach(({ iterator }) => iterator.close());
134
- iteratorRefs.current = [];
135
-
136
- closeControllerRef.current?.abort(new Error("Reset"));
137
- closeControllerRef.current = new AbortController();
138
- emptyResultsRef.current = false;
139
- waitedOnceRef.current = false;
140
-
141
- allRef.current = [];
142
- itemIdRef.current = new WeakMap();
143
- setAll([]);
144
- setIsLoading(false);
145
- log("Iterators reset");
146
- };
147
-
148
- /* ────────── rebuild iterators when db list / query etc. change ────────── */
149
- useEffect(() => {
150
- /* derive canonical list of open DBs */
151
- const openDbs = dbs.filter((d): d is Documents<T, I> =>
152
- Boolean(d && !d.closed)
153
- );
154
- const { query, resolve } = options;
155
-
156
- if (!openDbs.length || query == null) {
157
- reset();
158
- return;
159
- }
160
-
161
- reset();
162
- const abortSignal = closeControllerRef.current?.signal;
163
- const onMissedResults = (evt: { amount: number }) => {
164
- console.error("Not effective yet: missed results", evt);
165
- /* if (allRef.current.length > 0 || typeof options.remote !== "object" || !options.updates) {
166
- return;
167
- }
168
- console.log("Missed results, loading more", evt.amount);
169
- loadMore(evt.amount); */
170
- };
171
- let draining = false;
172
- const scheduleDrain = (ref: ResultsIterator<RT>, amount: number) => {
173
- log("Schedule drain", draining, ref, amount);
174
- if (draining) return;
175
- draining = true;
176
- loadMore(amount)
177
- .catch((e) => {
178
- if (!(e instanceof ClosedError)) throw e;
179
- })
180
- .finally(() => {
181
- draining = false;
182
- });
183
- };
184
-
185
- iteratorRefs.current = openDbs.map((db) => {
186
- let currentRef: IteratorRef | undefined;
187
- const iterator = db.index.iterate(query ?? {}, {
188
- closePolicy: "manual",
189
- local: options.local ?? true,
190
- remote: options.remote
191
- ? {
192
- ...(typeof options?.remote === "object"
193
- ? {
194
- ...options.remote,
195
- onLateResults: onMissedResults,
196
- wait: {
197
- ...options?.remote?.wait,
198
- timeout:
199
- options?.remote?.wait?.timeout ??
200
- 5000,
201
- },
202
- }
203
- : options?.remote
204
- ? {
205
- onLateResults: onMissedResults,
206
- }
207
- : undefined),
208
- }
209
- : undefined,
210
- resolve,
211
- signal: abortSignal,
212
- updates: {
213
- push: true,
214
- merge:
215
- typeof options.updates === "boolean" && options.updates
216
- ? true
217
- : typeof options.updates === "object" &&
218
- options.updates.merge
219
- ? true
220
- : false,
221
- onChange: (evt) => {
222
- log("Live update", evt);
223
- if (evt.added.length > 0) {
224
- scheduleDrain(
225
- iterator as ResultsIterator<RT>,
226
- evt.added.length
227
- );
228
- }
229
- },
230
- onResults: (batch, props) => {
231
- log("onResults", { batch, props, currentRef: !!currentRef });
232
- if (
233
- props.reason === "join" ||
234
- props.reason === "change"
235
- ) {
236
- if (!currentRef) return;
237
- handleBatch(iteratorRefs.current, [
238
- { ref: currentRef, items: batch as Item[] },
239
- ]);
240
- }
241
- },
242
- },
243
- }) as ResultsIterator<Item>;
244
-
245
- const ref: IteratorRef = {
246
- id: uuid(),
247
- db,
248
- iterator,
249
- itemsConsumed: 0,
250
- };
251
- currentRef = ref;
252
- log("Iterator init", ref.id, "db", db.address);
253
- return ref;
254
- });
255
-
256
- /* store a deterministic id (useful for external keys) */
257
- setId(uuid());
258
-
259
- /* prefetch if requested */
260
- if (options.prefetch) void loadMore();
261
- // eslint-disable-next-line react-hooks/exhaustive-deps
262
- }, [
263
- dbs.map((d) => d?.address).join("|"),
264
- options.query,
265
- options.resolve,
266
- options.reverse,
267
- ]);
268
-
269
- /* ────────────── loadMore implementation ────────────── */
270
- const batchSize = options.batchSize ?? 10;
271
-
272
- const shouldWait = (): boolean => {
273
- if (waitedOnceRef.current) return false;
274
- if (options.remote === false) return false;
275
- return true; // mimic original behaviour – wait once if remote allowed
276
- };
277
-
278
- const markWaited = () => {
279
- waitedOnceRef.current = true;
280
- };
281
-
282
- /* helper to turn primitive ids into stable map keys */
283
- const idToKey = (value: indexerTypes.IdPrimitive): string => {
284
- switch (typeof value) {
285
- case "string":
286
- return `s:${value}`;
287
- case "number":
288
- return `n:${value}`;
289
- default:
290
- return `b:${value.toString()}`;
291
- }
292
- };
293
-
294
- const handleBatch = async (
295
- iterators: IteratorRef[],
296
- batches: { ref: IteratorRef; items: Item[] }[]
297
- ): Promise<boolean> => {
298
- if (!iterators.length) {
299
- log("No iterators in handleBatch");
300
- return false;
301
- }
302
-
303
- const totalFetched = batches.reduce(
304
- (sum, batch) => sum + batch.items.length,
305
- 0
306
- );
307
- if (totalFetched === 0) {
308
- log("No items fetched");
309
- emptyResultsRef.current = iterators.every((i) => i.iterator.done());
310
- return !emptyResultsRef.current;
311
- }
312
-
313
- let processed = batches;
314
- if (options.transform) {
315
- const transform = options.transform;
316
- processed = await Promise.all(
317
- batches.map(async ({ ref, items }) => ({
318
- ref,
319
- items: await Promise.all(items.map(transform)),
320
- }))
321
- );
322
- }
323
-
324
- const prev = allRef.current;
325
- const next = [...prev];
326
- const keyIndex = new Map<string, number>();
327
- prev.forEach((item, idx) => {
328
- const key = itemIdRef.current.get(item as object);
329
- if (key) keyIndex.set(key, idx);
330
- });
331
-
332
- const seenHeads = new Set(prev.map((x) => (x as any).__context?.head));
333
- const freshItems: Item[] = [];
334
- let hasMutations = false;
335
-
336
- log("Processing batches", { processed, keyIndex });
337
- for (const { ref, items } of processed) {
338
- const db = ref.db;
339
- for (const item of items) {
340
- const ctx = (item as WithContext<any>).__context;
341
- const head = ctx?.head;
342
-
343
- let key: string | null = null;
344
- try {
345
- key = idToKey(
346
- db.index.resolveId(
347
- item as WithContext<I> | WithIndexedContext<T, I>
348
- ).primitive
349
- );
350
- } catch (error) {
351
- log("useQuery: failed to resolve id", error);
352
- }
353
-
354
- if (key && keyIndex.has(key)) {
355
- const existingIndex = keyIndex.get(key)!;
356
- const current = next[existingIndex];
357
- const currentContext: Context | undefined = (
358
- current as WithContext<any>
359
- )?.__context;
360
- const incomingContext: Context | undefined = ctx;
361
- const shouldReplace =
362
- !currentContext ||
363
- !incomingContext ||
364
- currentContext.modified <= incomingContext.modified;
365
-
366
- if (shouldReplace && current !== item) {
367
- itemIdRef.current.delete(current as object);
368
- next[existingIndex] = item;
369
- hasMutations = true;
370
- }
371
-
372
- if (key) {
373
- itemIdRef.current.set(item as object, key);
374
- keyIndex.set(key, existingIndex);
375
- }
376
- if (head != null) seenHeads.add(head);
377
- continue;
378
- }
379
-
380
- if (head != null && seenHeads.has(head)) continue;
381
- if (head != null) seenHeads.add(head);
382
-
383
- freshItems.push(item);
384
- if (key) {
385
- itemIdRef.current.set(item as object, key);
386
- keyIndex.set(key, prev.length + freshItems.length - 1);
387
- }
388
- }
389
- }
390
-
391
-
392
- if (!freshItems.length && !hasMutations) {
393
- emptyResultsRef.current = iterators.every((i) => i.iterator.done());
394
- log("No new items or mutations");
395
- return !emptyResultsRef.current;
396
- }
397
-
398
-
399
- const combined = reverseRef.current
400
- ? [...freshItems.reverse(), ...next]
401
- : [...next, ...freshItems];
402
-
403
- log("Updating all with", {
404
- prevLength: prev.length,
405
- freshLength: freshItems.length,
406
- combinedLength: combined.length,
407
- });
408
- updateAll(combined);
409
-
410
- emptyResultsRef.current = iterators.every((i) => i.iterator.done());
411
- return !emptyResultsRef.current;
412
- };
413
-
414
- const drainRoundRobin = async (
415
- iterators: IteratorRef[],
416
- n: number
417
- ): Promise<boolean> => {
418
- const batches: { ref: IteratorRef; items: Item[] }[] = [];
419
- for (const ref of iterators) {
420
- if (ref.iterator.done()) continue;
421
- const batch = await ref.iterator.next(n);
422
- log("Iterator", ref.id, "fetched", batch.length, "items");
423
- if (batch.length) {
424
- ref.itemsConsumed += batch.length;
425
- batches.push({ ref, items: batch });
426
- }
427
- }
428
- return handleBatch(iterators, batches);
429
- };
430
-
431
- /* maybe make the rule that if results are empty and we get results from joining
432
- set the results to the joining results
433
- when results are not empty use onMerge option to merge the results ? */
434
-
435
- const loadMore = async (n: number = batchSize): Promise<boolean> => {
436
- const iterators = iteratorRefs.current;
437
- if (!iterators.length || emptyResultsRef.current) {
438
- log("No iterators or already empty", {
439
- length: iterators.length,
440
- emptyResultsRef: emptyResultsRef.current,
441
- });
442
- return false;
443
- }
444
-
445
- setIsLoading(true);
446
- try {
447
- /* one-time replicator warm-up across all DBs */
448
- if (shouldWait()) {
449
- /* if (
450
- typeof options.remote === "object" &&
451
- options.remote.wait
452
- ) {
453
- await Promise.all(
454
- iterators.map(async ({ db }) => {
455
- try {
456
- await db.log.waitForReplicators({
457
- timeout: (options.remote as { warmup })
458
- .warmup,
459
- signal: closeControllerRef.current?.signal,
460
- });
461
- } catch (e) {
462
- if (
463
- e instanceof AbortError ||
464
- e instanceof NoPeersError
465
- )
466
- return;
467
- console.warn("Remote replicators not ready", e);
468
- }
469
- })
470
- );
471
- }*/
472
- markWaited();
473
- }
474
-
475
- return drainRoundRobin(iterators, n);
476
- } catch (e) {
477
- if (!(e instanceof ClosedError)) throw e;
478
- return false;
479
- } finally {
480
- setIsLoading(false);
481
- }
482
- };
483
-
484
- /* ────────────── live-merge listeners ────────────── */
485
- useEffect(() => {
486
- if (!options.updates) {
487
- return;
488
- }
489
-
490
- /* const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
491
- const mergeFn =
492
- typeof options.onChange?.merge === "function"
493
- ? options.onChange.merge
494
- : (c: DocumentsChange<T, I>) => c;
495
-
496
- const handler = async (e: CustomEvent<DocumentsChange<T, I>>) => {
497
- log("Merge change", e.detail, "it", itId);
498
- const filtered = await mergeFn(e.detail);
499
- if (
500
- !filtered ||
501
- (!filtered.added.length && !filtered.removed.length)
502
- )
503
- return;
504
-
505
- let merged: Item[];
506
- if (options.onChange?.update) {
507
- merged = await options.onChange.update(
508
- allRef.current,
509
- filtered
510
- );
511
- } else {
512
- merged = await db.index.updateResults(
513
- allRef.current as WithContext<RT>[],
514
- filtered,
515
- options.query || {},
516
- options.resolve ?? true
517
- );
518
- }
519
- updateAll(options.reverse ? merged.reverse() : merged);
520
- };
521
- db.events.addEventListener("change", handler);
522
- return { db, handler };
523
- });
524
-
525
- return () => {
526
- listeners.forEach(({ db, handler }) =>
527
- db.events.removeEventListener("change", handler)
528
- );
529
- }; */
530
-
531
- // eslint-disable-next-line react-hooks/exhaustive-deps
532
- }, [
533
- iteratorRefs.current.map((r) => r.db.address).join("|"),
534
- options.updates,
535
- options.query,
536
- options.resolve,
537
- options.reverse,
538
- ]);
539
-
540
- /* ────────────── public API – unchanged from the caller's perspective ────────────── */
541
- return {
542
- items: all,
543
- loadMore,
544
- isLoading,
545
- empty: () => emptyResultsRef.current,
546
- id,
547
- };
548
- };
File without changes