@peerbit/react 0.0.30 → 0.0.32

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/src/useQuery.tsx CHANGED
@@ -1,16 +1,17 @@
1
1
  import { useState, useEffect, useRef, useMemo } from "react";
2
2
  import {
3
+ AbstractSearchRequest,
4
+ AbstractSearchResult,
3
5
  ClosedError,
4
6
  Documents,
5
- DocumentsChange,
7
+ RemoteQueryOptions,
6
8
  ResultsIterator,
7
9
  WithContext,
8
10
  } from "@peerbit/document";
9
11
  import * as indexerTypes from "@peerbit/indexer-interface";
10
- import { AbortError } from "@peerbit/time";
11
- import { NoPeersError } from "@peerbit/shared-log";
12
12
  import { v4 as uuid } from "uuid";
13
13
  import { WithIndexedContext } from "@peerbit/document";
14
+ import { UpdateOptions } from "@peerbit/document";
14
15
 
15
16
  type QueryOptions = { query: QueryLike; id?: string };
16
17
 
@@ -26,38 +27,39 @@ export type QueryLike = {
26
27
  * All the non-DB-specific options supported by the original single-DB hook.
27
28
  * They stay fully backward-compatible.
28
29
  */
29
- export type UseQuerySharedOptions<T, I, R extends boolean | undefined, RT> = {
30
+ export type UseQuerySharedOptions<
31
+ T,
32
+ I,
33
+ R extends boolean | undefined,
34
+ RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>,
35
+ > = {
30
36
  /* original behavioural flags */
31
37
  resolve?: R;
32
38
  transform?: (r: RT) => Promise<RT>;
33
39
  debounce?: number;
34
- debug?: boolean | { id: string };
40
+ debug?: boolean | string;
35
41
  reverse?: boolean;
36
42
  batchSize?: number;
37
43
  prefetch?: boolean;
38
- ignoreUpdates?: boolean;
39
- onChange?: {
40
- merge?:
41
- | boolean
42
- | ((
43
- c: DocumentsChange<T, I>
44
- ) =>
45
- | DocumentsChange<T, I>
46
- | Promise<DocumentsChange<T, I>>
47
- | undefined);
48
- update?: (
49
- prev: RT[],
50
- change: DocumentsChange<T, I>
51
- ) => RT[] | Promise<RT[]>;
52
- };
44
+ /* onChange?: {
45
+ merge?:
46
+ | boolean
47
+ | ((
48
+ c: DocumentsChange<T, I>
49
+ ) =>
50
+ | DocumentsChange<T, I>
51
+ | Promise<DocumentsChange<T, I>>
52
+ | undefined);
53
+ update?: (
54
+ prev: RT[],
55
+ change: DocumentsChange<T, I>
56
+ ) => RT[] | Promise<RT[]>;
57
+ }; */
58
+ updates?: UpdateOptions<T, I, R>;
53
59
  local?: boolean;
54
60
  remote?:
55
- | boolean
56
- | {
57
- warmup?: number;
58
- joining?: { waitFor?: number };
59
- eager?: boolean;
60
- };
61
+ | boolean
62
+ | RemoteQueryOptions<AbstractSearchRequest, AbstractSearchResult, any>;
61
63
  } & QueryOptions;
62
64
 
63
65
  /* ────────────────────────── Main Hook ────────────────────────── */
@@ -74,7 +76,7 @@ export const useQuery = <
74
76
  T extends Record<string, any>,
75
77
  I extends Record<string, any>,
76
78
  R extends boolean | undefined = true,
77
- RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>
79
+ RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>,
78
80
  >(
79
81
  /** Single DB or list of DBs. 100 % backward-compatible with the old single param. */
80
82
  dbOrDbs: Documents<T, I> | Documents<T, I>[] | undefined,
@@ -118,7 +120,7 @@ export const useQuery = <
118
120
  const log = (...a: any[]) => {
119
121
  if (!options.debug) return;
120
122
  if (typeof options.debug === "boolean") console.log(...a);
121
- else console.log(options.debug.id, ...a);
123
+ else console.log(options.debug, ...a);
122
124
  };
123
125
 
124
126
  const updateAll = (combined: Item[]) => {
@@ -127,7 +129,7 @@ export const useQuery = <
127
129
  };
128
130
 
129
131
  const reset = () => {
130
- iteratorRefs.current.forEach(({ iterator }) => iterator.close());
132
+ iteratorRefs.current?.forEach(({ iterator }) => iterator.close());
131
133
  iteratorRefs.current = [];
132
134
 
133
135
  closeControllerRef.current?.abort();
@@ -154,15 +156,120 @@ export const useQuery = <
154
156
  return;
155
157
  }
156
158
 
159
+ let isLogOpenInterval = options.debug
160
+ ? setInterval(() => {
161
+ log(
162
+ "is open?",
163
+ iteratorRefs.current.map((x) => !x.iterator.done())
164
+ );
165
+ }, 5e3)
166
+ : undefined;
157
167
  reset();
158
168
  const abortSignal = closeControllerRef.current?.signal;
169
+ const onMissedResults = (evt: { amount: number }) => {
170
+ console.error("Not effective yet: missed results", evt);
171
+ /* if (allRef.current.length > 0 || typeof options.remote !== "object" || !options.updates) {
172
+ return;
173
+ }
174
+ console.log("Missed results, loading more", evt.amount);
175
+ loadMore(evt.amount); */
176
+ };
177
+ let draining = false;
178
+ const scheduleDrain = (ref: ResultsIterator<RT>, amount: number) => {
179
+ if (draining) return;
180
+ draining = true;
181
+ loadMore(amount)
182
+ .catch((e) => {
183
+ if (!(e instanceof ClosedError)) throw e;
184
+ })
185
+ .finally(() => {
186
+ draining = false;
187
+ });
188
+ };
159
189
 
160
190
  iteratorRefs.current = openDbs.map((db) => {
161
191
  const iterator = db.index.iterate(query ?? {}, {
192
+ closePolicy: "manual",
162
193
  local: options.local ?? true,
163
- remote: options.remote ?? undefined,
194
+ remote: options.remote
195
+ ? {
196
+ ...(typeof options?.remote === "object"
197
+ ? {
198
+ ...options.remote,
199
+ onLateResults: onMissedResults,
200
+ wait: {
201
+ ...options?.remote?.wait,
202
+ timeout:
203
+ options?.remote?.wait?.timeout ??
204
+ 5000,
205
+ },
206
+ }
207
+ : options?.remote
208
+ ? {
209
+ onLateResults: onMissedResults,
210
+ }
211
+ : undefined),
212
+ }
213
+ : undefined,
164
214
  resolve,
165
215
  signal: abortSignal,
216
+ updates: {
217
+ merge:
218
+ typeof options.updates === "boolean" && options.updates
219
+ ? true
220
+ : typeof options.updates === "object" &&
221
+ options.updates.merge
222
+ ? true
223
+ : false,
224
+ onChange: (evt) => {
225
+ if (evt.added.length > 0) {
226
+ scheduleDrain(
227
+ iterator as ResultsIterator<RT>,
228
+ evt.added.length
229
+ );
230
+ }
231
+ },
232
+ onResults: (batch, props) => {
233
+ if (
234
+ props.reason === "join" ||
235
+ props.reason === "change"
236
+ ) {
237
+ let newArr = [...allRef.current];
238
+ for (const item of batch) {
239
+ const id = db.index.resolveId(item);
240
+ const existingIndex = newArr.findIndex((x) => {
241
+ let ix = (
242
+ options?.resolve
243
+ ? (x as WithIndexedContext<T, I>)
244
+ ?.__indexed
245
+ : (x as WithContext<I>)
246
+ ) as I;
247
+ const existingId = db.index.resolveId(ix);
248
+ return existingId === id;
249
+ });
250
+ if (existingIndex !== -1) {
251
+ newArr[existingIndex] = item as Item;
252
+ } else {
253
+ if (!options.reverse) {
254
+ newArr.unshift(item as Item);
255
+ } else {
256
+ newArr.push(item as Item);
257
+ }
258
+ }
259
+ }
260
+ log(
261
+ "merging ",
262
+ batch,
263
+ "into ",
264
+ newArr,
265
+ [...allRef.current],
266
+ options?.resolve
267
+ );
268
+
269
+ updateAll(newArr);
270
+ }
271
+ },
272
+ },
166
273
  }) as ResultsIterator<Item>;
167
274
 
168
275
  const ref = { id: uuid(), db, iterator, itemsConsumed: 0 };
@@ -170,10 +277,15 @@ export const useQuery = <
170
277
  return ref;
171
278
  });
172
279
 
173
- /* prefetch if requested */
174
- if (options.prefetch) void loadMore();
175
280
  /* store a deterministic id (useful for external keys) */
176
281
  setId(uuid());
282
+
283
+ /* prefetch if requested */
284
+ if (options.prefetch) void loadMore();
285
+
286
+ return () => {
287
+ clearInterval(isLogOpenInterval);
288
+ };
177
289
  // eslint-disable-next-line react-hooks/exhaustive-deps
178
290
  }, [
179
291
  dbs.map((d) => d?.address).join("|"),
@@ -195,37 +307,47 @@ export const useQuery = <
195
307
  waitedOnceRef.current = true;
196
308
  };
197
309
 
310
+ /* maybe make the rule that if results are empty and we get results from joining
311
+ set the results to the joining results
312
+ when results are not empty use onMerge option to merge the results ? */
313
+
198
314
  const loadMore = async (n: number = batchSize): Promise<boolean> => {
199
315
  const iterators = iteratorRefs.current;
200
- if (!iterators.length || emptyResultsRef.current) return false;
316
+ if (!iterators.length || emptyResultsRef.current) {
317
+ log("No iterators or already empty", {
318
+ length: iterators.length,
319
+ emptyResultsRef: emptyResultsRef.current,
320
+ });
321
+ return false;
322
+ }
201
323
 
202
324
  setIsLoading(true);
203
325
  try {
204
326
  /* one-time replicator warm-up across all DBs */
205
327
  if (shouldWait()) {
206
- if (
207
- typeof options.remote === "object" &&
208
- options.remote.warmup
209
- ) {
210
- await Promise.all(
211
- iterators.map(async ({ db }) => {
212
- try {
213
- await db.log.waitForReplicators({
214
- timeout: (options.remote as { warmup })
215
- .warmup,
216
- signal: closeControllerRef.current?.signal,
217
- });
218
- } catch (e) {
219
- if (
220
- e instanceof AbortError ||
221
- e instanceof NoPeersError
222
- )
223
- return;
224
- console.warn("Remote replicators not ready", e);
225
- }
226
- })
227
- );
228
- }
328
+ /* if (
329
+ typeof options.remote === "object" &&
330
+ options.remote.wait
331
+ ) {
332
+ await Promise.all(
333
+ iterators.map(async ({ db }) => {
334
+ try {
335
+ await db.log.waitForReplicators({
336
+ timeout: (options.remote as { warmup })
337
+ .warmup,
338
+ signal: closeControllerRef.current?.signal,
339
+ });
340
+ } catch (e) {
341
+ if (
342
+ e instanceof AbortError ||
343
+ e instanceof NoPeersError
344
+ )
345
+ return;
346
+ console.warn("Remote replicators not ready", e);
347
+ }
348
+ })
349
+ );
350
+ }*/
229
351
  markWaited();
230
352
  }
231
353
 
@@ -234,6 +356,7 @@ export const useQuery = <
234
356
  for (const ref of iterators) {
235
357
  if (ref.iterator.done()) continue;
236
358
  const batch = await ref.iterator.next(n); // pull up to <n> at once
359
+ log("Iterator", ref.id, "fetched", batch.length, "items");
237
360
  if (batch.length) {
238
361
  ref.itemsConsumed += batch.length;
239
362
  newlyFetched.push(...batch);
@@ -281,13 +404,15 @@ export const useQuery = <
281
404
 
282
405
  /* ────────────── live-merge listeners ────────────── */
283
406
  useEffect(() => {
284
- if (!options.onChange || options.onChange.merge === false) return;
407
+ if (!options.updates) {
408
+ return;
409
+ }
285
410
 
286
- const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
287
- const mergeFn =
411
+ /* const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
412
+ const mergeFn =
288
413
  typeof options.onChange?.merge === "function"
289
414
  ? options.onChange.merge
290
- : (c: DocumentsChange<T, I>) => c;
415
+ : (c: DocumentsChange<T, I>) => c;
291
416
 
292
417
  const handler = async (e: CustomEvent<DocumentsChange<T, I>>) => {
293
418
  log("Merge change", e.detail, "it", itId);
@@ -314,7 +439,7 @@ export const useQuery = <
314
439
  }
315
440
  updateAll(options.reverse ? merged.reverse() : merged);
316
441
  };
317
- db.events.addEventListener("change", handler);
442
+ db.events.addEventListener("change", handler);
318
443
  return { db, handler };
319
444
  });
320
445
 
@@ -322,11 +447,15 @@ export const useQuery = <
322
447
  listeners.forEach(({ db, handler }) =>
323
448
  db.events.removeEventListener("change", handler)
324
449
  );
325
- };
450
+ }; */
451
+
326
452
  // eslint-disable-next-line react-hooks/exhaustive-deps
327
453
  }, [
328
454
  iteratorRefs.current.map((r) => r.db.address).join("|"),
329
- options.onChange,
455
+ options.updates,
456
+ options.query,
457
+ options.resolve,
458
+ options.reverse,
330
459
  ]);
331
460
 
332
461
  /* ────────────── public API – unchanged from the caller's perspective ────────────── */
package/src/utils.ts CHANGED
@@ -142,7 +142,7 @@ export const inIframe = () => {
142
142
  };
143
143
 
144
144
  export function debounceLeadingTrailing<
145
- T extends (this: any, ...args: any[]) => void
145
+ T extends (this: any, ...args: any[]) => void,
146
146
  >(
147
147
  func: T,
148
148
  delay: number