@pluv/react 0.38.13 → 0.39.0
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +54 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +209 -55
- package/dist/index.mjs +204 -47
- package/eslint.config.mjs +4 -0
- package/package.json +9 -9
- package/src/createBundle.tsx +63 -15
- package/src/internal/hooks/index.ts +2 -0
- package/src/internal/hooks/useAsyncQueue.ts +18 -0
- package/src/internal/hooks/useDeepAsyncMemo.ts +120 -0
- package/src/internal/index.ts +1 -1
- package/.eslintrc.js +0 -4
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,136 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __async = (__this, __arguments, generator) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
var fulfilled = (value) => {
|
|
23
|
+
try {
|
|
24
|
+
step(generator.next(value));
|
|
25
|
+
} catch (e) {
|
|
26
|
+
reject(e);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var rejected = (value) => {
|
|
30
|
+
try {
|
|
31
|
+
step(generator.throw(value));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
reject(e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
37
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
1
41
|
// src/createBundle.tsx
|
|
2
42
|
import { MockedRoom } from "@pluv/client";
|
|
43
|
+
import fastDeepEqual2 from "fast-deep-equal";
|
|
44
|
+
import { createContext, memo, useCallback as useCallback4, useContext, useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
|
|
45
|
+
|
|
46
|
+
// src/internal/hooks/useAsyncQueue.ts
|
|
47
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
48
|
+
var useAsyncQueue = () => {
|
|
49
|
+
const queueRef = useRef(Promise.resolve(void 0));
|
|
50
|
+
const push = useCallback((promise) => __async(void 0, null, function* () {
|
|
51
|
+
queueRef.current = queueRef.current.then(() => __async(void 0, null, function* () {
|
|
52
|
+
return yield promise;
|
|
53
|
+
}));
|
|
54
|
+
return yield queueRef.current;
|
|
55
|
+
}), []);
|
|
56
|
+
return useMemo(() => ({ push }), [push]);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/internal/hooks/useDeepAsyncMemo.ts
|
|
60
|
+
import { useEffect, useRef as useRef2, useState } from "react";
|
|
3
61
|
import fastDeepEqual from "fast-deep-equal";
|
|
4
|
-
|
|
62
|
+
var useDeepAsyncMemo = (factory, isEqual = fastDeepEqual) => {
|
|
63
|
+
const [state, setState] = useState({
|
|
64
|
+
isInitialized: false,
|
|
65
|
+
isLoading: true,
|
|
66
|
+
value: void 0,
|
|
67
|
+
error: null
|
|
68
|
+
});
|
|
69
|
+
const lastValueRef = useRef2(void 0);
|
|
70
|
+
const hasValueRef = useRef2(false);
|
|
71
|
+
const inflightRef = useRef2(null);
|
|
72
|
+
const isMountedRef = useRef2(true);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
isMountedRef.current = true;
|
|
75
|
+
const promise = factory();
|
|
76
|
+
inflightRef.current = promise;
|
|
77
|
+
setState((prev) => {
|
|
78
|
+
return __spreadProps(__spreadValues({}, prev), {
|
|
79
|
+
isLoading: true,
|
|
80
|
+
error: null
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
promise.then((result) => {
|
|
84
|
+
if (!isMountedRef.current || inflightRef.current !== promise) return;
|
|
85
|
+
const isSame = hasValueRef.current && isEqual(lastValueRef.current, result);
|
|
86
|
+
if (isSame) {
|
|
87
|
+
setState((prev) => {
|
|
88
|
+
return __spreadProps(__spreadValues({}, prev), {
|
|
89
|
+
isInitialized: true,
|
|
90
|
+
isLoading: false
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
lastValueRef.current = result;
|
|
96
|
+
hasValueRef.current = true;
|
|
97
|
+
setState({
|
|
98
|
+
isInitialized: true,
|
|
99
|
+
isLoading: false,
|
|
100
|
+
value: result,
|
|
101
|
+
error: null
|
|
102
|
+
});
|
|
103
|
+
}).catch((err) => {
|
|
104
|
+
if (!isMountedRef.current || inflightRef.current !== promise) return;
|
|
105
|
+
setState({
|
|
106
|
+
isInitialized: hasValueRef.current,
|
|
107
|
+
isLoading: false,
|
|
108
|
+
value: void 0,
|
|
109
|
+
error: err instanceof Error ? err : new Error("Unknown error")
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return () => {
|
|
113
|
+
isMountedRef.current = false;
|
|
114
|
+
};
|
|
115
|
+
}, [
|
|
116
|
+
/**
|
|
117
|
+
* !HACK
|
|
118
|
+
* @description We're only going to use the `isEqual` function to determine if this hook
|
|
119
|
+
* should re-run. This is basically our hook's version of a dependency array
|
|
120
|
+
* @date April 13, 2025
|
|
121
|
+
*/
|
|
122
|
+
// factory,
|
|
123
|
+
isEqual
|
|
124
|
+
]);
|
|
125
|
+
return state;
|
|
126
|
+
};
|
|
5
127
|
|
|
6
128
|
// src/internal/hooks/useNoSsr.ts
|
|
7
|
-
import { useCallback, useEffect, useState } from "react";
|
|
129
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
8
130
|
var useNoSsr = () => {
|
|
9
|
-
const [didMount, setDidMount] =
|
|
10
|
-
|
|
11
|
-
const noSsr =
|
|
131
|
+
const [didMount, setDidMount] = useState2(false);
|
|
132
|
+
useEffect2(() => setDidMount(typeof window !== "undefined"), []);
|
|
133
|
+
const noSsr = useCallback2(
|
|
12
134
|
(value, fallback = null) => {
|
|
13
135
|
return didMount ? typeof value !== "function" ? value : value() : fallback;
|
|
14
136
|
},
|
|
@@ -18,16 +140,16 @@ var useNoSsr = () => {
|
|
|
18
140
|
};
|
|
19
141
|
|
|
20
142
|
// src/internal/hooks/useRerender.ts
|
|
21
|
-
import { useCallback as
|
|
143
|
+
import { useCallback as useCallback3, useState as useState3 } from "react";
|
|
22
144
|
var useRerender = () => {
|
|
23
|
-
const [, setCount] =
|
|
24
|
-
return
|
|
145
|
+
const [, setCount] = useState3(0);
|
|
146
|
+
return useCallback3(() => {
|
|
25
147
|
setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
|
|
26
148
|
}, []);
|
|
27
149
|
};
|
|
28
150
|
|
|
29
151
|
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
30
|
-
import { useRef, useSyncExternalStore } from "react";
|
|
152
|
+
import { useRef as useRef3, useSyncExternalStore } from "react";
|
|
31
153
|
|
|
32
154
|
// src/internal/utils/identity.ts
|
|
33
155
|
var identity = (x) => x;
|
|
@@ -43,7 +165,7 @@ var shallowEqual = (a, b) => a === b;
|
|
|
43
165
|
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
44
166
|
var useSyncExternalStoreWithSelector = (subscribe, getClientSnapshot, getServerSnapshot, selector, isEqual) => {
|
|
45
167
|
const noSsr = useNoSsr();
|
|
46
|
-
const cacheRef =
|
|
168
|
+
const cacheRef = useRef3(null);
|
|
47
169
|
const _getServerSnapshot = getServerSnapshot ? () => {
|
|
48
170
|
const data = getServerSnapshot();
|
|
49
171
|
const snapshot = selector(data);
|
|
@@ -71,10 +193,10 @@ import { jsx } from "react/jsx-runtime";
|
|
|
71
193
|
var createBundle = (client, options = {}) => {
|
|
72
194
|
const PluvContext = createContext(null);
|
|
73
195
|
const PluvRoomContext = createContext(null);
|
|
74
|
-
const MockedRoomContext = createContext(
|
|
196
|
+
const MockedRoomContext = createContext(null);
|
|
75
197
|
const MockedRoomProvider = memo((props) => {
|
|
76
198
|
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
77
|
-
const [room] =
|
|
199
|
+
const [room] = useState4(() => {
|
|
78
200
|
var _a;
|
|
79
201
|
return new MockedRoom(_room, {
|
|
80
202
|
events,
|
|
@@ -82,14 +204,28 @@ var createBundle = (client, options = {}) => {
|
|
|
82
204
|
initialStorage: typeof initialStorage === "function" ? (_a = client._defs.initialStorage) == null ? void 0 : _a.getFactory(initialStorage) : client._defs.initialStorage
|
|
83
205
|
});
|
|
84
206
|
});
|
|
85
|
-
return /* @__PURE__ */ jsx(MockedRoomContext.Provider, { value:
|
|
207
|
+
return /* @__PURE__ */ jsx(MockedRoomContext.Provider, { value: room, children: /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: room, children }) });
|
|
86
208
|
});
|
|
87
209
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
88
210
|
const PluvRoomProvider = memo((props) => {
|
|
89
|
-
const {
|
|
211
|
+
const {
|
|
212
|
+
children,
|
|
213
|
+
connect = true,
|
|
214
|
+
debug,
|
|
215
|
+
initialPresence,
|
|
216
|
+
initialStorage,
|
|
217
|
+
metadata,
|
|
218
|
+
onAuthorizationFail,
|
|
219
|
+
room: _room
|
|
220
|
+
} = props;
|
|
221
|
+
const queue = useAsyncQueue();
|
|
90
222
|
const rerender = useRerender();
|
|
91
|
-
const
|
|
92
|
-
const
|
|
223
|
+
const mockedRoom = useContext(MockedRoomContext);
|
|
224
|
+
const resolvedMeta = useDeepAsyncMemo(() => __async(void 0, null, function* () {
|
|
225
|
+
const resolved = yield Promise.resolve(typeof metadata === "function" ? metadata() : metadata);
|
|
226
|
+
return !!room.metadata ? room.metadata.parse(metadata) : resolved;
|
|
227
|
+
}));
|
|
228
|
+
const [room] = useState4(() => {
|
|
93
229
|
var _a;
|
|
94
230
|
return client.createRoom(_room, {
|
|
95
231
|
addons: options.addons,
|
|
@@ -101,7 +237,7 @@ var createBundle = (client, options = {}) => {
|
|
|
101
237
|
router: options.router
|
|
102
238
|
});
|
|
103
239
|
});
|
|
104
|
-
|
|
240
|
+
useEffect3(() => {
|
|
105
241
|
const unsubscribe = room.subscribe("connection", () => {
|
|
106
242
|
rerender();
|
|
107
243
|
});
|
|
@@ -109,12 +245,33 @@ var createBundle = (client, options = {}) => {
|
|
|
109
245
|
unsubscribe();
|
|
110
246
|
};
|
|
111
247
|
}, [rerender, room]);
|
|
112
|
-
|
|
113
|
-
|
|
248
|
+
useEffect3(() => {
|
|
249
|
+
const leaveRoom = () => __async(void 0, null, function* () {
|
|
250
|
+
yield queue.push(
|
|
251
|
+
client.leave(room).catch((error) => {
|
|
252
|
+
console.error(error);
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
if (!connect) {
|
|
257
|
+
leaveRoom();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (!resolvedMeta.isInitialized) {
|
|
261
|
+
leaveRoom();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const resolved = resolvedMeta.value;
|
|
265
|
+
queue.push(
|
|
266
|
+
client.enter(room, ...[{ metadata: resolved }]).catch((error) => __async(void 0, null, function* () {
|
|
267
|
+
console.error(error);
|
|
268
|
+
yield leaveRoom();
|
|
269
|
+
}))
|
|
270
|
+
);
|
|
114
271
|
return () => {
|
|
115
|
-
|
|
272
|
+
leaveRoom();
|
|
116
273
|
};
|
|
117
|
-
}, [room]);
|
|
274
|
+
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
118
275
|
return /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
119
276
|
});
|
|
120
277
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
@@ -131,13 +288,13 @@ var createBundle = (client, options = {}) => {
|
|
|
131
288
|
};
|
|
132
289
|
const useBroadcast = () => {
|
|
133
290
|
const room = useRoom();
|
|
134
|
-
const broadcast =
|
|
291
|
+
const broadcast = useCallback4(
|
|
135
292
|
(event2, data) => {
|
|
136
293
|
room.broadcast(event2, data);
|
|
137
294
|
},
|
|
138
295
|
[room]
|
|
139
296
|
);
|
|
140
|
-
return
|
|
297
|
+
return useMemo2(() => {
|
|
141
298
|
return new Proxy(broadcast, {
|
|
142
299
|
get(fn, prop) {
|
|
143
300
|
return (data) => {
|
|
@@ -149,35 +306,35 @@ var createBundle = (client, options = {}) => {
|
|
|
149
306
|
};
|
|
150
307
|
const useCanRedo = () => {
|
|
151
308
|
const room = useRoom();
|
|
152
|
-
const subscribe =
|
|
153
|
-
const getSnapshot =
|
|
309
|
+
const subscribe = useCallback4((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
310
|
+
const getSnapshot = useCallback4(() => room.canRedo(), [room]);
|
|
154
311
|
const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
155
312
|
return canRedo;
|
|
156
313
|
};
|
|
157
314
|
const useCanUndo = () => {
|
|
158
315
|
const room = useRoom();
|
|
159
|
-
const subscribe =
|
|
160
|
-
const getSnapshot =
|
|
316
|
+
const subscribe = useCallback4((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
317
|
+
const getSnapshot = useCallback4(() => room.canUndo(), [room]);
|
|
161
318
|
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
162
319
|
return canUndo;
|
|
163
320
|
};
|
|
164
321
|
const useConnection = (selector = identity, options2) => {
|
|
165
322
|
var _a;
|
|
166
323
|
const room = useRoom();
|
|
167
|
-
const subscribe =
|
|
324
|
+
const subscribe = useCallback4(
|
|
168
325
|
(onStoreChange) => {
|
|
169
326
|
return room.subscribe("connection", onStoreChange);
|
|
170
327
|
},
|
|
171
328
|
[room]
|
|
172
329
|
);
|
|
173
330
|
const getSnapshot = room.getConnection;
|
|
174
|
-
const _selector =
|
|
331
|
+
const _selector = useCallback4((snapshot) => selector(snapshot), [selector]);
|
|
175
332
|
return useSyncExternalStoreWithSelector(
|
|
176
333
|
subscribe,
|
|
177
334
|
getSnapshot,
|
|
178
335
|
getSnapshot,
|
|
179
336
|
_selector,
|
|
180
|
-
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a :
|
|
337
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual2
|
|
181
338
|
);
|
|
182
339
|
};
|
|
183
340
|
const useDoc = () => {
|
|
@@ -186,7 +343,7 @@ var createBundle = (client, options = {}) => {
|
|
|
186
343
|
};
|
|
187
344
|
const useEvent = (type, callback) => {
|
|
188
345
|
const room = useRoom();
|
|
189
|
-
|
|
346
|
+
useEffect3(() => {
|
|
190
347
|
const unsubscribe = room.event(type, callback);
|
|
191
348
|
return () => {
|
|
192
349
|
unsubscribe();
|
|
@@ -207,34 +364,34 @@ var createBundle = (client, options = {}) => {
|
|
|
207
364
|
const useMyPresence = (selector = identity, options2) => {
|
|
208
365
|
var _a;
|
|
209
366
|
const room = useRoom();
|
|
210
|
-
const subscribe =
|
|
367
|
+
const subscribe = useCallback4(
|
|
211
368
|
(onStoreChange) => {
|
|
212
369
|
return room.subscribe("my-presence", onStoreChange);
|
|
213
370
|
},
|
|
214
371
|
[room]
|
|
215
372
|
);
|
|
216
373
|
const getSnapshot = room.getMyPresence;
|
|
217
|
-
const _selector =
|
|
374
|
+
const _selector = useCallback4((snapshot) => selector(snapshot), [selector]);
|
|
218
375
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
219
376
|
subscribe,
|
|
220
377
|
getSnapshot,
|
|
221
378
|
getSnapshot,
|
|
222
379
|
_selector,
|
|
223
|
-
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a :
|
|
380
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual2
|
|
224
381
|
);
|
|
225
382
|
return [myPresence, room.updateMyPresence];
|
|
226
383
|
};
|
|
227
384
|
const useMyself = (selector = identity, options2) => {
|
|
228
385
|
var _a;
|
|
229
386
|
const room = useRoom();
|
|
230
|
-
const subscribe =
|
|
387
|
+
const subscribe = useCallback4(
|
|
231
388
|
(onStoreChange) => {
|
|
232
389
|
return room.subscribe("myself", onStoreChange);
|
|
233
390
|
},
|
|
234
391
|
[room]
|
|
235
392
|
);
|
|
236
393
|
const getSnapshot = room.getMyself;
|
|
237
|
-
const _selector =
|
|
394
|
+
const _selector = useCallback4(
|
|
238
395
|
(snapshot) => {
|
|
239
396
|
return !snapshot ? null : selector(snapshot);
|
|
240
397
|
},
|
|
@@ -245,20 +402,20 @@ var createBundle = (client, options = {}) => {
|
|
|
245
402
|
getSnapshot,
|
|
246
403
|
getSnapshot,
|
|
247
404
|
_selector,
|
|
248
|
-
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a :
|
|
405
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual2
|
|
249
406
|
);
|
|
250
407
|
};
|
|
251
408
|
const useOther = (connectionId, selector = identity, options2) => {
|
|
252
409
|
var _a;
|
|
253
410
|
const room = useRoom();
|
|
254
|
-
const subscribe =
|
|
411
|
+
const subscribe = useCallback4(
|
|
255
412
|
(onStoreChange) => {
|
|
256
413
|
return room.other(connectionId, onStoreChange);
|
|
257
414
|
},
|
|
258
415
|
[room, connectionId]
|
|
259
416
|
);
|
|
260
|
-
const getSnapshot =
|
|
261
|
-
const _selector =
|
|
417
|
+
const getSnapshot = useCallback4(() => room.getOther(connectionId), [room, connectionId]);
|
|
418
|
+
const _selector = useCallback4(
|
|
262
419
|
(snapshot) => {
|
|
263
420
|
return !snapshot ? null : selector(snapshot);
|
|
264
421
|
},
|
|
@@ -269,13 +426,13 @@ var createBundle = (client, options = {}) => {
|
|
|
269
426
|
getSnapshot,
|
|
270
427
|
getSnapshot,
|
|
271
428
|
_selector,
|
|
272
|
-
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a :
|
|
429
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual2
|
|
273
430
|
);
|
|
274
431
|
};
|
|
275
432
|
const useOthers = (selector = identity, options2) => {
|
|
276
433
|
var _a;
|
|
277
434
|
const room = useRoom();
|
|
278
|
-
const subscribe =
|
|
435
|
+
const subscribe = useCallback4(
|
|
279
436
|
(onStoreChange) => {
|
|
280
437
|
return room.subscribe("others", onStoreChange);
|
|
281
438
|
},
|
|
@@ -291,7 +448,7 @@ var createBundle = (client, options = {}) => {
|
|
|
291
448
|
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
292
449
|
return shallowArrayEqual(a, b);
|
|
293
450
|
}
|
|
294
|
-
return
|
|
451
|
+
return fastDeepEqual2(a, b);
|
|
295
452
|
}
|
|
296
453
|
);
|
|
297
454
|
};
|
|
@@ -306,11 +463,11 @@ var createBundle = (client, options = {}) => {
|
|
|
306
463
|
room.subscribe("storage-loaded", () => {
|
|
307
464
|
rerender();
|
|
308
465
|
});
|
|
309
|
-
const subscribe =
|
|
310
|
-
const getSnapshot =
|
|
466
|
+
const subscribe = useCallback4((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
|
|
467
|
+
const getSnapshot = useCallback4(() => {
|
|
311
468
|
return room.getStorageJson(key);
|
|
312
469
|
}, [key, room]);
|
|
313
|
-
const _selector =
|
|
470
|
+
const _selector = useCallback4(
|
|
314
471
|
(snapshot) => {
|
|
315
472
|
return snapshot === null ? null : selector(snapshot);
|
|
316
473
|
},
|
|
@@ -321,7 +478,7 @@ var createBundle = (client, options = {}) => {
|
|
|
321
478
|
getSnapshot,
|
|
322
479
|
getSnapshot,
|
|
323
480
|
_selector,
|
|
324
|
-
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a :
|
|
481
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual2
|
|
325
482
|
);
|
|
326
483
|
const sharedType = (_b = room.getStorage(key)) != null ? _b : null;
|
|
327
484
|
return [data, sharedType];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "@pluv/client React.js bindings",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,27 +18,27 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"fast-deep-equal": "^3.1.3",
|
|
21
|
-
"@pluv/client": "^0.
|
|
22
|
-
"@pluv/
|
|
23
|
-
"@pluv/
|
|
21
|
+
"@pluv/client": "^0.39.0",
|
|
22
|
+
"@pluv/types": "^0.39.0",
|
|
23
|
+
"@pluv/crdt": "^0.39.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
27
27
|
"react": "^18.0.0 || ^19.0.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/react": "^19.1.
|
|
31
|
-
"eslint": "^
|
|
30
|
+
"@types/react": "^19.1.1",
|
|
31
|
+
"eslint": "^9.24.0",
|
|
32
32
|
"react": "^19.1.0",
|
|
33
33
|
"tsup": "^8.4.0",
|
|
34
34
|
"typescript": "^5.8.3",
|
|
35
|
-
"@pluv/tsconfig": "^0.
|
|
36
|
-
"eslint-config-pluv": "^0.
|
|
35
|
+
"@pluv/tsconfig": "^0.39.0",
|
|
36
|
+
"eslint-config-pluv": "^0.39.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
|
40
40
|
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
41
|
-
"lint": "eslint src/**/*.ts* --fix",
|
|
41
|
+
"lint": "eslint src/**/*.ts* --fix --max-warnings 0",
|
|
42
42
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/src/createBundle.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbstractRoom,
|
|
3
3
|
CreateRoomOptions,
|
|
4
|
+
EnterRoomParams,
|
|
4
5
|
MergeEvents,
|
|
5
6
|
MockedRoomEvents,
|
|
6
7
|
PluvClient,
|
|
@@ -13,11 +14,18 @@ import type {
|
|
|
13
14
|
} from "@pluv/client";
|
|
14
15
|
import { MockedRoom, PluvRoom } from "@pluv/client";
|
|
15
16
|
import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
16
|
-
import type { Id, InferIOInput, InferIOOutput, IOEventMessage, IOLike, JsonObject } from "@pluv/types";
|
|
17
|
+
import type { Id, InferIOInput, InferIOOutput, IOEventMessage, IOLike, JsonObject, MaybePromise } from "@pluv/types";
|
|
17
18
|
import fastDeepEqual from "fast-deep-equal";
|
|
18
19
|
import type { Dispatch, FC, ReactNode } from "react";
|
|
19
20
|
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
identity,
|
|
23
|
+
shallowArrayEqual,
|
|
24
|
+
useAsyncQueue,
|
|
25
|
+
useDeepAsyncMemo,
|
|
26
|
+
useRerender,
|
|
27
|
+
useSyncExternalStoreWithSelector,
|
|
28
|
+
} from "./internal";
|
|
21
29
|
|
|
22
30
|
export interface PluvProviderProps {
|
|
23
31
|
children?: ReactNode;
|
|
@@ -38,15 +46,18 @@ export type MockedRoomProviderProps<
|
|
|
38
46
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
39
47
|
};
|
|
40
48
|
|
|
49
|
+
export type MetadataGetter<TMetadata extends JsonObject> = TMetadata | (() => MaybePromise<TMetadata>);
|
|
50
|
+
|
|
41
51
|
export type PluvRoomProviderProps<
|
|
42
52
|
TIO extends IOLike<any, any>,
|
|
43
53
|
TMetadata extends JsonObject,
|
|
44
54
|
TPresence extends JsonObject,
|
|
45
55
|
TStorage extends Record<string, CrdtType<any, any>>,
|
|
46
56
|
> = BaseRoomProviderProps<TPresence, TStorage> & {
|
|
57
|
+
connect?: boolean;
|
|
47
58
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
48
59
|
onAuthorizationFail?: (error: Error) => void;
|
|
49
|
-
} & (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata });
|
|
60
|
+
} & (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: MetadataGetter<TMetadata> });
|
|
50
61
|
|
|
51
62
|
export interface SubscriptionHookOptions<T extends unknown> {
|
|
52
63
|
isEqual?: (a: T, b: T) => boolean;
|
|
@@ -151,7 +162,6 @@ export const createBundle = <
|
|
|
151
162
|
* !HACK
|
|
152
163
|
* @description We'll let the context error out if client is not provided,
|
|
153
164
|
* and let the users deal with it.
|
|
154
|
-
* @author leedavidcs
|
|
155
165
|
* @date October 27, 2022
|
|
156
166
|
*/
|
|
157
167
|
const PluvContext = createContext<PluvClient<TIO, TPresence, TStorage, TMetadata>>(null as any);
|
|
@@ -160,14 +170,11 @@ export const createBundle = <
|
|
|
160
170
|
* !HACK
|
|
161
171
|
* @description We'll let the context error out if the room is not provided,
|
|
162
172
|
* and let the users deal with it.
|
|
163
|
-
* @author leedavidcs
|
|
164
173
|
* @date November 11, 2022
|
|
165
174
|
*/
|
|
166
175
|
const PluvRoomContext = createContext<AbstractRoom<TIO, TPresence, TStorage>>(null as any);
|
|
167
176
|
|
|
168
|
-
const MockedRoomContext = createContext<
|
|
169
|
-
room: AbstractRoom<TIO, TPresence, TStorage> | null;
|
|
170
|
-
}>({ room: null });
|
|
177
|
+
const MockedRoomContext = createContext<AbstractRoom<TIO, TPresence, TStorage> | null>(null);
|
|
171
178
|
|
|
172
179
|
const MockedRoomProvider = memo<MockedRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
|
|
173
180
|
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
@@ -184,7 +191,7 @@ export const createBundle = <
|
|
|
184
191
|
});
|
|
185
192
|
|
|
186
193
|
return (
|
|
187
|
-
<MockedRoomContext.Provider value={
|
|
194
|
+
<MockedRoomContext.Provider value={room}>
|
|
188
195
|
<PluvRoomContext.Provider value={room}>{children}</PluvRoomContext.Provider>
|
|
189
196
|
</MockedRoomContext.Provider>
|
|
190
197
|
);
|
|
@@ -193,10 +200,26 @@ export const createBundle = <
|
|
|
193
200
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
194
201
|
|
|
195
202
|
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TMetadata, TPresence, TStorage>>((props) => {
|
|
196
|
-
const {
|
|
197
|
-
|
|
203
|
+
const {
|
|
204
|
+
children,
|
|
205
|
+
connect = true,
|
|
206
|
+
debug,
|
|
207
|
+
initialPresence,
|
|
208
|
+
initialStorage,
|
|
209
|
+
metadata,
|
|
210
|
+
onAuthorizationFail,
|
|
211
|
+
room: _room,
|
|
212
|
+
} = props;
|
|
213
|
+
|
|
214
|
+
const queue = useAsyncQueue();
|
|
198
215
|
const rerender = useRerender();
|
|
199
|
-
const
|
|
216
|
+
const mockedRoom = useContext(MockedRoomContext);
|
|
217
|
+
|
|
218
|
+
const resolvedMeta = useDeepAsyncMemo(async () => {
|
|
219
|
+
const resolved = await Promise.resolve(typeof metadata === "function" ? metadata() : metadata);
|
|
220
|
+
|
|
221
|
+
return !!room.metadata ? room.metadata.parse(metadata) : resolved;
|
|
222
|
+
});
|
|
200
223
|
|
|
201
224
|
const [room] = useState<PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>>(() => {
|
|
202
225
|
return client.createRoom(_room, {
|
|
@@ -224,12 +247,37 @@ export const createBundle = <
|
|
|
224
247
|
}, [rerender, room]);
|
|
225
248
|
|
|
226
249
|
useEffect(() => {
|
|
227
|
-
|
|
250
|
+
const leaveRoom = async (): Promise<void> => {
|
|
251
|
+
await queue.push(
|
|
252
|
+
client.leave(room).catch((error) => {
|
|
253
|
+
console.error(error);
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (!connect) {
|
|
259
|
+
leaveRoom();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!resolvedMeta.isInitialized) {
|
|
264
|
+
leaveRoom();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const resolved = resolvedMeta.value as TMetadata;
|
|
269
|
+
|
|
270
|
+
queue.push(
|
|
271
|
+
client.enter(room, ...([{ metadata: resolved }] as EnterRoomParams<TMetadata>)).catch(async (error) => {
|
|
272
|
+
console.error(error);
|
|
273
|
+
await leaveRoom();
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
228
276
|
|
|
229
277
|
return () => {
|
|
230
|
-
|
|
278
|
+
leaveRoom();
|
|
231
279
|
};
|
|
232
|
-
}, [room]);
|
|
280
|
+
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
233
281
|
|
|
234
282
|
return <PluvRoomContext.Provider value={mockedRoom ?? room}>{children}</PluvRoomContext.Provider>;
|
|
235
283
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { useAsyncQueue } from "./useAsyncQueue";
|
|
2
|
+
export { useDeepAsyncMemo } from "./useDeepAsyncMemo";
|
|
1
3
|
export { useNoSsr } from "./useNoSsr";
|
|
2
4
|
export { useRerender } from "./useRerender";
|
|
3
5
|
export { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description This is to handle race conditions, so that our async operations that can
|
|
5
|
+
* happen between re-renders will happen sequentially.
|
|
6
|
+
* @date April 13, 2025
|
|
7
|
+
*/
|
|
8
|
+
export const useAsyncQueue = () => {
|
|
9
|
+
const queueRef = useRef<Promise<void>>(Promise.resolve(undefined));
|
|
10
|
+
|
|
11
|
+
const push = useCallback(async (promise: Promise<any>) => {
|
|
12
|
+
queueRef.current = queueRef.current.then(async () => await promise);
|
|
13
|
+
|
|
14
|
+
return await queueRef.current;
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
return useMemo(() => ({ push }), [push]);
|
|
18
|
+
};
|