@peerbit/react 0.0.26 → 0.0.27
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/useQuery.d.ts +33 -15
- package/lib/esm/useQuery.js +142 -194
- package/lib/esm/useQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/useQuery.tsx +240 -305
package/lib/esm/useQuery.d.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { Documents, DocumentsChange, WithContext } from "@peerbit/document";
|
|
2
2
|
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
3
|
-
import { WithIndexedContext } from "@peerbit/document
|
|
4
|
-
type QueryLike = {
|
|
5
|
-
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
6
|
-
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
7
|
-
};
|
|
3
|
+
import { WithIndexedContext } from "@peerbit/document";
|
|
8
4
|
type QueryOptions = {
|
|
9
5
|
query: QueryLike;
|
|
10
6
|
id?: string;
|
|
11
7
|
};
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
eager?: boolean;
|
|
8
|
+
export type QueryLike = {
|
|
9
|
+
/** Mongo-style selector or array of selectors */
|
|
10
|
+
query?: indexerTypes.QueryLike | indexerTypes.Query[];
|
|
11
|
+
/** Sort definition compatible with `@peerbit/indexer-interface` */
|
|
12
|
+
sort?: indexerTypes.SortLike | indexerTypes.Sort | indexerTypes.Sort[];
|
|
18
13
|
};
|
|
19
|
-
|
|
14
|
+
/**
|
|
15
|
+
* All the non-DB-specific options supported by the original single-DB hook.
|
|
16
|
+
* They stay fully backward-compatible.
|
|
17
|
+
*/
|
|
18
|
+
export type UseQuerySharedOptions<T, I, R extends boolean | undefined, RT> = {
|
|
20
19
|
resolve?: R;
|
|
21
20
|
transform?: (r: RT) => Promise<RT>;
|
|
22
21
|
debounce?: number;
|
|
@@ -26,15 +25,34 @@ export declare const useQuery: <T extends Record<string, any>, I extends Record<
|
|
|
26
25
|
reverse?: boolean;
|
|
27
26
|
batchSize?: number;
|
|
28
27
|
prefetch?: boolean;
|
|
28
|
+
ignoreUpdates?: boolean;
|
|
29
29
|
onChange?: {
|
|
30
30
|
merge?: boolean | ((c: DocumentsChange<T, I>) => DocumentsChange<T, I> | Promise<DocumentsChange<T, I>> | undefined);
|
|
31
31
|
update?: (prev: RT[], change: DocumentsChange<T, I>) => RT[] | Promise<RT[]>;
|
|
32
32
|
};
|
|
33
33
|
local?: boolean;
|
|
34
|
-
remote?: boolean |
|
|
35
|
-
|
|
34
|
+
remote?: boolean | {
|
|
35
|
+
warmup?: number;
|
|
36
|
+
joining?: {
|
|
37
|
+
waitFor?: number;
|
|
38
|
+
};
|
|
39
|
+
eager?: boolean;
|
|
40
|
+
};
|
|
41
|
+
} & QueryOptions;
|
|
42
|
+
/**
|
|
43
|
+
* `useQuery` – unified hook that accepts **either**
|
|
44
|
+
* 1. a single `Documents` instance
|
|
45
|
+
* 2. an array of `Documents` instances
|
|
46
|
+
* 3. *or* omits the first argument and provides `dbs` inside the `options` object.
|
|
47
|
+
*
|
|
48
|
+
* It supersedes the original single-DB version as well as the experimental
|
|
49
|
+
* `useMultiQuery` so callers never have to choose between two APIs.
|
|
50
|
+
*/
|
|
51
|
+
export declare const useQuery: <T extends Record<string, any>, I extends Record<string, any>, R extends boolean | undefined = true, RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>>(
|
|
52
|
+
/** Single DB or list of DBs. 100 % backward-compatible with the old single param. */
|
|
53
|
+
dbOrDbs: Documents<T, I> | Documents<T, I>[] | undefined, options: UseQuerySharedOptions<T, I, R, RT>) => {
|
|
36
54
|
items: RT[];
|
|
37
|
-
loadMore: (n?: number
|
|
55
|
+
loadMore: (n?: number) => Promise<boolean>;
|
|
38
56
|
isLoading: boolean;
|
|
39
57
|
empty: () => boolean;
|
|
40
58
|
id: string | undefined;
|
package/lib/esm/useQuery.js
CHANGED
|
@@ -1,27 +1,46 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from "react";
|
|
1
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
2
2
|
import { ClosedError, } from "@peerbit/document";
|
|
3
3
|
import { AbortError } from "@peerbit/time";
|
|
4
4
|
import { NoPeersError } from "@peerbit/shared-log";
|
|
5
5
|
import { v4 as uuid } from "uuid";
|
|
6
|
-
/*
|
|
7
|
-
|
|
6
|
+
/* ────────────────────────── Main Hook ────────────────────────── */
|
|
7
|
+
/**
|
|
8
|
+
* `useQuery` – unified hook that accepts **either**
|
|
9
|
+
* 1. a single `Documents` instance
|
|
10
|
+
* 2. an array of `Documents` instances
|
|
11
|
+
* 3. *or* omits the first argument and provides `dbs` inside the `options` object.
|
|
12
|
+
*
|
|
13
|
+
* It supersedes the original single-DB version as well as the experimental
|
|
14
|
+
* `useMultiQuery` so callers never have to choose between two APIs.
|
|
15
|
+
*/
|
|
16
|
+
export const useQuery = (
|
|
17
|
+
/** Single DB or list of DBs. 100 % backward-compatible with the old single param. */
|
|
18
|
+
dbOrDbs, options) => {
|
|
19
|
+
/* ────────────── normalise DBs input ────────────── */
|
|
20
|
+
const dbs = useMemo(() => {
|
|
21
|
+
if (Array.isArray(dbOrDbs))
|
|
22
|
+
return dbOrDbs;
|
|
23
|
+
if (dbOrDbs)
|
|
24
|
+
return [dbOrDbs];
|
|
25
|
+
return [];
|
|
26
|
+
}, [dbOrDbs]);
|
|
8
27
|
/* ────────────── state & refs ────────────── */
|
|
9
28
|
const [all, setAll] = useState([]);
|
|
10
29
|
const allRef = useRef([]);
|
|
11
30
|
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
-
const
|
|
13
|
-
const iteratorRef = useRef(null);
|
|
31
|
+
const iteratorRefs = useRef([]);
|
|
14
32
|
const emptyResultsRef = useRef(false);
|
|
15
33
|
const closeControllerRef = useRef(null);
|
|
16
34
|
const waitedOnceRef = useRef(false);
|
|
17
|
-
|
|
18
|
-
const
|
|
35
|
+
/* keep an id mostly for debugging – mirrors original behaviour */
|
|
36
|
+
const [id, setId] = useState(options.id);
|
|
37
|
+
const reverseRef = useRef(options.reverse);
|
|
19
38
|
useEffect(() => {
|
|
20
|
-
reverseRef.current = options
|
|
21
|
-
}, [options
|
|
22
|
-
/* ──────────────
|
|
39
|
+
reverseRef.current = options.reverse;
|
|
40
|
+
}, [options.reverse]);
|
|
41
|
+
/* ────────────── utilities ────────────── */
|
|
23
42
|
const log = (...a) => {
|
|
24
|
-
if (!options
|
|
43
|
+
if (!options.debug)
|
|
25
44
|
return;
|
|
26
45
|
if (typeof options.debug === "boolean")
|
|
27
46
|
console.log(...a);
|
|
@@ -32,230 +51,159 @@ export const useQuery = (db, options) => {
|
|
|
32
51
|
allRef.current = combined;
|
|
33
52
|
setAll(combined);
|
|
34
53
|
};
|
|
35
|
-
const reset = (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
iteratorRef.current = null;
|
|
54
|
+
const reset = () => {
|
|
55
|
+
iteratorRefs.current.forEach(({ iterator }) => iterator.close());
|
|
56
|
+
iteratorRefs.current = [];
|
|
41
57
|
closeControllerRef.current?.abort();
|
|
42
58
|
closeControllerRef.current = new AbortController();
|
|
43
59
|
emptyResultsRef.current = false;
|
|
44
|
-
|
|
60
|
+
waitedOnceRef.current = false;
|
|
45
61
|
allRef.current = [];
|
|
46
62
|
setAll([]);
|
|
47
63
|
setIsLoading(false);
|
|
48
|
-
|
|
49
|
-
log("Iterator reset", toClose?.id, fromRef?.id);
|
|
50
|
-
setId(undefined);
|
|
64
|
+
log("Iterators reset");
|
|
51
65
|
};
|
|
66
|
+
/* ────────── rebuild iterators when db list / query etc. change ────────── */
|
|
52
67
|
useEffect(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
reset(null);
|
|
68
|
+
/* derive canonical list of open DBs */
|
|
69
|
+
const openDbs = dbs.filter((d) => Boolean(d && !d.closed));
|
|
70
|
+
const { query, resolve } = options;
|
|
71
|
+
if (!openDbs.length || query == null) {
|
|
72
|
+
reset();
|
|
59
73
|
return;
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
waitFor: options.remote.joining?.waitFor ??
|
|
73
|
-
5e3,
|
|
74
|
-
onMissedResults: ({ amount }) => {
|
|
75
|
-
loadMore(amount, true);
|
|
76
|
-
},
|
|
77
|
-
}
|
|
78
|
-
: undefined,
|
|
79
|
-
};
|
|
80
|
-
const ref = {
|
|
81
|
-
id,
|
|
82
|
-
iterator: db.index.iterate(options.query ?? {}, {
|
|
83
|
-
local: options?.local ?? true,
|
|
84
|
-
remote: remoteQueryOptions,
|
|
85
|
-
resolve: options?.resolve,
|
|
86
|
-
signal: closeControllerRef.current?.signal,
|
|
87
|
-
}),
|
|
88
|
-
itemsConsumed: 0,
|
|
89
|
-
};
|
|
90
|
-
iteratorRef.current = ref;
|
|
91
|
-
if (options?.prefetch) {
|
|
92
|
-
loadMore();
|
|
93
|
-
}
|
|
94
|
-
setId(id);
|
|
95
|
-
log("Iterator initialised", ref.id);
|
|
75
|
+
reset();
|
|
76
|
+
const abortSignal = closeControllerRef.current?.signal;
|
|
77
|
+
iteratorRefs.current = openDbs.map((db) => {
|
|
78
|
+
const iterator = db.index.iterate(query ?? {}, {
|
|
79
|
+
local: options.local ?? true,
|
|
80
|
+
remote: options.remote ?? undefined,
|
|
81
|
+
resolve,
|
|
82
|
+
signal: abortSignal,
|
|
83
|
+
});
|
|
84
|
+
const ref = { id: uuid(), db, iterator, itemsConsumed: 0 };
|
|
85
|
+
log("Iterator init", ref.id, "db", db.address);
|
|
96
86
|
return ref;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
log("Merge change", e.detail, "iterator", newIteratorRef.id);
|
|
108
|
-
const filtered = await mergeFn(e.detail);
|
|
109
|
-
if (!filtered ||
|
|
110
|
-
(filtered.added.length === 0 &&
|
|
111
|
-
filtered.removed.length === 0))
|
|
112
|
-
return;
|
|
113
|
-
let merged;
|
|
114
|
-
if (options.onChange?.update) {
|
|
115
|
-
merged = [
|
|
116
|
-
...(await options.onChange?.update(allRef.current, filtered)),
|
|
117
|
-
];
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
merged = await db.index.updateResults(allRef.current, filtered, options.query || {}, options.resolve ?? true);
|
|
121
|
-
log("After update", {
|
|
122
|
-
current: allRef.current,
|
|
123
|
-
merged,
|
|
124
|
-
filtered,
|
|
125
|
-
query: options.query,
|
|
126
|
-
});
|
|
127
|
-
const expectedDiff = filtered.added.length - filtered.removed.length;
|
|
128
|
-
if (merged === allRef.current ||
|
|
129
|
-
(expectedDiff !== 0 &&
|
|
130
|
-
merged.length === allRef.current.length)) {
|
|
131
|
-
// no change
|
|
132
|
-
log("no change after merge");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
updateAll(options?.reverse ? merged.reverse() : merged);
|
|
137
|
-
};
|
|
138
|
-
db.events.addEventListener("change", handleChange);
|
|
139
|
-
}
|
|
140
|
-
return () => {
|
|
141
|
-
handleChange &&
|
|
142
|
-
db.events.removeEventListener("change", handleChange);
|
|
143
|
-
reset(newIteratorRef);
|
|
144
|
-
};
|
|
145
|
-
}, [
|
|
146
|
-
db?.closed ? undefined : db?.address,
|
|
147
|
-
options?.id ?? options?.query,
|
|
148
|
-
options?.resolve,
|
|
149
|
-
options?.reverse,
|
|
150
|
-
]);
|
|
151
|
-
/* ────────────── loadMore (once-wait aware) ────────────── */
|
|
152
|
-
const batchSize = options?.batchSize ?? 10;
|
|
87
|
+
});
|
|
88
|
+
/* prefetch if requested */
|
|
89
|
+
if (options.prefetch)
|
|
90
|
+
void loadMore();
|
|
91
|
+
/* store a deterministic id (useful for external keys) */
|
|
92
|
+
setId(uuid());
|
|
93
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
+
}, [dbs.map((d) => d?.address).join("|"), options.query, options.resolve, options.reverse]);
|
|
95
|
+
/* ────────────── loadMore implementation ────────────── */
|
|
96
|
+
const batchSize = options.batchSize ?? 10;
|
|
153
97
|
const shouldWait = () => {
|
|
154
|
-
if (waitedOnceRef.current)
|
|
98
|
+
if (waitedOnceRef.current)
|
|
155
99
|
return false;
|
|
156
|
-
|
|
157
|
-
if (options?.remote === false)
|
|
100
|
+
if (options.remote === false)
|
|
158
101
|
return false;
|
|
159
|
-
if
|
|
160
|
-
return true;
|
|
161
|
-
if (options?.remote == null)
|
|
162
|
-
return true;
|
|
163
|
-
if (typeof options?.remote === "object") {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return true;
|
|
102
|
+
return true; // mimic original behaviour – wait once if remote allowed
|
|
167
103
|
};
|
|
168
104
|
const markWaited = () => {
|
|
169
105
|
waitedOnceRef.current = true;
|
|
170
106
|
};
|
|
171
|
-
const loadMore = async (n = batchSize
|
|
172
|
-
const
|
|
173
|
-
if (!
|
|
174
|
-
(emptyResultsRef.current && !pollEvenIfWasEmpty) ||
|
|
175
|
-
iterator.iterator.done() ||
|
|
176
|
-
loadingMoreRef.current) {
|
|
107
|
+
const loadMore = async (n = batchSize) => {
|
|
108
|
+
const iterators = iteratorRefs.current;
|
|
109
|
+
if (!iterators.length || emptyResultsRef.current)
|
|
177
110
|
return false;
|
|
178
|
-
}
|
|
179
111
|
setIsLoading(true);
|
|
180
|
-
loadingMoreRef.current = true;
|
|
181
112
|
try {
|
|
182
|
-
/*
|
|
113
|
+
/* one-time replicator warm-up across all DBs */
|
|
183
114
|
if (shouldWait()) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
e instanceof NoPeersError)
|
|
199
|
-
return;
|
|
200
|
-
console.warn("Remote replicators not ready", e);
|
|
201
|
-
})
|
|
202
|
-
.finally(() => {
|
|
203
|
-
log("Wait for replicators done", iterator.id, "time", Date.now() - t0);
|
|
204
|
-
markWaited();
|
|
205
|
-
});
|
|
115
|
+
if (typeof options.remote === "object" && options.remote.warmup) {
|
|
116
|
+
await Promise.all(iterators.map(async ({ db }) => {
|
|
117
|
+
try {
|
|
118
|
+
await db.log.waitForReplicators({
|
|
119
|
+
timeout: options.remote.warmup,
|
|
120
|
+
signal: closeControllerRef.current?.signal,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
if (e instanceof AbortError || e instanceof NoPeersError)
|
|
125
|
+
return;
|
|
126
|
+
console.warn("Remote replicators not ready", e);
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
206
129
|
}
|
|
130
|
+
markWaited();
|
|
207
131
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
/* iterator might have been reset while we were async… */
|
|
220
|
-
if (iteratorRef.current !== iterator) {
|
|
221
|
-
log("Iterator reset while loading more");
|
|
222
|
-
return false;
|
|
132
|
+
/* pull items round-robin */
|
|
133
|
+
const newlyFetched = [];
|
|
134
|
+
for (const ref of iterators) {
|
|
135
|
+
if (ref.iterator.done())
|
|
136
|
+
continue;
|
|
137
|
+
const batch = await ref.iterator.next(n); // pull up to <n> at once
|
|
138
|
+
if (batch.length) {
|
|
139
|
+
ref.itemsConsumed += batch.length;
|
|
140
|
+
newlyFetched.push(...batch);
|
|
141
|
+
}
|
|
223
142
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
log("Loaded more items for iterator", iterator.id, "current id", iteratorRef.current?.id, "new items", newItems.length, "previous results", allRef.current.length, "batchSize", batchSize, "items consumed", iterator.itemsConsumed);
|
|
228
|
-
const prev = allRef.current;
|
|
229
|
-
const dedup = new Set(prev.map((x) => x.__context.head));
|
|
230
|
-
const unique = newItems.filter((x) => !dedup.has(x.__context.head));
|
|
231
|
-
if (!unique.length)
|
|
232
|
-
return;
|
|
233
|
-
const combined = reverseRef.current
|
|
234
|
-
? [...unique.reverse(), ...prev]
|
|
235
|
-
: [...prev, ...unique];
|
|
236
|
-
updateAll(combined);
|
|
143
|
+
if (!newlyFetched.length) {
|
|
144
|
+
emptyResultsRef.current = iterators.every((i) => i.iterator.done());
|
|
145
|
+
return !emptyResultsRef.current;
|
|
237
146
|
}
|
|
238
|
-
|
|
239
|
-
|
|
147
|
+
/* optional transform */
|
|
148
|
+
let processed = newlyFetched;
|
|
149
|
+
if (options.transform) {
|
|
150
|
+
processed = await Promise.all(processed.map(options.transform));
|
|
240
151
|
}
|
|
241
|
-
|
|
152
|
+
/* deduplicate & merge */
|
|
153
|
+
const prev = allRef.current;
|
|
154
|
+
const dedupHeads = new Set(prev.map((x) => x.__context.head));
|
|
155
|
+
const unique = processed.filter((x) => !dedupHeads.has(x.__context.head));
|
|
156
|
+
if (!unique.length)
|
|
157
|
+
return !iterators.every((i) => i.iterator.done());
|
|
158
|
+
const combined = reverseRef.current ? [...unique.reverse(), ...prev] : [...prev, ...unique];
|
|
159
|
+
updateAll(combined);
|
|
160
|
+
emptyResultsRef.current = iterators.every((i) => i.iterator.done());
|
|
161
|
+
return !emptyResultsRef.current;
|
|
242
162
|
}
|
|
243
163
|
catch (e) {
|
|
244
164
|
if (!(e instanceof ClosedError))
|
|
245
165
|
throw e;
|
|
166
|
+
return false;
|
|
246
167
|
}
|
|
247
168
|
finally {
|
|
248
169
|
setIsLoading(false);
|
|
249
|
-
loadingMoreRef.current = false;
|
|
250
170
|
}
|
|
251
171
|
};
|
|
252
|
-
/* ──────────────
|
|
172
|
+
/* ────────────── live-merge listeners ────────────── */
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!options.onChange || options.onChange.merge === false)
|
|
175
|
+
return;
|
|
176
|
+
const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
|
|
177
|
+
const mergeFn = typeof options.onChange?.merge === "function" ? options.onChange.merge : (c) => c;
|
|
178
|
+
const handler = async (e) => {
|
|
179
|
+
log("Merge change", e.detail, "it", itId);
|
|
180
|
+
const filtered = await mergeFn(e.detail);
|
|
181
|
+
if (!filtered || (!filtered.added.length && !filtered.removed.length))
|
|
182
|
+
return;
|
|
183
|
+
let merged;
|
|
184
|
+
if (options.onChange?.update) {
|
|
185
|
+
merged = await options.onChange.update(allRef.current, filtered);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
merged = await db.index.updateResults(allRef.current, filtered, options.query || {}, options.resolve ?? true);
|
|
189
|
+
}
|
|
190
|
+
updateAll(options.reverse ? merged.reverse() : merged);
|
|
191
|
+
};
|
|
192
|
+
db.events.addEventListener("change", handler);
|
|
193
|
+
return { db, handler };
|
|
194
|
+
});
|
|
195
|
+
return () => {
|
|
196
|
+
listeners.forEach(({ db, handler }) => db.events.removeEventListener("change", handler));
|
|
197
|
+
};
|
|
198
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
199
|
+
}, [iteratorRefs.current.map((r) => r.db.address).join("|"), options.onChange]);
|
|
200
|
+
/* ────────────── public API – unchanged from the caller's perspective ────────────── */
|
|
253
201
|
return {
|
|
254
202
|
items: all,
|
|
255
203
|
loadMore,
|
|
256
204
|
isLoading,
|
|
257
205
|
empty: () => emptyResultsRef.current,
|
|
258
|
-
id
|
|
206
|
+
id,
|
|
259
207
|
};
|
|
260
208
|
};
|
|
261
209
|
//# sourceMappingURL=useQuery.js.map
|
package/lib/esm/useQuery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQuery.js","sourceRoot":"","sources":["../../src/useQuery.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"useQuery.js","sourceRoot":"","sources":["../../src/useQuery.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"homepage": "https://dao-xyz.github.io/peerbit-examples",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"last 1 safari version"
|
|
71
71
|
]
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "727b1345b244358196a3782f7b1a61b26d8db498"
|
|
74
74
|
}
|
package/src/useQuery.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useRef,
|
|
1
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
3
|
ClosedError,
|
|
4
4
|
Documents,
|
|
@@ -10,82 +10,113 @@ import * as indexerTypes from "@peerbit/indexer-interface";
|
|
|
10
10
|
import { AbortError } from "@peerbit/time";
|
|
11
11
|
import { NoPeersError } from "@peerbit/shared-log";
|
|
12
12
|
import { v4 as uuid } from "uuid";
|
|
13
|
-
import { WithIndexedContext } from "@peerbit/document
|
|
14
|
-
|
|
15
|
-
/* ────────────── helper types ────────────── */
|
|
16
|
-
type QueryLike = {
|
|
17
|
-
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
18
|
-
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
19
|
-
};
|
|
13
|
+
import { WithIndexedContext } from "@peerbit/document";
|
|
14
|
+
|
|
20
15
|
type QueryOptions = { query: QueryLike; id?: string };
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
/* ────────────── helper types ────────────── */
|
|
18
|
+
export type QueryLike = {
|
|
19
|
+
/** Mongo-style selector or array of selectors */
|
|
20
|
+
query?: indexerTypes.QueryLike | indexerTypes.Query[];
|
|
21
|
+
/** Sort definition compatible with `@peerbit/indexer-interface` */
|
|
22
|
+
sort?: indexerTypes.SortLike | indexerTypes.Sort | indexerTypes.Sort[];
|
|
26
23
|
};
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
/**
|
|
26
|
+
* All the non-DB-specific options supported by the original single-DB hook.
|
|
27
|
+
* They stay fully backward-compatible.
|
|
28
|
+
*/
|
|
29
|
+
export type UseQuerySharedOptions<T, I, R extends boolean | undefined, RT> = {
|
|
30
|
+
/* original behavioural flags */
|
|
31
|
+
resolve?: R;
|
|
32
|
+
transform?: (r: RT) => Promise<RT>;
|
|
33
|
+
debounce?: number;
|
|
34
|
+
debug?: boolean | { id: string };
|
|
35
|
+
reverse?: boolean;
|
|
36
|
+
batchSize?: number;
|
|
37
|
+
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
|
+
};
|
|
53
|
+
local?: boolean;
|
|
54
|
+
remote?:
|
|
55
|
+
| boolean
|
|
56
|
+
| {
|
|
57
|
+
warmup?: number;
|
|
58
|
+
joining?: { waitFor?: number };
|
|
59
|
+
eager?: boolean;
|
|
60
|
+
};
|
|
61
|
+
} & QueryOptions;
|
|
62
|
+
|
|
63
|
+
/* ────────────────────────── Main Hook ────────────────────────── */
|
|
64
|
+
/**
|
|
65
|
+
* `useQuery` – unified hook that accepts **either**
|
|
66
|
+
* 1. a single `Documents` instance
|
|
67
|
+
* 2. an array of `Documents` instances
|
|
68
|
+
* 3. *or* omits the first argument and provides `dbs` inside the `options` object.
|
|
69
|
+
*
|
|
70
|
+
* It supersedes the original single-DB version as well as the experimental
|
|
71
|
+
* `useMultiQuery` so callers never have to choose between two APIs.
|
|
72
|
+
*/
|
|
29
73
|
export const useQuery = <
|
|
30
74
|
T extends Record<string, any>,
|
|
31
75
|
I extends Record<string, any>,
|
|
32
76
|
R extends boolean | undefined = true,
|
|
33
77
|
RT = R extends false ? WithContext<I> : WithIndexedContext<T, I>
|
|
34
78
|
>(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
transform?: (r: RT) => Promise<RT>;
|
|
39
|
-
debounce?: number;
|
|
40
|
-
debug?: boolean | { id: string };
|
|
41
|
-
reverse?: boolean;
|
|
42
|
-
batchSize?: number;
|
|
43
|
-
prefetch?: boolean;
|
|
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
|
-
local?: boolean;
|
|
59
|
-
remote?: boolean | RemoteQueryOptions;
|
|
60
|
-
} & QueryOptions
|
|
79
|
+
/** Single DB or list of DBs. 100 % backward-compatible with the old single param. */
|
|
80
|
+
dbOrDbs: Documents<T, I> | Documents<T, I>[] | undefined,
|
|
81
|
+
options: UseQuerySharedOptions<T, I, R, RT>
|
|
61
82
|
) => {
|
|
62
|
-
/*
|
|
83
|
+
/* ─────── internal type alias for convenience ─────── */
|
|
63
84
|
type Item = RT;
|
|
64
85
|
|
|
86
|
+
/* ────────────── normalise DBs input ────────────── */
|
|
87
|
+
const dbs = useMemo<(Documents<T, I> | undefined)[]>(() => {
|
|
88
|
+
if (Array.isArray(dbOrDbs)) return dbOrDbs;
|
|
89
|
+
if (dbOrDbs) return [dbOrDbs];
|
|
90
|
+
return [];
|
|
91
|
+
}, [dbOrDbs]);
|
|
92
|
+
|
|
65
93
|
/* ────────────── state & refs ────────────── */
|
|
66
94
|
const [all, setAll] = useState<Item[]>([]);
|
|
67
95
|
const allRef = useRef<Item[]>([]);
|
|
68
96
|
const [isLoading, setIsLoading] = useState(false);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
const iteratorRefs = useRef<
|
|
98
|
+
{
|
|
99
|
+
id: string;
|
|
100
|
+
db: Documents<T, I>;
|
|
101
|
+
iterator: ResultsIterator<Item>;
|
|
102
|
+
itemsConsumed: number;
|
|
103
|
+
}[]
|
|
104
|
+
>([]);
|
|
75
105
|
const emptyResultsRef = useRef(false);
|
|
76
106
|
const closeControllerRef = useRef<AbortController | null>(null);
|
|
77
107
|
const waitedOnceRef = useRef(false);
|
|
78
108
|
|
|
79
|
-
|
|
109
|
+
/* keep an id mostly for debugging – mirrors original behaviour */
|
|
110
|
+
const [id, setId] = useState<string | undefined>(options.id);
|
|
80
111
|
|
|
81
|
-
const reverseRef = useRef(options
|
|
112
|
+
const reverseRef = useRef(options.reverse);
|
|
82
113
|
useEffect(() => {
|
|
83
|
-
reverseRef.current = options
|
|
84
|
-
}, [options
|
|
114
|
+
reverseRef.current = options.reverse;
|
|
115
|
+
}, [options.reverse]);
|
|
85
116
|
|
|
86
|
-
/* ──────────────
|
|
117
|
+
/* ────────────── utilities ────────────── */
|
|
87
118
|
const log = (...a: any[]) => {
|
|
88
|
-
if (!options
|
|
119
|
+
if (!options.debug) return;
|
|
89
120
|
if (typeof options.debug === "boolean") console.log(...a);
|
|
90
121
|
else console.log(options.debug.id, ...a);
|
|
91
122
|
};
|
|
@@ -95,311 +126,215 @@ export const useQuery = <
|
|
|
95
126
|
setAll(combined);
|
|
96
127
|
};
|
|
97
128
|
|
|
98
|
-
const reset = (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
iterator: ResultsIterator<Item>;
|
|
102
|
-
} | null
|
|
103
|
-
) => {
|
|
104
|
-
const toClose = iteratorRef.current;
|
|
105
|
-
if (toClose && fromRef && toClose !== fromRef) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
iteratorRef.current = null;
|
|
129
|
+
const reset = () => {
|
|
130
|
+
iteratorRefs.current.forEach(({ iterator }) => iterator.close());
|
|
131
|
+
iteratorRefs.current = [];
|
|
110
132
|
|
|
111
133
|
closeControllerRef.current?.abort();
|
|
112
134
|
closeControllerRef.current = new AbortController();
|
|
113
135
|
emptyResultsRef.current = false;
|
|
114
|
-
|
|
115
|
-
toClose?.iterator.close();
|
|
136
|
+
waitedOnceRef.current = false;
|
|
116
137
|
|
|
117
138
|
allRef.current = [];
|
|
118
139
|
setAll([]);
|
|
119
|
-
|
|
120
140
|
setIsLoading(false);
|
|
121
|
-
|
|
122
|
-
log("Iterator reset", toClose?.id, fromRef?.id);
|
|
123
|
-
setId(undefined);
|
|
141
|
+
log("Iterators reset");
|
|
124
142
|
};
|
|
125
143
|
|
|
144
|
+
/* ────────── rebuild iterators when db list / query etc. change ────────── */
|
|
126
145
|
useEffect(() => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
/* derive canonical list of open DBs */
|
|
147
|
+
const openDbs = dbs.filter((d): d is Documents<T, I> =>
|
|
148
|
+
Boolean(d && !d.closed)
|
|
149
|
+
);
|
|
150
|
+
const { query, resolve } = options;
|
|
151
|
+
|
|
152
|
+
if (!openDbs.length || query == null) {
|
|
153
|
+
reset();
|
|
134
154
|
return;
|
|
135
155
|
}
|
|
136
156
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
let remoteQueryOptions =
|
|
140
|
-
options.remote == null || options.remote === false
|
|
141
|
-
? false
|
|
142
|
-
: {
|
|
143
|
-
...(typeof options.remote === "object"
|
|
144
|
-
? options.remote
|
|
145
|
-
: {}),
|
|
146
|
-
joining:
|
|
147
|
-
typeof options.remote === "object" &&
|
|
148
|
-
options.remote.joining?.waitFor !== undefined
|
|
149
|
-
? {
|
|
150
|
-
waitFor:
|
|
151
|
-
options.remote.joining?.waitFor ??
|
|
152
|
-
5e3,
|
|
153
|
-
onMissedResults: ({ amount }) => {
|
|
154
|
-
loadMore(amount, true);
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
: undefined,
|
|
158
|
-
};
|
|
159
|
-
const ref = {
|
|
160
|
-
id,
|
|
161
|
-
iterator: db.index.iterate(options.query ?? {}, {
|
|
162
|
-
local: options?.local ?? true,
|
|
163
|
-
remote: remoteQueryOptions,
|
|
164
|
-
resolve: options?.resolve,
|
|
165
|
-
signal: closeControllerRef.current?.signal,
|
|
166
|
-
}) as ResultsIterator<Item>,
|
|
167
|
-
itemsConsumed: 0,
|
|
168
|
-
};
|
|
169
|
-
iteratorRef.current = ref;
|
|
170
|
-
if (options?.prefetch) {
|
|
171
|
-
loadMore();
|
|
172
|
-
}
|
|
173
|
-
setId(id);
|
|
174
|
-
|
|
175
|
-
log("Iterator initialised", ref.id);
|
|
176
|
-
return ref;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
reset(iteratorRef.current);
|
|
180
|
-
const newIteratorRef = initIterator();
|
|
181
|
-
|
|
182
|
-
/* live-merge listener (optional) */
|
|
183
|
-
let handleChange:
|
|
184
|
-
| ((e: CustomEvent<DocumentsChange<T, I>>) => void | Promise<void>)
|
|
185
|
-
| undefined;
|
|
186
|
-
|
|
187
|
-
if (options?.onChange && options.onChange.merge !== false) {
|
|
188
|
-
const mergeFn =
|
|
189
|
-
typeof options.onChange.merge === "function"
|
|
190
|
-
? options.onChange.merge
|
|
191
|
-
: (c: DocumentsChange<T, I>) => c;
|
|
192
|
-
|
|
193
|
-
handleChange = async (e: CustomEvent<DocumentsChange<T, I>>) => {
|
|
194
|
-
log("Merge change", e.detail, "iterator", newIteratorRef.id);
|
|
195
|
-
const filtered = await mergeFn(e.detail);
|
|
196
|
-
if (
|
|
197
|
-
!filtered ||
|
|
198
|
-
(filtered.added.length === 0 &&
|
|
199
|
-
filtered.removed.length === 0)
|
|
200
|
-
)
|
|
201
|
-
return;
|
|
202
|
-
|
|
203
|
-
let merged: Item[];
|
|
204
|
-
if (options.onChange?.update) {
|
|
205
|
-
merged = [
|
|
206
|
-
...(await options.onChange?.update(
|
|
207
|
-
allRef.current,
|
|
208
|
-
filtered
|
|
209
|
-
)),
|
|
210
|
-
];
|
|
211
|
-
} else {
|
|
212
|
-
merged = await db.index.updateResults(
|
|
213
|
-
allRef.current as WithContext<RT>[],
|
|
214
|
-
filtered,
|
|
215
|
-
options.query || {},
|
|
216
|
-
options.resolve ?? true
|
|
217
|
-
);
|
|
157
|
+
reset();
|
|
158
|
+
const abortSignal = closeControllerRef.current?.signal;
|
|
218
159
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
filtered.added.length - filtered.removed.length;
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
merged === allRef.current ||
|
|
230
|
-
(expectedDiff !== 0 &&
|
|
231
|
-
merged.length === allRef.current.length)
|
|
232
|
-
) {
|
|
233
|
-
// no change
|
|
234
|
-
log("no change after merge");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
updateAll(options?.reverse ? merged.reverse() : merged);
|
|
240
|
-
};
|
|
160
|
+
iteratorRefs.current = openDbs.map((db) => {
|
|
161
|
+
const iterator = db.index.iterate(query ?? {}, {
|
|
162
|
+
local: options.local ?? true,
|
|
163
|
+
remote: options.remote ?? undefined,
|
|
164
|
+
resolve,
|
|
165
|
+
signal: abortSignal,
|
|
166
|
+
}) as ResultsIterator<Item>;
|
|
241
167
|
|
|
242
|
-
|
|
243
|
-
|
|
168
|
+
const ref = { id: uuid(), db, iterator, itemsConsumed: 0 };
|
|
169
|
+
log("Iterator init", ref.id, "db", db.address);
|
|
170
|
+
return ref;
|
|
171
|
+
});
|
|
244
172
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
173
|
+
/* prefetch if requested */
|
|
174
|
+
if (options.prefetch) void loadMore();
|
|
175
|
+
/* store a deterministic id (useful for external keys) */
|
|
176
|
+
setId(uuid());
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
250
178
|
}, [
|
|
251
|
-
|
|
252
|
-
options
|
|
253
|
-
options
|
|
254
|
-
options
|
|
179
|
+
dbs.map((d) => d?.address).join("|"),
|
|
180
|
+
options.query,
|
|
181
|
+
options.resolve,
|
|
182
|
+
options.reverse,
|
|
255
183
|
]);
|
|
256
184
|
|
|
257
|
-
/* ────────────── loadMore
|
|
258
|
-
const batchSize = options
|
|
185
|
+
/* ────────────── loadMore implementation ────────────── */
|
|
186
|
+
const batchSize = options.batchSize ?? 10;
|
|
259
187
|
|
|
260
188
|
const shouldWait = (): boolean => {
|
|
261
|
-
if (waitedOnceRef.current)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (options?.remote === false) return false;
|
|
265
|
-
if (options?.remote === true) return true;
|
|
266
|
-
if (options?.remote == null) return true;
|
|
267
|
-
if (typeof options?.remote === "object") {
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
return true;
|
|
189
|
+
if (waitedOnceRef.current) return false;
|
|
190
|
+
if (options.remote === false) return false;
|
|
191
|
+
return true; // mimic original behaviour – wait once if remote allowed
|
|
271
192
|
};
|
|
272
193
|
|
|
273
194
|
const markWaited = () => {
|
|
274
195
|
waitedOnceRef.current = true;
|
|
275
196
|
};
|
|
276
197
|
|
|
277
|
-
const loadMore = async (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
) => {
|
|
281
|
-
const iterator = iteratorRef.current;
|
|
282
|
-
if (
|
|
283
|
-
!iterator ||
|
|
284
|
-
(emptyResultsRef.current && !pollEvenIfWasEmpty) ||
|
|
285
|
-
iterator.iterator.done() ||
|
|
286
|
-
loadingMoreRef.current
|
|
287
|
-
) {
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
198
|
+
const loadMore = async (n: number = batchSize): Promise<boolean> => {
|
|
199
|
+
const iterators = iteratorRefs.current;
|
|
200
|
+
if (!iterators.length || emptyResultsRef.current) return false;
|
|
290
201
|
|
|
291
202
|
setIsLoading(true);
|
|
292
|
-
loadingMoreRef.current = true;
|
|
293
|
-
|
|
294
203
|
try {
|
|
295
|
-
/*
|
|
204
|
+
/* one-time replicator warm-up across all DBs */
|
|
296
205
|
if (shouldWait()) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return;
|
|
318
|
-
console.warn("Remote replicators not ready", e);
|
|
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
|
+
}
|
|
319
226
|
})
|
|
320
|
-
|
|
321
|
-
log(
|
|
322
|
-
"Wait for replicators done",
|
|
323
|
-
iterator.id,
|
|
324
|
-
"time",
|
|
325
|
-
Date.now() - t0
|
|
326
|
-
);
|
|
327
|
-
markWaited();
|
|
328
|
-
});
|
|
227
|
+
);
|
|
329
228
|
}
|
|
330
|
-
|
|
331
|
-
log("Skip wait for replicators", iterator.id);
|
|
229
|
+
markWaited();
|
|
332
230
|
}
|
|
333
231
|
|
|
334
|
-
/*
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
log("Transform end", iterator.id);
|
|
232
|
+
/* pull items round-robin */
|
|
233
|
+
const newlyFetched: Item[] = [];
|
|
234
|
+
for (const ref of iterators) {
|
|
235
|
+
if (ref.iterator.done()) continue;
|
|
236
|
+
const batch = await ref.iterator.next(n); // pull up to <n> at once
|
|
237
|
+
if (batch.length) {
|
|
238
|
+
ref.itemsConsumed += batch.length;
|
|
239
|
+
newlyFetched.push(...batch);
|
|
240
|
+
}
|
|
344
241
|
}
|
|
345
242
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
243
|
+
if (!newlyFetched.length) {
|
|
244
|
+
emptyResultsRef.current = iterators.every((i) =>
|
|
245
|
+
i.iterator.done()
|
|
246
|
+
);
|
|
247
|
+
return !emptyResultsRef.current;
|
|
351
248
|
}
|
|
352
249
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (newItems.length) {
|
|
358
|
-
log(
|
|
359
|
-
"Loaded more items for iterator",
|
|
360
|
-
iterator.id,
|
|
361
|
-
"current id",
|
|
362
|
-
iteratorRef.current?.id,
|
|
363
|
-
"new items",
|
|
364
|
-
newItems.length,
|
|
365
|
-
"previous results",
|
|
366
|
-
allRef.current.length,
|
|
367
|
-
"batchSize",
|
|
368
|
-
batchSize,
|
|
369
|
-
"items consumed",
|
|
370
|
-
iterator.itemsConsumed
|
|
371
|
-
);
|
|
372
|
-
const prev = allRef.current;
|
|
373
|
-
const dedup = new Set(
|
|
374
|
-
prev.map((x) => (x as any).__context.head)
|
|
375
|
-
);
|
|
376
|
-
const unique = newItems.filter(
|
|
377
|
-
(x) => !dedup.has((x as any).__context.head)
|
|
378
|
-
);
|
|
379
|
-
if (!unique.length) return;
|
|
380
|
-
|
|
381
|
-
const combined = reverseRef.current
|
|
382
|
-
? [...unique.reverse(), ...prev]
|
|
383
|
-
: [...prev, ...unique];
|
|
384
|
-
updateAll(combined);
|
|
385
|
-
} else {
|
|
386
|
-
log("No new items", iterator.id);
|
|
250
|
+
/* optional transform */
|
|
251
|
+
let processed = newlyFetched;
|
|
252
|
+
if (options.transform) {
|
|
253
|
+
processed = await Promise.all(processed.map(options.transform));
|
|
387
254
|
}
|
|
388
|
-
|
|
255
|
+
|
|
256
|
+
/* deduplicate & merge */
|
|
257
|
+
const prev = allRef.current;
|
|
258
|
+
const dedupHeads = new Set(
|
|
259
|
+
prev.map((x) => (x as any).__context.head)
|
|
260
|
+
);
|
|
261
|
+
const unique = processed.filter(
|
|
262
|
+
(x) => !dedupHeads.has((x as any).__context.head)
|
|
263
|
+
);
|
|
264
|
+
if (!unique.length)
|
|
265
|
+
return !iterators.every((i) => i.iterator.done());
|
|
266
|
+
|
|
267
|
+
const combined = reverseRef.current
|
|
268
|
+
? [...unique.reverse(), ...prev]
|
|
269
|
+
: [...prev, ...unique];
|
|
270
|
+
updateAll(combined);
|
|
271
|
+
|
|
272
|
+
emptyResultsRef.current = iterators.every((i) => i.iterator.done());
|
|
273
|
+
return !emptyResultsRef.current;
|
|
389
274
|
} catch (e) {
|
|
390
275
|
if (!(e instanceof ClosedError)) throw e;
|
|
276
|
+
return false;
|
|
391
277
|
} finally {
|
|
392
278
|
setIsLoading(false);
|
|
393
|
-
loadingMoreRef.current = false;
|
|
394
279
|
}
|
|
395
280
|
};
|
|
396
281
|
|
|
397
|
-
/* ──────────────
|
|
282
|
+
/* ────────────── live-merge listeners ────────────── */
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (!options.onChange || options.onChange.merge === false) return;
|
|
285
|
+
|
|
286
|
+
const listeners = iteratorRefs.current.map(({ db, id: itId }) => {
|
|
287
|
+
const mergeFn =
|
|
288
|
+
typeof options.onChange?.merge === "function"
|
|
289
|
+
? options.onChange.merge
|
|
290
|
+
: (c: DocumentsChange<T, I>) => c;
|
|
291
|
+
|
|
292
|
+
const handler = async (e: CustomEvent<DocumentsChange<T, I>>) => {
|
|
293
|
+
log("Merge change", e.detail, "it", itId);
|
|
294
|
+
const filtered = await mergeFn(e.detail);
|
|
295
|
+
if (
|
|
296
|
+
!filtered ||
|
|
297
|
+
(!filtered.added.length && !filtered.removed.length)
|
|
298
|
+
)
|
|
299
|
+
return;
|
|
300
|
+
|
|
301
|
+
let merged: Item[];
|
|
302
|
+
if (options.onChange?.update) {
|
|
303
|
+
merged = await options.onChange.update(
|
|
304
|
+
allRef.current,
|
|
305
|
+
filtered
|
|
306
|
+
);
|
|
307
|
+
} else {
|
|
308
|
+
merged = await db.index.updateResults(
|
|
309
|
+
allRef.current as WithContext<RT>[],
|
|
310
|
+
filtered,
|
|
311
|
+
options.query || {},
|
|
312
|
+
options.resolve ?? true
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
updateAll(options.reverse ? merged.reverse() : merged);
|
|
316
|
+
};
|
|
317
|
+
db.events.addEventListener("change", handler);
|
|
318
|
+
return { db, handler };
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return () => {
|
|
322
|
+
listeners.forEach(({ db, handler }) =>
|
|
323
|
+
db.events.removeEventListener("change", handler)
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
327
|
+
}, [
|
|
328
|
+
iteratorRefs.current.map((r) => r.db.address).join("|"),
|
|
329
|
+
options.onChange,
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
/* ────────────── public API – unchanged from the caller's perspective ────────────── */
|
|
398
333
|
return {
|
|
399
334
|
items: all,
|
|
400
335
|
loadMore,
|
|
401
336
|
isLoading,
|
|
402
337
|
empty: () => emptyResultsRef.current,
|
|
403
|
-
id
|
|
338
|
+
id,
|
|
404
339
|
};
|
|
405
340
|
};
|