@peerbit/react 0.0.13 → 0.0.15
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 +4 -1
- package/lib/esm/index.js +4 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/lockstorage.d.ts +4 -2
- package/lib/esm/lockstorage.js +32 -19
- package/lib/esm/lockstorage.js.map +1 -1
- package/lib/esm/useCount.d.ts +11 -0
- package/lib/esm/useCount.js +40 -0
- package/lib/esm/useCount.js.map +1 -0
- package/lib/esm/useLocal.d.ts +18 -2
- package/lib/esm/useLocal.js +45 -11
- package/lib/esm/useLocal.js.map +1 -1
- package/lib/esm/useOnline.d.ts +10 -0
- package/lib/esm/useOnline.js +52 -0
- package/lib/esm/useOnline.js.map +1 -0
- package/lib/esm/usePeer.d.ts +24 -5
- package/lib/esm/usePeer.js +106 -71
- package/lib/esm/usePeer.js.map +1 -1
- package/lib/esm/useProgram.d.ts +7 -2
- package/lib/esm/useProgram.js +36 -12
- package/lib/esm/useProgram.js.map +1 -1
- package/lib/esm/utils.d.ts +7 -3
- package/lib/esm/utils.js +58 -9
- package/lib/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +9 -1
- package/src/lockstorage.ts +47 -23
- package/src/useCount.tsx +60 -0
- package/src/useLocal.tsx +83 -17
- package/src/useOnline.tsx +66 -0
- package/src/usePeer.tsx +149 -103
- package/src/useProgram.tsx +43 -24
- package/src/utils.ts +75 -20
package/src/lockstorage.ts
CHANGED
|
@@ -45,8 +45,13 @@ export class FastMutex {
|
|
|
45
45
|
|
|
46
46
|
lock(
|
|
47
47
|
key: string,
|
|
48
|
-
keepLocked?: () => boolean
|
|
49
|
-
|
|
48
|
+
keepLocked?: () => boolean,
|
|
49
|
+
options?: { replaceIfSameClient?: boolean }
|
|
50
|
+
): Promise<{
|
|
51
|
+
restartCount: number;
|
|
52
|
+
contentionCount: number;
|
|
53
|
+
locksLost: number;
|
|
54
|
+
}> {
|
|
50
55
|
debug(
|
|
51
56
|
'Attempting to acquire Lock on "%s" using FastMutex instance "%s"',
|
|
52
57
|
key,
|
|
@@ -54,25 +59,47 @@ export class FastMutex {
|
|
|
54
59
|
);
|
|
55
60
|
const x = this.xPrefix + key;
|
|
56
61
|
const y = this.yPrefix + key;
|
|
57
|
-
let acquireStart =
|
|
62
|
+
let acquireStart = Date.now();
|
|
58
63
|
return new Promise((resolve, reject) => {
|
|
59
|
-
// we need to differentiate between API calls to lock() and our internal
|
|
60
|
-
// recursive calls so that we can timeout based on the original lock() and
|
|
61
|
-
// not each subsequent call. Therefore, create a new function here within
|
|
62
|
-
// the promise closure that we use for subsequent calls:
|
|
63
64
|
let restartCount = 0;
|
|
64
65
|
let contentionCount = 0;
|
|
65
66
|
let locksLost = 0;
|
|
66
|
-
|
|
67
|
+
|
|
68
|
+
const acquireLock = (key: string) => {
|
|
69
|
+
// If the option is set and the same client already holds both keys,
|
|
70
|
+
// update the expiry and resolve immediately.
|
|
71
|
+
if (options?.replaceIfSameClient) {
|
|
72
|
+
const currentX = this.getItem(x);
|
|
73
|
+
const currentY = this.getItem(y);
|
|
74
|
+
if (
|
|
75
|
+
currentX === this.clientId &&
|
|
76
|
+
currentY === this.clientId
|
|
77
|
+
) {
|
|
78
|
+
// Update expiry so that the lock is effectively "replaced"
|
|
79
|
+
this.setItem(x, this.clientId, keepLocked);
|
|
80
|
+
this.setItem(y, this.clientId, keepLocked);
|
|
81
|
+
debug(
|
|
82
|
+
'FastMutex client "%s" replaced its own lock on "%s".',
|
|
83
|
+
this.clientId,
|
|
84
|
+
key
|
|
85
|
+
);
|
|
86
|
+
return resolve({
|
|
87
|
+
restartCount,
|
|
88
|
+
contentionCount,
|
|
89
|
+
locksLost,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for overall retries/timeouts.
|
|
67
95
|
if (
|
|
68
96
|
restartCount > 1000 ||
|
|
69
97
|
contentionCount > 1000 ||
|
|
70
98
|
locksLost > 1000
|
|
71
99
|
) {
|
|
72
|
-
reject("Failed to resolve lock");
|
|
100
|
+
return reject("Failed to resolve lock");
|
|
73
101
|
}
|
|
74
|
-
|
|
75
|
-
const elapsedTime = new Date().getTime() - acquireStart;
|
|
102
|
+
const elapsedTime = Date.now() - acquireStart;
|
|
76
103
|
if (elapsedTime >= this.timeout) {
|
|
77
104
|
debug(
|
|
78
105
|
'Lock on "%s" could not be acquired within %sms by FastMutex client "%s"',
|
|
@@ -87,43 +114,40 @@ export class FastMutex {
|
|
|
87
114
|
);
|
|
88
115
|
}
|
|
89
116
|
|
|
117
|
+
// First, set key X.
|
|
90
118
|
this.setItem(x, this.clientId, keepLocked);
|
|
91
119
|
|
|
92
|
-
// if
|
|
120
|
+
// Check if key Y exists (another client may be acquiring the lock)
|
|
93
121
|
let lsY = this.getItem(y);
|
|
94
122
|
if (lsY) {
|
|
95
123
|
debug("Lock exists on Y (%s), restarting...", lsY);
|
|
96
124
|
restartCount++;
|
|
97
|
-
setTimeout(() => acquireLock(key));
|
|
125
|
+
setTimeout(() => acquireLock(key), 10);
|
|
98
126
|
return;
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
//
|
|
129
|
+
// Request the inner lock by setting Y.
|
|
102
130
|
this.setItem(y, this.clientId, keepLocked);
|
|
103
131
|
|
|
104
|
-
// if
|
|
132
|
+
// Re-check X; if it was changed, we have contention.
|
|
105
133
|
let lsX = this.getItem(x);
|
|
106
134
|
if (lsX !== this.clientId) {
|
|
107
135
|
contentionCount++;
|
|
108
136
|
debug('Lock contention detected. X="%s"', lsX);
|
|
109
|
-
|
|
110
|
-
// Give enough time for critical section:
|
|
111
137
|
setTimeout(() => {
|
|
112
138
|
lsY = this.getItem(y);
|
|
113
139
|
if (lsY === this.clientId) {
|
|
114
|
-
// we have a lock
|
|
115
140
|
debug(
|
|
116
141
|
'FastMutex client "%s" won the lock contention on "%s"',
|
|
117
142
|
this.clientId,
|
|
118
143
|
key
|
|
119
144
|
);
|
|
120
145
|
resolve({
|
|
146
|
+
restartCount,
|
|
121
147
|
contentionCount,
|
|
122
148
|
locksLost,
|
|
123
|
-
restartCount,
|
|
124
149
|
});
|
|
125
150
|
} else {
|
|
126
|
-
// we lost the lock, restart the process again
|
|
127
151
|
restartCount++;
|
|
128
152
|
locksLost++;
|
|
129
153
|
debug(
|
|
@@ -132,19 +156,19 @@ export class FastMutex {
|
|
|
132
156
|
key,
|
|
133
157
|
lsY
|
|
134
158
|
);
|
|
135
|
-
setTimeout(() => acquireLock(key));
|
|
159
|
+
setTimeout(() => acquireLock(key), 10);
|
|
136
160
|
}
|
|
137
161
|
}, 50);
|
|
138
162
|
return;
|
|
139
163
|
}
|
|
140
164
|
|
|
141
|
-
//
|
|
165
|
+
// No contention: lock is acquired.
|
|
142
166
|
debug(
|
|
143
167
|
'FastMutex client "%s" acquired a lock on "%s" with no contention',
|
|
144
168
|
this.clientId,
|
|
145
169
|
key
|
|
146
170
|
);
|
|
147
|
-
resolve({ contentionCount, locksLost
|
|
171
|
+
resolve({ restartCount, contentionCount, locksLost });
|
|
148
172
|
};
|
|
149
173
|
|
|
150
174
|
acquireLock(key);
|
package/src/useCount.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ClosedError, Documents, WithContext } from "@peerbit/document";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
4
|
+
import { debounceLeadingTrailing } from "./utils";
|
|
5
|
+
|
|
6
|
+
type QueryOptons = {
|
|
7
|
+
query: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
export const useCount = <T extends Record<string, any>>(
|
|
11
|
+
db?: Documents<T, any, any>,
|
|
12
|
+
options?: {
|
|
13
|
+
debounce?: number;
|
|
14
|
+
debug?: boolean; // add debug option here
|
|
15
|
+
} & QueryOptons
|
|
16
|
+
) => {
|
|
17
|
+
const [count, setCount] = useState<number>(0);
|
|
18
|
+
const countRef = useRef<number>(0);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!db || db.closed) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const _l = async (args?: any) => {
|
|
26
|
+
try {
|
|
27
|
+
const count = await db.count({
|
|
28
|
+
query: options?.query,
|
|
29
|
+
approximate: true,
|
|
30
|
+
});
|
|
31
|
+
countRef.current = count;
|
|
32
|
+
setCount(count);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof ClosedError) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const debounced = debounceLeadingTrailing(
|
|
42
|
+
_l,
|
|
43
|
+
options?.debounce ?? 1000
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const handleChange = () => {
|
|
47
|
+
debounced();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
debounced();
|
|
51
|
+
db.events.addEventListener("change", handleChange);
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
db.events.removeEventListener("change", handleChange);
|
|
55
|
+
debounced.cancel();
|
|
56
|
+
};
|
|
57
|
+
}, [db?.closed ? undefined : db?.rootAddress, options?.id]);
|
|
58
|
+
|
|
59
|
+
return count;
|
|
60
|
+
};
|
package/src/useLocal.tsx
CHANGED
|
@@ -1,35 +1,101 @@
|
|
|
1
|
-
import { ClosedError, Documents,
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { ClosedError, Documents, WithContext } from "@peerbit/document";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
4
|
+
import { debounceLeadingTrailing } from "./utils";
|
|
5
|
+
|
|
6
|
+
type QueryLike = {
|
|
7
|
+
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
8
|
+
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
9
|
+
};
|
|
10
|
+
type QueryOptons = {
|
|
11
|
+
query: QueryLike;
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useLocal = <
|
|
16
|
+
T extends Record<string, any>,
|
|
17
|
+
I extends Record<string, any>,
|
|
18
|
+
R extends boolean | undefined = true,
|
|
19
|
+
RT = R extends false ? WithContext<I> : WithContext<T>
|
|
20
|
+
>(
|
|
21
|
+
db?: Documents<T, I>,
|
|
22
|
+
options?: {
|
|
23
|
+
resolve?: R;
|
|
24
|
+
transform?: (result: RT) => Promise<RT>;
|
|
25
|
+
onChanges?: (all: RT[]) => void;
|
|
26
|
+
debounce?: number;
|
|
27
|
+
debug?: boolean; // add debug option here
|
|
28
|
+
} & QueryOptons
|
|
5
29
|
) => {
|
|
6
|
-
const [all, setAll] = useState<
|
|
30
|
+
const [all, setAll] = useState<RT[]>([]);
|
|
31
|
+
const emptyResultsRef = useRef(false);
|
|
32
|
+
|
|
7
33
|
useEffect(() => {
|
|
8
34
|
if (!db || db.closed) {
|
|
9
35
|
return;
|
|
10
36
|
}
|
|
11
37
|
|
|
12
|
-
const
|
|
38
|
+
const _l = async (args?: any) => {
|
|
13
39
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
const iterator = db.index.iterate(options?.query ?? {}, {
|
|
41
|
+
local: true,
|
|
42
|
+
remote: false,
|
|
43
|
+
resolve: options?.resolve as any,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let results: RT[] = (await iterator.all()) as any;
|
|
47
|
+
|
|
48
|
+
if (options?.transform) {
|
|
49
|
+
results = await Promise.all(
|
|
50
|
+
results.map((x) => options.transform!(x))
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
emptyResultsRef.current = results.length === 0;
|
|
54
|
+
setAll(() => {
|
|
55
|
+
options?.onChanges?.(results);
|
|
56
|
+
return results;
|
|
57
|
+
});
|
|
20
58
|
} catch (error) {
|
|
21
59
|
if (error instanceof ClosedError) {
|
|
22
|
-
// ignore
|
|
23
60
|
return;
|
|
24
61
|
}
|
|
25
62
|
throw error;
|
|
26
63
|
}
|
|
27
64
|
};
|
|
28
65
|
|
|
29
|
-
|
|
30
|
-
|
|
66
|
+
const debounced = debounceLeadingTrailing(
|
|
67
|
+
_l,
|
|
68
|
+
options?.debounce ?? 1000
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
let ts = setTimeout(() => {
|
|
72
|
+
_l();
|
|
73
|
+
}, 3000);
|
|
74
|
+
|
|
75
|
+
const handleChange = () => {
|
|
76
|
+
if (emptyResultsRef.current) {
|
|
77
|
+
debounced.cancel();
|
|
78
|
+
_l();
|
|
79
|
+
} else {
|
|
80
|
+
debounced();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
debounced();
|
|
85
|
+
db.events.addEventListener("change", handleChange);
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
db.events.removeEventListener("change", handleChange);
|
|
89
|
+
debounced.cancel();
|
|
90
|
+
clearTimeout(ts);
|
|
91
|
+
};
|
|
92
|
+
}, [
|
|
93
|
+
db?.closed ? undefined : db?.rootAddress,
|
|
94
|
+
options?.id,
|
|
95
|
+
options?.resolve,
|
|
96
|
+
options?.onChanges,
|
|
97
|
+
options?.transform,
|
|
98
|
+
]);
|
|
31
99
|
|
|
32
|
-
return () => db.events.addEventListener("change", changeListener);
|
|
33
|
-
}, [db?.address, db?.closed]);
|
|
34
100
|
return all;
|
|
35
101
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Program, OpenOptions, ProgramEvents } from "@peerbit/program";
|
|
2
|
+
import { PublicSignKey } from "@peerbit/crypto";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
const addressOrDefined = <A, B extends ProgramEvents, P extends Program<A, B>>(
|
|
5
|
+
p?: P
|
|
6
|
+
) => {
|
|
7
|
+
try {
|
|
8
|
+
return p?.rootAddress;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
return !!p;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
type ExtractArgs<T> = T extends Program<infer Args> ? Args : never;
|
|
14
|
+
type ExtractEvents<T> = T extends Program<any, infer Events> ? Events : never;
|
|
15
|
+
|
|
16
|
+
export const useOnline = <
|
|
17
|
+
P extends Program<ExtractArgs<P>, ExtractEvents<P>> &
|
|
18
|
+
Program<any, ProgramEvents>
|
|
19
|
+
>(
|
|
20
|
+
program?: P,
|
|
21
|
+
options?: { id?: string }
|
|
22
|
+
) => {
|
|
23
|
+
const [peers, setPeers] = useState<PublicSignKey[]>([]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!program || program.closed) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let changeListener: () => void;
|
|
30
|
+
|
|
31
|
+
let closed = false;
|
|
32
|
+
const p = program;
|
|
33
|
+
changeListener = () => {
|
|
34
|
+
p.getReady()
|
|
35
|
+
.then((set) => {
|
|
36
|
+
setPeers([...set.values()]);
|
|
37
|
+
})
|
|
38
|
+
.catch((e) => {
|
|
39
|
+
console.error(e, closed);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
p.events.addEventListener("join", changeListener);
|
|
43
|
+
p.events.addEventListener("leave", changeListener);
|
|
44
|
+
p.getReady()
|
|
45
|
+
.then((set) => {
|
|
46
|
+
setPeers([...set.values()]);
|
|
47
|
+
})
|
|
48
|
+
.catch((e) => {
|
|
49
|
+
console.log("Error getReady()", {
|
|
50
|
+
closed,
|
|
51
|
+
pClosed: p.closed,
|
|
52
|
+
e,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// TODO AbortController?
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
closed = true;
|
|
59
|
+
p.events.removeEventListener("join", changeListener);
|
|
60
|
+
p.events.removeEventListener("leave", changeListener);
|
|
61
|
+
};
|
|
62
|
+
}, [options?.id, addressOrDefined(program)]);
|
|
63
|
+
return {
|
|
64
|
+
peers,
|
|
65
|
+
};
|
|
66
|
+
};
|