@peerbit/react 0.0.31 → 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/lib/esm/__tests__/useQuery.dom.test.d.ts +1 -0
- package/lib/esm/__tests__/useQuery.dom.test.js +359 -0
- package/lib/esm/__tests__/useQuery.dom.test.js.map +1 -0
- package/lib/esm/useQuery.d.ts +6 -17
- package/lib/esm/useQuery.js +165 -42
- package/lib/esm/useQuery.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/useQuery.dom.test.ts +391 -0
- package/src/useLocal.tsx +1 -1
- package/src/useOnline.tsx +1 -1
- package/src/useProgram.tsx +1 -1
- package/src/useQuery.tsx +192 -63
- package/src/utils.ts +1 -1
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
|
-
|
|
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<
|
|
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 |
|
|
40
|
+
debug?: boolean | string;
|
|
35
41
|
reverse?: boolean;
|
|
36
42
|
batchSize?: number;
|
|
37
43
|
prefetch?: boolean;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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.
|
|
407
|
+
if (!options.updates) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
285
410
|
|
|
286
|
-
const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
|
|
287
|
-
|
|
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.
|
|
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