@peerbit/react 0.0.15 → 0.0.16
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/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/useLocal.d.ts +3 -1
- package/lib/esm/useLocal.js +8 -5
- package/lib/esm/useLocal.js.map +1 -1
- package/lib/esm/useLocalPaginated.d.ts +27 -0
- package/lib/esm/useLocalPaginated.js +135 -0
- package/lib/esm/useLocalPaginated.js.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/useLocal.tsx +15 -7
- package/src/useLocalPaginated.tsx +208 -0
package/lib/esm/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./utils.js";
|
|
|
3
3
|
export { FastMutex } from "./lockstorage.js";
|
|
4
4
|
export { useProgram } from "./useProgram.js";
|
|
5
5
|
export { useLocal } from "./useLocal.js";
|
|
6
|
+
export { useLocalPaginated } from "./useLocalPaginated.js";
|
|
6
7
|
export { useOnline } from "./useOnline.js";
|
|
7
8
|
export { useCount } from "./useCount.js";
|
|
8
9
|
export { debounceLeadingTrailing } from "./utils.js";
|
package/lib/esm/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./utils.js";
|
|
|
3
3
|
export { FastMutex } from "./lockstorage.js";
|
|
4
4
|
export { useProgram } from "./useProgram.js";
|
|
5
5
|
export { useLocal } from "./useLocal.js";
|
|
6
|
+
export { useLocalPaginated } from "./useLocalPaginated.js";
|
|
6
7
|
export { useOnline } from "./useOnline.js";
|
|
7
8
|
export { useCount } from "./useCount.js";
|
|
8
9
|
export { debounceLeadingTrailing } from "./utils.js";
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,WAAW,EACX,OAAO,EACP,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,WAAW,EACX,OAAO,EACP,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
|
package/lib/esm/useLocal.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const useLocal: <T extends Record<string, any>, I extends Record<
|
|
|
13
13
|
transform?: (result: RT) => Promise<RT>;
|
|
14
14
|
onChanges?: (all: RT[]) => void;
|
|
15
15
|
debounce?: number;
|
|
16
|
-
debug?: boolean
|
|
16
|
+
debug?: boolean | {
|
|
17
|
+
id: string;
|
|
18
|
+
};
|
|
17
19
|
} & QueryOptons) => RT[];
|
|
18
20
|
export {};
|
package/lib/esm/useLocal.js
CHANGED
|
@@ -5,7 +5,8 @@ export const useLocal = (db, options) => {
|
|
|
5
5
|
const [all, setAll] = useState([]);
|
|
6
6
|
const emptyResultsRef = useRef(false);
|
|
7
7
|
useEffect(() => {
|
|
8
|
-
if (!db || db.closed) {
|
|
8
|
+
if (!db || db.closed || options?.query === null) {
|
|
9
|
+
// null query means no query at all
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
12
|
const _l = async (args) => {
|
|
@@ -19,6 +20,12 @@ export const useLocal = (db, options) => {
|
|
|
19
20
|
if (options?.transform) {
|
|
20
21
|
results = await Promise.all(results.map((x) => options.transform(x)));
|
|
21
22
|
}
|
|
23
|
+
if (options?.debug) {
|
|
24
|
+
let dbgId = typeof options.debug === "boolean"
|
|
25
|
+
? undefined
|
|
26
|
+
: options.debug.id;
|
|
27
|
+
console.log(dbgId ? "fetched " + dbgId : "fetched", results, "query", options?.query);
|
|
28
|
+
}
|
|
22
29
|
emptyResultsRef.current = results.length === 0;
|
|
23
30
|
setAll(() => {
|
|
24
31
|
options?.onChanges?.(results);
|
|
@@ -33,9 +40,6 @@ export const useLocal = (db, options) => {
|
|
|
33
40
|
}
|
|
34
41
|
};
|
|
35
42
|
const debounced = debounceLeadingTrailing(_l, options?.debounce ?? 1000);
|
|
36
|
-
let ts = setTimeout(() => {
|
|
37
|
-
_l();
|
|
38
|
-
}, 3000);
|
|
39
43
|
const handleChange = () => {
|
|
40
44
|
if (emptyResultsRef.current) {
|
|
41
45
|
debounced.cancel();
|
|
@@ -50,7 +54,6 @@ export const useLocal = (db, options) => {
|
|
|
50
54
|
return () => {
|
|
51
55
|
db.events.removeEventListener("change", handleChange);
|
|
52
56
|
debounced.cancel();
|
|
53
|
-
clearTimeout(ts);
|
|
54
57
|
};
|
|
55
58
|
}, [
|
|
56
59
|
db?.closed ? undefined : db?.rootAddress,
|
package/lib/esm/useLocal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLocal.js","sourceRoot":"","sources":["../../src/useLocal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAWlD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAMpB,EAAoB,EACpB,OAMe,EACjB,EAAE;IACA,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAO,EAAE,CAAC,CAAC;IACzC,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEtC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"useLocal.js","sourceRoot":"","sources":["../../src/useLocal.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Documents, WithContext } from "@peerbit/document";
|
|
2
|
+
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
3
|
+
type QueryLike = {
|
|
4
|
+
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
5
|
+
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
6
|
+
};
|
|
7
|
+
type QueryOptions = {
|
|
8
|
+
query: QueryLike;
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const useLocalPaginated: <T extends Record<string, any>, I extends Record<string, any>, R extends boolean | undefined = true, RT = R extends false ? WithContext<I> : WithContext<T>>(db?: Documents<T, I>, options?: {
|
|
12
|
+
resolve?: R;
|
|
13
|
+
transform?: (result: WithContext<RT>) => Promise<WithContext<RT>>;
|
|
14
|
+
onChanges?: (all: RT[]) => void;
|
|
15
|
+
debounce?: number;
|
|
16
|
+
debug?: boolean | {
|
|
17
|
+
id: string;
|
|
18
|
+
};
|
|
19
|
+
reverse?: boolean;
|
|
20
|
+
batchSize?: number;
|
|
21
|
+
} & QueryOptions) => {
|
|
22
|
+
items: WithContext<RT>[];
|
|
23
|
+
loadMore: () => Promise<void>;
|
|
24
|
+
isLoading: boolean;
|
|
25
|
+
empty: boolean;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { ClosedError, } from "@peerbit/document";
|
|
3
|
+
export const useLocalPaginated = (db, options) => {
|
|
4
|
+
const [all, setAll] = useState([]);
|
|
5
|
+
const allRef = useRef([]);
|
|
6
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7
|
+
const iteratorRef = useRef(null);
|
|
8
|
+
const emptyResultsRef = useRef(false);
|
|
9
|
+
const updateAll = (combined) => {
|
|
10
|
+
if (options?.onChanges) {
|
|
11
|
+
options?.onChanges?.(combined);
|
|
12
|
+
}
|
|
13
|
+
if (options?.debug) {
|
|
14
|
+
let dbgId = typeof options.debug === "boolean"
|
|
15
|
+
? undefined
|
|
16
|
+
: options.debug.id;
|
|
17
|
+
console.log("Loading more items, new combined length", dbgId, combined.length);
|
|
18
|
+
}
|
|
19
|
+
const dedub = new Set();
|
|
20
|
+
for (const item of combined) {
|
|
21
|
+
if ("idString" in item) {
|
|
22
|
+
if (dedub.has(item.idString)) {
|
|
23
|
+
throw new Error("Duplicate item found in iterator");
|
|
24
|
+
}
|
|
25
|
+
dedub.add(item.idString);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
allRef.current = combined;
|
|
29
|
+
setAll(combined);
|
|
30
|
+
};
|
|
31
|
+
// Initialize the iterator only once or when query changes
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!db || db.closed || options?.query === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const initIterator = () => {
|
|
37
|
+
try {
|
|
38
|
+
// Initialize the iterator and load initial batch.
|
|
39
|
+
emptyResultsRef.current = false;
|
|
40
|
+
iteratorRef.current?.close();
|
|
41
|
+
iteratorRef.current = db.index.iterate(options?.query ?? {}, {
|
|
42
|
+
local: true,
|
|
43
|
+
remote: false,
|
|
44
|
+
resolve: options?.resolve,
|
|
45
|
+
}); // TODO types
|
|
46
|
+
if (options?.debug) {
|
|
47
|
+
let dbgId = typeof options.debug === "boolean"
|
|
48
|
+
? undefined
|
|
49
|
+
: options.debug.id;
|
|
50
|
+
console.log("Create new iterator", dbgId);
|
|
51
|
+
}
|
|
52
|
+
loadMore(); // initial load
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error("Error initializing iterator", error);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
// Reset state when the db or query changes.
|
|
59
|
+
console.log("RESET FROM", all.length);
|
|
60
|
+
setAll([]);
|
|
61
|
+
allRef.current = [];
|
|
62
|
+
initIterator();
|
|
63
|
+
const handleChange = async (e) => {
|
|
64
|
+
// while we are iterating, we might get new documents.. so this method inserts them where they should be
|
|
65
|
+
let merged = await db.index.updateResults(allRef.current, e.detail, options?.query || {}, options?.resolve ?? true);
|
|
66
|
+
console.log("merge result", "change: " +
|
|
67
|
+
(merged === allRef.current &&
|
|
68
|
+
merged.length &&
|
|
69
|
+
allRef.current.length === 0), merged, all, e.detail, allRef.current);
|
|
70
|
+
if (merged === allRef.current &&
|
|
71
|
+
merged.length &&
|
|
72
|
+
allRef.current.length === 0) {
|
|
73
|
+
// no change
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
updateAll(options?.reverse ? merged.reverse() : merged);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
db.events.addEventListener("change", handleChange);
|
|
80
|
+
return () => {
|
|
81
|
+
db.events.removeEventListener("change", handleChange);
|
|
82
|
+
iteratorRef.current?.close();
|
|
83
|
+
};
|
|
84
|
+
}, [
|
|
85
|
+
db?.closed ? undefined : db?.rootAddress,
|
|
86
|
+
options?.id,
|
|
87
|
+
options?.query,
|
|
88
|
+
options?.resolve,
|
|
89
|
+
]);
|
|
90
|
+
// Define the loadMore function
|
|
91
|
+
const loadMore = async () => {
|
|
92
|
+
if (!iteratorRef.current || isLoading || emptyResultsRef.current)
|
|
93
|
+
return;
|
|
94
|
+
setIsLoading(true);
|
|
95
|
+
try {
|
|
96
|
+
// Fetch next batchSize number of items:
|
|
97
|
+
let refBefore = iteratorRef.current;
|
|
98
|
+
let newItems = await iteratorRef.current.next(options?.batchSize ?? 10);
|
|
99
|
+
if (options?.transform) {
|
|
100
|
+
newItems = await Promise.all(newItems.map((item) => options.transform(item)));
|
|
101
|
+
}
|
|
102
|
+
if (iteratorRef.current !== refBefore) {
|
|
103
|
+
// If the iterator has changed, we should not update the state
|
|
104
|
+
// This can happen if the iterator was closed and a new one was created
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
emptyResultsRef.current = newItems.length === 0;
|
|
108
|
+
if (newItems.length > 0) {
|
|
109
|
+
let prev = allRef.current;
|
|
110
|
+
let prevHash = new Set(prev.map((x) => x.__context.head));
|
|
111
|
+
let newItemsNoHash = newItems.filter((x) => !prevHash.has(x.__context.head));
|
|
112
|
+
if (newItemsNoHash.length === 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const combined = options?.reverse
|
|
116
|
+
? [...newItemsNoHash.reverse(), ...prev]
|
|
117
|
+
: [...prev, ...newItemsNoHash];
|
|
118
|
+
updateAll(combined);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error instanceof ClosedError) {
|
|
123
|
+
// Handle closed database gracefully
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
setIsLoading(false);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
return { items: all, loadMore, isLoading, empty: emptyResultsRef.current };
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=useLocalPaginated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLocalPaginated.js","sourceRoot":"","sources":["../../src/useLocalPaginated.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EACH,WAAW,GAOd,MAAM,mBAAmB,CAAC;AAS3B,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAM7B,EAAoB,EACpB,OAQgB,EAClB,EAAE;IACA,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAoB,EAAE,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAoB,EAAE,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,MAAM,CAA0C,IAAI,CAAC,CAAC;IAC1E,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEtC,MAAM,SAAS,GAAG,CAAC,QAA2B,EAAE,EAAE;QAC9C,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;YACrB,OAAO,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;QAED,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACjB,IAAI,KAAK,GACL,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS;gBAC9B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CACP,yCAAyC,EACzC,KAAK,EACL,QAAQ,CAAC,MAAM,CAClB,CAAC;QACN,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;gBACrB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAkB,CAAC,EAAE,CAAC;oBACrC,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;gBACxD,CAAC;gBACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAkB,CAAC,CAAC;YACvC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAC;QAE1B,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,IAAI,OAAO,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;YAC9C,OAAO;QACX,CAAC;QACD,MAAM,YAAY,GAAG,GAAG,EAAE;YACtB,IAAI,CAAC;gBACD,kDAAkD;gBAElD,eAAe,CAAC,OAAO,GAAG,KAAK,CAAC;gBAChC,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;gBAC7B,WAAW,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE;oBACzD,KAAK,EAAE,IAAI;oBACX,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE,OAAO,EAAE,OAAc;iBACnC,CAA4C,CAAC,CAAC,aAAa;gBAE5D,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;oBACjB,IAAI,KAAK,GACL,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS;wBAC9B,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;gBAC9C,CAAC;gBAED,QAAQ,EAAE,CAAC,CAAC,eAAe;YAC/B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;YACxD,CAAC;QACL,CAAC,CAAC;QAEF,4CAA4C;QAE5C,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,CAAC;QACX,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;QAEpB,YAAY,EAAE,CAAC;QAEf,MAAM,YAAY,GAAG,KAAK,EAAE,CAAkC,EAAE,EAAE;YAC9D,wGAAwG;YACxG,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,CACrC,MAAM,CAAC,OAAO,EACd,CAAC,CAAC,MAAM,EACR,OAAO,EAAE,KAAK,IAAI,EAAE,EACpB,OAAO,EAAE,OAAO,IAAI,IAAI,CAC3B,CAAC;YACF,OAAO,CAAC,GAAG,CACP,cAAc,EACd,UAAU;gBACN,CAAC,MAAM,KAAK,MAAM,CAAC,OAAO;oBACtB,MAAM,CAAC,MAAM;oBACb,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,EACpC,MAAM,EACN,GAAG,EACH,CAAC,CAAC,MAAM,EACR,MAAM,CAAC,OAAO,CACjB,CAAC;YACF,IACI,MAAM,KAAK,MAAM,CAAC,OAAO;gBACzB,MAAM,CAAC,MAAM;gBACb,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAC7B,CAAC;gBACC,YAAY;YAChB,CAAC;iBAAM,CAAC;gBACJ,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC5D,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACnD,OAAO,GAAG,EAAE;YACR,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;YACtD,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC,CAAC;IACN,CAAC,EAAE;QACC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW;QACxC,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,OAAO;KACnB,CAAC,CAAC;IAEH,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QACxB,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,SAAS,IAAI,eAAe,CAAC,OAAO;YAC5D,OAAO;QAEX,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC;YACD,wCAAwC;YACxC,IAAI,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC;YAEpC,IAAI,QAAQ,GAAsB,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAC5D,OAAO,EAAE,SAAS,IAAI,EAAE,CAC3B,CAAC;YAEF,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;gBACrB,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CACxB,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,SAAU,CAAC,IAAI,CAAC,CAAC,CACnD,CAAC;YACN,CAAC;YAED,IAAI,WAAW,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACpC,8DAA8D;gBAC9D,uEAAuE;gBACvE,OAAO;YACX,CAAC;YAED,eAAe,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;YAEhD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC;gBAC1B,IAAI,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1D,IAAI,cAAc,GAAG,QAAQ,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CACzC,CAAC;gBACF,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC9B,OAAO;gBACX,CAAC;gBACD,MAAM,QAAQ,GAAG,OAAO,EAAE,OAAO;oBAC7B,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC;oBACxC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,cAAc,CAAC,CAAC;gBACnC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;gBAC/B,oCAAoC;YACxC,CAAC;iBAAM,CAAC;gBACJ,MAAM,KAAK,CAAC;YAChB,CAAC;QACL,CAAC;gBAAS,CAAC;YACP,YAAY,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,CAAC;AAC/E,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
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": "1b0737b523b88552b08edd24a7a967a61b4af163"
|
|
74
74
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./utils.js";
|
|
|
8
8
|
export { FastMutex } from "./lockstorage.js";
|
|
9
9
|
export { useProgram } from "./useProgram.js";
|
|
10
10
|
export { useLocal } from "./useLocal.js";
|
|
11
|
+
export { useLocalPaginated } from "./useLocalPaginated.js";
|
|
11
12
|
export { useOnline } from "./useOnline.js";
|
|
12
13
|
export { useCount } from "./useCount.js";
|
|
13
14
|
export { debounceLeadingTrailing } from "./utils.js";
|
package/src/useLocal.tsx
CHANGED
|
@@ -24,14 +24,15 @@ export const useLocal = <
|
|
|
24
24
|
transform?: (result: RT) => Promise<RT>;
|
|
25
25
|
onChanges?: (all: RT[]) => void;
|
|
26
26
|
debounce?: number;
|
|
27
|
-
debug?: boolean
|
|
27
|
+
debug?: boolean | { id: string };
|
|
28
28
|
} & QueryOptons
|
|
29
29
|
) => {
|
|
30
30
|
const [all, setAll] = useState<RT[]>([]);
|
|
31
31
|
const emptyResultsRef = useRef(false);
|
|
32
32
|
|
|
33
33
|
useEffect(() => {
|
|
34
|
-
if (!db || db.closed) {
|
|
34
|
+
if (!db || db.closed || options?.query === null) {
|
|
35
|
+
// null query means no query at all
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -50,6 +51,18 @@ export const useLocal = <
|
|
|
50
51
|
results.map((x) => options.transform!(x))
|
|
51
52
|
);
|
|
52
53
|
}
|
|
54
|
+
if (options?.debug) {
|
|
55
|
+
let dbgId =
|
|
56
|
+
typeof options.debug === "boolean"
|
|
57
|
+
? undefined
|
|
58
|
+
: options.debug.id;
|
|
59
|
+
console.log(
|
|
60
|
+
dbgId ? "fetched " + dbgId : "fetched",
|
|
61
|
+
results,
|
|
62
|
+
"query",
|
|
63
|
+
options?.query
|
|
64
|
+
);
|
|
65
|
+
}
|
|
53
66
|
emptyResultsRef.current = results.length === 0;
|
|
54
67
|
setAll(() => {
|
|
55
68
|
options?.onChanges?.(results);
|
|
@@ -68,10 +81,6 @@ export const useLocal = <
|
|
|
68
81
|
options?.debounce ?? 1000
|
|
69
82
|
);
|
|
70
83
|
|
|
71
|
-
let ts = setTimeout(() => {
|
|
72
|
-
_l();
|
|
73
|
-
}, 3000);
|
|
74
|
-
|
|
75
84
|
const handleChange = () => {
|
|
76
85
|
if (emptyResultsRef.current) {
|
|
77
86
|
debounced.cancel();
|
|
@@ -87,7 +96,6 @@ export const useLocal = <
|
|
|
87
96
|
return () => {
|
|
88
97
|
db.events.removeEventListener("change", handleChange);
|
|
89
98
|
debounced.cancel();
|
|
90
|
-
clearTimeout(ts);
|
|
91
99
|
};
|
|
92
100
|
}, [
|
|
93
101
|
db?.closed ? undefined : db?.rootAddress,
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ClosedError,
|
|
4
|
+
Documents,
|
|
5
|
+
DocumentsChange,
|
|
6
|
+
ResultsIterator,
|
|
7
|
+
SearchRequest,
|
|
8
|
+
SearchRequestIndexed,
|
|
9
|
+
WithContext,
|
|
10
|
+
} from "@peerbit/document";
|
|
11
|
+
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
12
|
+
|
|
13
|
+
type QueryLike = {
|
|
14
|
+
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
15
|
+
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
16
|
+
};
|
|
17
|
+
type QueryOptions = { query: QueryLike; id: string };
|
|
18
|
+
|
|
19
|
+
export const useLocalPaginated = <
|
|
20
|
+
T extends Record<string, any>,
|
|
21
|
+
I extends Record<string, any>,
|
|
22
|
+
R extends boolean | undefined = true,
|
|
23
|
+
RT = R extends false ? WithContext<I> : WithContext<T>
|
|
24
|
+
>(
|
|
25
|
+
db?: Documents<T, I>,
|
|
26
|
+
options?: {
|
|
27
|
+
resolve?: R;
|
|
28
|
+
transform?: (result: WithContext<RT>) => Promise<WithContext<RT>>;
|
|
29
|
+
onChanges?: (all: RT[]) => void;
|
|
30
|
+
debounce?: number;
|
|
31
|
+
debug?: boolean | { id: string };
|
|
32
|
+
reverse?: boolean;
|
|
33
|
+
batchSize?: number; // You can set a default batch size here
|
|
34
|
+
} & QueryOptions
|
|
35
|
+
) => {
|
|
36
|
+
const [all, setAll] = useState<WithContext<RT>[]>([]);
|
|
37
|
+
const allRef = useRef<WithContext<RT>[]>([]);
|
|
38
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
39
|
+
const iteratorRef = useRef<ResultsIterator<WithContext<RT>> | null>(null);
|
|
40
|
+
const emptyResultsRef = useRef(false);
|
|
41
|
+
|
|
42
|
+
const updateAll = (combined: WithContext<RT>[]) => {
|
|
43
|
+
if (options?.onChanges) {
|
|
44
|
+
options?.onChanges?.(combined);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (options?.debug) {
|
|
48
|
+
let dbgId =
|
|
49
|
+
typeof options.debug === "boolean"
|
|
50
|
+
? undefined
|
|
51
|
+
: options.debug.id;
|
|
52
|
+
console.log(
|
|
53
|
+
"Loading more items, new combined length",
|
|
54
|
+
dbgId,
|
|
55
|
+
combined.length
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const dedub = new Set<string>();
|
|
60
|
+
for (const item of combined) {
|
|
61
|
+
if ("idString" in item) {
|
|
62
|
+
if (dedub.has(item.idString as string)) {
|
|
63
|
+
throw new Error("Duplicate item found in iterator");
|
|
64
|
+
}
|
|
65
|
+
dedub.add(item.idString as string);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
allRef.current = combined;
|
|
69
|
+
|
|
70
|
+
setAll(combined);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Initialize the iterator only once or when query changes
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!db || db.closed || options?.query === null) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const initIterator = () => {
|
|
79
|
+
try {
|
|
80
|
+
// Initialize the iterator and load initial batch.
|
|
81
|
+
|
|
82
|
+
emptyResultsRef.current = false;
|
|
83
|
+
iteratorRef.current?.close();
|
|
84
|
+
iteratorRef.current = db.index.iterate(options?.query ?? {}, {
|
|
85
|
+
local: true,
|
|
86
|
+
remote: false,
|
|
87
|
+
resolve: options?.resolve as any,
|
|
88
|
+
}) as any as ResultsIterator<WithContext<RT>>; // TODO types
|
|
89
|
+
|
|
90
|
+
if (options?.debug) {
|
|
91
|
+
let dbgId =
|
|
92
|
+
typeof options.debug === "boolean"
|
|
93
|
+
? undefined
|
|
94
|
+
: options.debug.id;
|
|
95
|
+
console.log("Create new iterator", dbgId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
loadMore(); // initial load
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("Error initializing iterator", error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Reset state when the db or query changes.
|
|
105
|
+
|
|
106
|
+
console.log("RESET FROM", all.length);
|
|
107
|
+
setAll([]);
|
|
108
|
+
allRef.current = [];
|
|
109
|
+
|
|
110
|
+
initIterator();
|
|
111
|
+
|
|
112
|
+
const handleChange = async (e: CustomEvent<DocumentsChange<T>>) => {
|
|
113
|
+
// while we are iterating, we might get new documents.. so this method inserts them where they should be
|
|
114
|
+
let merged = await db.index.updateResults(
|
|
115
|
+
allRef.current,
|
|
116
|
+
e.detail,
|
|
117
|
+
options?.query || {},
|
|
118
|
+
options?.resolve ?? true
|
|
119
|
+
);
|
|
120
|
+
console.log(
|
|
121
|
+
"merge result",
|
|
122
|
+
"change: " +
|
|
123
|
+
(merged === allRef.current &&
|
|
124
|
+
merged.length &&
|
|
125
|
+
allRef.current.length === 0),
|
|
126
|
+
merged,
|
|
127
|
+
all,
|
|
128
|
+
e.detail,
|
|
129
|
+
allRef.current
|
|
130
|
+
);
|
|
131
|
+
if (
|
|
132
|
+
merged === allRef.current &&
|
|
133
|
+
merged.length &&
|
|
134
|
+
allRef.current.length === 0
|
|
135
|
+
) {
|
|
136
|
+
// no change
|
|
137
|
+
} else {
|
|
138
|
+
updateAll(options?.reverse ? merged.reverse() : merged);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
db.events.addEventListener("change", handleChange);
|
|
143
|
+
return () => {
|
|
144
|
+
db.events.removeEventListener("change", handleChange);
|
|
145
|
+
iteratorRef.current?.close();
|
|
146
|
+
};
|
|
147
|
+
}, [
|
|
148
|
+
db?.closed ? undefined : db?.rootAddress,
|
|
149
|
+
options?.id,
|
|
150
|
+
options?.query,
|
|
151
|
+
options?.resolve,
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
// Define the loadMore function
|
|
155
|
+
const loadMore = async () => {
|
|
156
|
+
if (!iteratorRef.current || isLoading || emptyResultsRef.current)
|
|
157
|
+
return;
|
|
158
|
+
|
|
159
|
+
setIsLoading(true);
|
|
160
|
+
try {
|
|
161
|
+
// Fetch next batchSize number of items:
|
|
162
|
+
let refBefore = iteratorRef.current;
|
|
163
|
+
|
|
164
|
+
let newItems: WithContext<RT>[] = await iteratorRef.current.next(
|
|
165
|
+
options?.batchSize ?? 10
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (options?.transform) {
|
|
169
|
+
newItems = await Promise.all(
|
|
170
|
+
newItems.map((item) => options.transform!(item))
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (iteratorRef.current !== refBefore) {
|
|
175
|
+
// If the iterator has changed, we should not update the state
|
|
176
|
+
// This can happen if the iterator was closed and a new one was created
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
emptyResultsRef.current = newItems.length === 0;
|
|
181
|
+
|
|
182
|
+
if (newItems.length > 0) {
|
|
183
|
+
let prev = allRef.current;
|
|
184
|
+
let prevHash = new Set(prev.map((x) => x.__context.head));
|
|
185
|
+
let newItemsNoHash = newItems.filter(
|
|
186
|
+
(x) => !prevHash.has(x.__context.head)
|
|
187
|
+
);
|
|
188
|
+
if (newItemsNoHash.length === 0) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const combined = options?.reverse
|
|
192
|
+
? [...newItemsNoHash.reverse(), ...prev]
|
|
193
|
+
: [...prev, ...newItemsNoHash];
|
|
194
|
+
updateAll(combined);
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error instanceof ClosedError) {
|
|
198
|
+
// Handle closed database gracefully
|
|
199
|
+
} else {
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
setIsLoading(false);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return { items: all, loadMore, isLoading, empty: emptyResultsRef.current };
|
|
208
|
+
};
|