@korajs/react 1.0.0-beta.11 → 1.0.0-beta.13
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/README.md +48 -40
- package/dist/index.cjs +210 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -5
- package/dist/index.d.ts +172 -5
- package/dist/index.js +207 -64
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,70 +1,78 @@
|
|
|
1
1
|
# @korajs/react
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
React bindings for Kora.js: a provider, reactive queries, mutations, sync status, rich text and
|
|
4
|
+
presence. Hooks use `useSyncExternalStore`, so they are safe in concurrent rendering, StrictMode and
|
|
5
|
+
server rendering.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
pnpm add korajs @korajs/react
|
|
10
|
+
pnpm add korajs@beta @korajs/react@beta
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
<!-- docs-check-prelude
|
|
16
|
+
import { createApp, defineSchema, t } from 'korajs'
|
|
17
|
+
const schema = defineSchema({
|
|
18
|
+
version: 1,
|
|
19
|
+
collections: { todos: { fields: { title: t.string(), completed: t.boolean().default(false), createdAt: t.timestamp().auto() } } },
|
|
20
|
+
})
|
|
21
|
+
const app = createApp({ schema })
|
|
22
|
+
-->
|
|
14
23
|
|
|
15
24
|
```tsx
|
|
16
|
-
import { KoraProvider } from '@korajs/react'
|
|
25
|
+
import { createKoraHooks, KoraProvider } from '@korajs/react'
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
// Hooks typed by your schema
|
|
28
|
+
const { useCollection, useMutation, useQuery, useSyncStatus } = createKoraHooks<typeof app>()
|
|
29
|
+
|
|
30
|
+
export function Root() {
|
|
19
31
|
return (
|
|
20
|
-
<KoraProvider app={app}>
|
|
32
|
+
<KoraProvider app={app} fallback={<p>Loading...</p>}>
|
|
21
33
|
<TodoList />
|
|
22
34
|
</KoraProvider>
|
|
23
35
|
)
|
|
24
36
|
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Reactive Queries
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { useQuery } from '@korajs/react'
|
|
31
37
|
|
|
32
38
|
function TodoList() {
|
|
33
|
-
const todos =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Mutations
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { useMutation } from '@korajs/react'
|
|
44
|
-
|
|
45
|
-
function AddTodo() {
|
|
46
|
-
const addTodo = useMutation(app.todos.insert)
|
|
39
|
+
const todos = useCollection('todos')
|
|
40
|
+
const open = useQuery(todos.where({ completed: false }).orderBy('createdAt'))
|
|
41
|
+
const addTodo = useMutation(todos.insert)
|
|
42
|
+
const status = useSyncStatus()
|
|
47
43
|
|
|
48
|
-
return
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<button onClick={() => addTodo.mutate({ title: 'New todo' })}>Add</button>
|
|
47
|
+
{addTodo.error && <p role="alert">{addTodo.error.message}</p>}
|
|
48
|
+
<ul>
|
|
49
|
+
{open.map((todo) => (
|
|
50
|
+
<li key={todo.id}>{todo.title}</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
<small>
|
|
54
|
+
{status.status}, {status.pendingOperations} pending
|
|
55
|
+
</small>
|
|
56
|
+
</>
|
|
57
|
+
)
|
|
49
58
|
}
|
|
50
59
|
```
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
- `KoraProvider` renders `fallback` until the local database is open.
|
|
62
|
+
- `useQuery` renders `[]` first and the rows right after mount, then re-renders only when the
|
|
63
|
+
result changes. `useQueryState` returns `{ data, error, ready }`.
|
|
64
|
+
- `useMutation` returns `{ mutate, mutateAsync, isLoading, error, reset }`; writes land locally at
|
|
65
|
+
once and upload in the background.
|
|
66
|
+
- `useSyncStatus()` returns the full status (`status`, `pendingOperations`, `lastSyncedAt`, ...)
|
|
67
|
+
and re-renders only when it changes.
|
|
68
|
+
- `useRichText`, `usePresence`, `useCollaborators` and `AuthBoundKoraProvider` cover collaborative
|
|
69
|
+
editing and per-user databases.
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
const status = useSyncStatus()
|
|
59
|
-
// 'connected' | 'syncing' | 'synced' | 'offline' | 'error'
|
|
60
|
-
return <span>{status}</span>
|
|
61
|
-
}
|
|
62
|
-
```
|
|
71
|
+
## Documentation
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
[React Hooks guide](https://korajs.dev/guide/react-hooks) and the
|
|
74
|
+
[React API reference](https://korajs.dev/api/react).
|
|
65
75
|
|
|
66
76
|
## License
|
|
67
77
|
|
|
68
78
|
MIT
|
|
69
|
-
|
|
70
|
-
See the [full documentation](https://github.com/ehoneahobed/kora) for guides, API reference, and examples.
|
package/dist/index.cjs
CHANGED
|
@@ -22,12 +22,15 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthBoundKoraProvider: () => AuthBoundKoraProvider,
|
|
24
24
|
KoraProvider: () => KoraProvider,
|
|
25
|
+
classifyKoraInitializationError: () => classifyKoraInitializationError,
|
|
26
|
+
createKoraHooks: () => createKoraHooks,
|
|
25
27
|
useApp: () => useApp,
|
|
26
28
|
useCollaborators: () => useCollaborators,
|
|
27
29
|
useCollection: () => useCollection,
|
|
28
30
|
useMutation: () => useMutation,
|
|
29
31
|
usePresence: () => usePresence,
|
|
30
32
|
useQuery: () => useQuery,
|
|
33
|
+
useQueryState: () => useQueryState,
|
|
31
34
|
useRichText: () => useRichText,
|
|
32
35
|
useSyncStatus: () => useSyncStatus
|
|
33
36
|
});
|
|
@@ -123,11 +126,36 @@ function useKoraContext() {
|
|
|
123
126
|
|
|
124
127
|
// src/context/auth-bound-kora-provider.ts
|
|
125
128
|
var import_react2 = require("react");
|
|
129
|
+
var SENSITIVE_METADATA_KEY = /token|secret|password|authorization|cookie/i;
|
|
130
|
+
function classifyKoraInitializationError(error) {
|
|
131
|
+
const candidate = error;
|
|
132
|
+
const message = typeof candidate?.message === "string" ? candidate.message : String(error);
|
|
133
|
+
const name = typeof candidate?.name === "string" ? candidate.name : "Error";
|
|
134
|
+
const cause = new Error(message);
|
|
135
|
+
cause.name = name;
|
|
136
|
+
if (typeof candidate?.stack === "string") cause.stack = candidate.stack;
|
|
137
|
+
let code = typeof candidate?.code === "string" ? candidate.code : "INITIALIZATION_FAILED";
|
|
138
|
+
if (code === "INITIALIZATION_FAILED" && cause.name === "QuotaExceededError") {
|
|
139
|
+
code = "STORAGE_QUOTA_EXCEEDED";
|
|
140
|
+
}
|
|
141
|
+
const metadata = {};
|
|
142
|
+
if (candidate?.context && typeof candidate.context === "object") {
|
|
143
|
+
for (const [key, value] of Object.entries(candidate.context)) {
|
|
144
|
+
if (SENSITIVE_METADATA_KEY.test(key)) continue;
|
|
145
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
146
|
+
metadata[key] = value;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { code, name: cause.name, message: cause.message, metadata, cause };
|
|
151
|
+
}
|
|
126
152
|
function AuthBoundKoraProvider({
|
|
127
153
|
authClient,
|
|
128
154
|
createApp,
|
|
129
155
|
signedOut = null,
|
|
156
|
+
locked,
|
|
130
157
|
fallback = null,
|
|
158
|
+
error: renderError,
|
|
131
159
|
children
|
|
132
160
|
}) {
|
|
133
161
|
const [app, setApp] = (0, import_react2.useState)(null);
|
|
@@ -139,6 +167,8 @@ function AuthBoundKoraProvider({
|
|
|
139
167
|
const activeUserId = (0, import_react2.useRef)(null);
|
|
140
168
|
const generation = (0, import_react2.useRef)(0);
|
|
141
169
|
const transition = (0, import_react2.useRef)(Promise.resolve());
|
|
170
|
+
const reconcileRef = (0, import_react2.useRef)(() => {
|
|
171
|
+
});
|
|
142
172
|
(0, import_react2.useEffect)(() => {
|
|
143
173
|
let disposed = false;
|
|
144
174
|
const reconcile = () => {
|
|
@@ -176,10 +206,18 @@ function AuthBoundKoraProvider({
|
|
|
176
206
|
setApp(created);
|
|
177
207
|
}).catch((error) => {
|
|
178
208
|
if (!disposed && currentGeneration === generation.current) {
|
|
179
|
-
|
|
209
|
+
void authClient.resolveSyncState?.().then((failedSession) => {
|
|
210
|
+
if (!disposed && currentGeneration === generation.current && failedSession?.state === "authenticated") {
|
|
211
|
+
setInitError({
|
|
212
|
+
error: classifyKoraInitializationError(error),
|
|
213
|
+
session: { userId: failedSession.userId }
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
180
217
|
}
|
|
181
218
|
});
|
|
182
219
|
};
|
|
220
|
+
reconcileRef.current = reconcile;
|
|
183
221
|
reconcile();
|
|
184
222
|
const unsubscribe = authClient.subscribe?.(reconcile) ?? (() => {
|
|
185
223
|
});
|
|
@@ -194,14 +232,22 @@ function AuthBoundKoraProvider({
|
|
|
194
232
|
};
|
|
195
233
|
}, [authClient]);
|
|
196
234
|
if (initError) {
|
|
235
|
+
if (renderError) {
|
|
236
|
+
return renderError({
|
|
237
|
+
error: initError.error,
|
|
238
|
+
session: initError.session,
|
|
239
|
+
retry: () => reconcileRef.current()
|
|
240
|
+
});
|
|
241
|
+
}
|
|
197
242
|
return (0, import_react2.createElement)(
|
|
198
243
|
"div",
|
|
199
244
|
{ style: { color: "red", padding: "1rem", fontFamily: "monospace" } },
|
|
200
245
|
(0, import_react2.createElement)("strong", null, "Kora initialization error: "),
|
|
201
|
-
initError.message
|
|
246
|
+
initError.error.message
|
|
202
247
|
);
|
|
203
248
|
}
|
|
204
249
|
if (state.state === "signed-out" || state.state === "anonymous") return signedOut;
|
|
250
|
+
if (state.state === "authenticated" && state.locked) return locked ?? signedOut;
|
|
205
251
|
if (!app) return fallback;
|
|
206
252
|
return (0, import_react2.createElement)(KoraProvider, { app, fallback }, children);
|
|
207
253
|
}
|
|
@@ -225,10 +271,17 @@ var noopSubscribe = (_onStoreChange) => {
|
|
|
225
271
|
return () => {
|
|
226
272
|
};
|
|
227
273
|
};
|
|
228
|
-
|
|
274
|
+
var getServerSnapshot = () => EMPTY_ARRAY;
|
|
275
|
+
var getNoError = () => null;
|
|
276
|
+
function readQueryError(queryStore) {
|
|
277
|
+
if (!queryStore) return null;
|
|
278
|
+
const channel = queryStore;
|
|
279
|
+
return typeof channel.getError === "function" ? channel.getError() : null;
|
|
280
|
+
}
|
|
281
|
+
function useQuerySubscription(query, options) {
|
|
229
282
|
const { queryStoreCache } = useKoraContext();
|
|
230
283
|
const enabled = options?.enabled !== false;
|
|
231
|
-
const descriptorKey =
|
|
284
|
+
const descriptorKey = (0, import_store2.queryKey)(query.getDescriptor());
|
|
232
285
|
const queryRef = (0, import_react3.useRef)(query);
|
|
233
286
|
queryRef.current = query;
|
|
234
287
|
const lastSnapshotRef = (0, import_react3.useRef)(EMPTY_ARRAY);
|
|
@@ -246,122 +299,204 @@ function useQuery(query, options) {
|
|
|
246
299
|
queryStoreCache.release(currentQuery);
|
|
247
300
|
};
|
|
248
301
|
}, [descriptorKey, enabled, queryStoreCache]);
|
|
302
|
+
const subscribe = enabled && queryStore ? queryStore.subscribe : noopSubscribe;
|
|
249
303
|
const getSnapshot = (0, import_react3.useCallback)(() => {
|
|
250
304
|
if (!enabled) {
|
|
251
305
|
lastSnapshotRef.current = EMPTY_ARRAY;
|
|
252
306
|
return lastSnapshotRef.current;
|
|
253
307
|
}
|
|
254
|
-
if (!queryStore) {
|
|
255
|
-
return lastSnapshotRef.current;
|
|
256
|
-
}
|
|
257
|
-
if (!queryStore.hasSnapshot()) {
|
|
308
|
+
if (!queryStore || !queryStore.hasSnapshot()) {
|
|
258
309
|
return lastSnapshotRef.current;
|
|
259
310
|
}
|
|
260
311
|
const snapshot = queryStore.getSnapshot();
|
|
261
312
|
lastSnapshotRef.current = snapshot;
|
|
262
313
|
return snapshot;
|
|
263
314
|
}, [enabled, queryStore]);
|
|
264
|
-
|
|
315
|
+
const getError = (0, import_react3.useCallback)(
|
|
316
|
+
() => enabled ? readQueryError(queryStore) : null,
|
|
317
|
+
[enabled, queryStore]
|
|
318
|
+
);
|
|
319
|
+
const data = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
|
|
320
|
+
const error = (0, import_react3.useSyncExternalStore)(subscribe, getError, getNoError);
|
|
321
|
+
const ready = enabled && queryStore !== null && queryStore.hasSnapshot();
|
|
322
|
+
return { data, error, ready };
|
|
323
|
+
}
|
|
324
|
+
function useQuery(query, options) {
|
|
325
|
+
const { data, error } = useQuerySubscription(query, options);
|
|
326
|
+
if (error && options?.throwOnError !== false) {
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
return data;
|
|
330
|
+
}
|
|
331
|
+
function useQueryState(query, options) {
|
|
332
|
+
const { data, error, ready } = useQuerySubscription(query, options);
|
|
333
|
+
return (0, import_react3.useMemo)(() => ({ data, error, ready }), [data, error, ready]);
|
|
265
334
|
}
|
|
266
335
|
|
|
336
|
+
// src/hooks/create-kora-hooks.ts
|
|
337
|
+
var import_react7 = require("react");
|
|
338
|
+
|
|
267
339
|
// src/hooks/use-mutation.ts
|
|
268
340
|
var import_bindings = require("@korajs/core/bindings");
|
|
269
341
|
var import_react5 = require("react");
|
|
270
342
|
|
|
271
343
|
// src/hooks/use-controller.ts
|
|
272
344
|
var import_react4 = require("react");
|
|
345
|
+
var ORPHAN_REAP_MS = 1e3;
|
|
273
346
|
function useController(create, destroy, deps) {
|
|
274
|
-
const
|
|
347
|
+
const entryRef = (0, import_react4.useRef)(null);
|
|
275
348
|
const createRef = (0, import_react4.useRef)(create);
|
|
276
349
|
createRef.current = create;
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
350
|
+
const destroyRef = (0, import_react4.useRef)(destroy);
|
|
351
|
+
destroyRef.current = destroy;
|
|
352
|
+
const [version, bumpVersion] = (0, import_react4.useReducer)((count) => count + 1, 0);
|
|
353
|
+
const handle = (0, import_react4.useRef)({
|
|
354
|
+
get: () => {
|
|
355
|
+
const current = entryRef.current;
|
|
356
|
+
if (current !== null && !current.reaped) {
|
|
357
|
+
return current.controller;
|
|
358
|
+
}
|
|
359
|
+
const entry = {
|
|
360
|
+
controller: createRef.current(),
|
|
361
|
+
claimed: false,
|
|
362
|
+
reaped: false
|
|
363
|
+
};
|
|
364
|
+
entryRef.current = entry;
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
if (!entry.claimed && !entry.reaped) {
|
|
367
|
+
entry.reaped = true;
|
|
368
|
+
destroyRef.current(entry.controller);
|
|
369
|
+
}
|
|
370
|
+
}, ORPHAN_REAP_MS);
|
|
371
|
+
return entry.controller;
|
|
372
|
+
},
|
|
373
|
+
peek: () => {
|
|
374
|
+
const current = entryRef.current;
|
|
375
|
+
return current?.claimed && !current.reaped ? current.controller : null;
|
|
281
376
|
}
|
|
282
|
-
return controllerRef.current;
|
|
283
377
|
}).current;
|
|
284
378
|
(0, import_react4.useEffect)(() => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
379
|
+
const current = entryRef.current;
|
|
380
|
+
if (current !== null && !current.reaped && !current.claimed) {
|
|
381
|
+
current.claimed = true;
|
|
382
|
+
} else if (current === null || current.reaped) {
|
|
383
|
+
entryRef.current = { controller: createRef.current(), claimed: true, reaped: false };
|
|
384
|
+
bumpVersion();
|
|
288
385
|
}
|
|
289
386
|
return () => {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
387
|
+
const entry = entryRef.current;
|
|
388
|
+
entryRef.current = null;
|
|
389
|
+
if (entry !== null && !entry.reaped) {
|
|
390
|
+
entry.reaped = true;
|
|
391
|
+
destroyRef.current(entry.controller);
|
|
294
392
|
}
|
|
295
393
|
};
|
|
296
394
|
}, deps);
|
|
297
|
-
return
|
|
395
|
+
return { get: handle.get, peek: handle.peek, version };
|
|
298
396
|
}
|
|
299
397
|
|
|
300
398
|
// src/hooks/use-mutation.ts
|
|
399
|
+
var IDLE = Object.freeze({ isLoading: false, error: null });
|
|
400
|
+
var getServerSnapshot2 = () => IDLE;
|
|
301
401
|
function useMutation(mutationFn, options) {
|
|
302
402
|
const fnRef = (0, import_react5.useRef)(mutationFn);
|
|
303
403
|
fnRef.current = mutationFn;
|
|
304
404
|
const optionsRef = (0, import_react5.useRef)(options);
|
|
305
405
|
optionsRef.current = options;
|
|
306
|
-
const
|
|
406
|
+
const controller = useController(
|
|
307
407
|
() => (0, import_bindings.createMutationController)({
|
|
308
408
|
mutationFn: (...args) => fnRef.current(...args),
|
|
309
409
|
resolveOptions: () => optionsRef.current
|
|
310
410
|
}),
|
|
311
|
-
(
|
|
411
|
+
(instance) => instance.destroy(),
|
|
312
412
|
[]
|
|
313
413
|
);
|
|
314
|
-
const
|
|
414
|
+
const getController = controller.get;
|
|
415
|
+
const subscribe = (0, import_react5.useCallback)(
|
|
315
416
|
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
316
|
-
|
|
317
|
-
|
|
417
|
+
[getController, controller.version]
|
|
418
|
+
);
|
|
419
|
+
const getSnapshot = (0, import_react5.useCallback)(() => getController().getSnapshot(), [getController]);
|
|
420
|
+
const state = (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot2);
|
|
421
|
+
const mutate = (0, import_react5.useCallback)((...args) => getController().mutate(...args), [getController]);
|
|
422
|
+
const mutateAsync = (0, import_react5.useCallback)(
|
|
423
|
+
(...args) => getController().mutateAsync(...args),
|
|
424
|
+
[getController]
|
|
425
|
+
);
|
|
426
|
+
const reset = (0, import_react5.useCallback)(() => getController().reset(), [getController]);
|
|
427
|
+
return (0, import_react5.useMemo)(
|
|
428
|
+
() => ({ mutate, mutateAsync, isLoading: state.isLoading, error: state.error, reset }),
|
|
429
|
+
[mutate, mutateAsync, reset, state.isLoading, state.error]
|
|
318
430
|
);
|
|
319
|
-
return {
|
|
320
|
-
mutate: (...args) => getController().mutate(...args),
|
|
321
|
-
mutateAsync: (...args) => getController().mutateAsync(...args),
|
|
322
|
-
isLoading: state.isLoading,
|
|
323
|
-
error: state.error,
|
|
324
|
-
reset: () => getController().reset()
|
|
325
|
-
};
|
|
326
431
|
}
|
|
327
432
|
|
|
328
433
|
// src/hooks/use-sync-status.ts
|
|
329
434
|
var import_sync = require("@korajs/sync");
|
|
330
435
|
var import_react6 = require("react");
|
|
436
|
+
var getServerSnapshot3 = () => import_sync.OFFLINE_SYNC_STATUS;
|
|
331
437
|
function useSyncStatus() {
|
|
332
438
|
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
333
|
-
const
|
|
439
|
+
const controller = useController(
|
|
334
440
|
() => (0, import_sync.createSyncStatusController)({
|
|
335
441
|
syncEngine,
|
|
336
442
|
subscribeSyncStatus,
|
|
337
443
|
events: subscribeSyncStatus ? null : events
|
|
338
444
|
}),
|
|
339
|
-
(
|
|
445
|
+
(instance) => instance.destroy(),
|
|
340
446
|
[syncEngine, subscribeSyncStatus, events]
|
|
341
447
|
);
|
|
342
|
-
|
|
448
|
+
const getController = controller.get;
|
|
449
|
+
const subscribe = (0, import_react6.useCallback)(
|
|
343
450
|
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
344
|
-
|
|
345
|
-
() => getController().getSnapshot()
|
|
451
|
+
[getController, controller.version]
|
|
346
452
|
);
|
|
453
|
+
const peek = controller.peek;
|
|
454
|
+
const getSnapshot = (0, import_react6.useCallback)(
|
|
455
|
+
() => peek()?.getSnapshot() ?? import_sync.OFFLINE_SYNC_STATUS,
|
|
456
|
+
[peek]
|
|
457
|
+
);
|
|
458
|
+
return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot3);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/hooks/create-kora-hooks.ts
|
|
462
|
+
function createKoraHooks() {
|
|
463
|
+
function useTypedApp() {
|
|
464
|
+
return useApp();
|
|
465
|
+
}
|
|
466
|
+
function useTypedCollection(name) {
|
|
467
|
+
const { app, store } = useKoraContext();
|
|
468
|
+
return (0, import_react7.useMemo)(() => {
|
|
469
|
+
const collections = app?.collections;
|
|
470
|
+
const accessor = collections && name in collections ? collections[name] : store.collection(name);
|
|
471
|
+
return accessor;
|
|
472
|
+
}, [app, store, name]);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
useApp: useTypedApp,
|
|
476
|
+
useCollection: useTypedCollection,
|
|
477
|
+
useQuery,
|
|
478
|
+
useQueryState,
|
|
479
|
+
useMutation,
|
|
480
|
+
useSyncStatus
|
|
481
|
+
};
|
|
347
482
|
}
|
|
348
483
|
|
|
349
484
|
// src/hooks/use-collection.ts
|
|
350
|
-
var
|
|
485
|
+
var import_react8 = require("react");
|
|
351
486
|
function useCollection(name) {
|
|
352
487
|
const { store } = useKoraContext();
|
|
353
|
-
return (0,
|
|
488
|
+
return (0, import_react8.useMemo)(() => {
|
|
354
489
|
return store.collection(name);
|
|
355
490
|
}, [store, name]);
|
|
356
491
|
}
|
|
357
492
|
|
|
358
493
|
// src/hooks/use-rich-text.ts
|
|
359
494
|
var import_store3 = require("@korajs/store");
|
|
360
|
-
var
|
|
495
|
+
var import_react9 = require("react");
|
|
361
496
|
function useRichText(collectionName, recordId, fieldName, options) {
|
|
362
497
|
const { store, syncEngine } = useKoraContext();
|
|
363
|
-
const collection = (0,
|
|
364
|
-
const
|
|
498
|
+
const collection = (0, import_react9.useMemo)(() => store.collection(collectionName), [store, collectionName]);
|
|
499
|
+
const controller = useController(
|
|
365
500
|
() => (0, import_store3.createRichTextController)({
|
|
366
501
|
collection,
|
|
367
502
|
collectionName,
|
|
@@ -372,29 +507,35 @@ function useRichText(collectionName, recordId, fieldName, options) {
|
|
|
372
507
|
useDocChannel: options?.useDocChannel,
|
|
373
508
|
user: options?.user
|
|
374
509
|
}),
|
|
375
|
-
(
|
|
510
|
+
(instance) => instance.destroy(),
|
|
376
511
|
[collection, collectionName, fieldName, options?.useDocChannel, recordId, store, syncEngine]
|
|
377
512
|
);
|
|
378
|
-
|
|
513
|
+
const getController = controller.get;
|
|
514
|
+
(0, import_react9.useEffect)(() => {
|
|
379
515
|
getController().setUser(options?.user);
|
|
380
|
-
}, [getController, options?.user]);
|
|
381
|
-
const
|
|
516
|
+
}, [getController, options?.user, controller.version]);
|
|
517
|
+
const subscribe = (0, import_react9.useCallback)(
|
|
382
518
|
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
383
|
-
|
|
384
|
-
() => getController().getSnapshot()
|
|
519
|
+
[getController, controller.version]
|
|
385
520
|
);
|
|
386
|
-
const
|
|
521
|
+
const getSnapshot = (0, import_react9.useCallback)(() => getController().getSnapshot(), [getController]);
|
|
522
|
+
const snapshot = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
523
|
+
const undo = (0, import_react9.useCallback)(() => {
|
|
387
524
|
getController().undo();
|
|
388
525
|
}, [getController]);
|
|
389
|
-
const redo = (0,
|
|
526
|
+
const redo = (0, import_react9.useCallback)(() => {
|
|
390
527
|
getController().redo();
|
|
391
528
|
}, [getController]);
|
|
392
|
-
const setCursor = (0,
|
|
529
|
+
const setCursor = (0, import_react9.useCallback)(
|
|
393
530
|
(anchor, head) => getController().setCursor(anchor, head),
|
|
394
531
|
[getController]
|
|
395
532
|
);
|
|
396
|
-
const clearCursor = (0,
|
|
397
|
-
|
|
533
|
+
const clearCursor = (0, import_react9.useCallback)(() => getController().clearCursor(), [getController]);
|
|
534
|
+
const live = getController();
|
|
535
|
+
return (0, import_react9.useMemo)(
|
|
536
|
+
() => buildResult(live, snapshot, undo, redo, setCursor, clearCursor),
|
|
537
|
+
[live, snapshot, undo, redo, setCursor, clearCursor, controller.version]
|
|
538
|
+
);
|
|
398
539
|
}
|
|
399
540
|
function buildResult(controller, snapshot, undo, redo, setCursor, clearCursor) {
|
|
400
541
|
return {
|
|
@@ -413,13 +554,13 @@ function buildResult(controller, snapshot, undo, redo, setCursor, clearCursor) {
|
|
|
413
554
|
}
|
|
414
555
|
|
|
415
556
|
// src/hooks/use-presence.ts
|
|
416
|
-
var
|
|
557
|
+
var import_react10 = require("react");
|
|
417
558
|
function usePresence(user) {
|
|
418
559
|
const { syncEngine } = useKoraContext();
|
|
419
560
|
const name = user?.name ?? null;
|
|
420
561
|
const color = user?.color ?? null;
|
|
421
562
|
const avatar = user?.avatar ?? null;
|
|
422
|
-
(0,
|
|
563
|
+
(0, import_react10.useEffect)(() => {
|
|
423
564
|
if (!syncEngine || !name || !color) return;
|
|
424
565
|
const awareness = syncEngine.getAwarenessManager();
|
|
425
566
|
awareness.setLocalState({
|
|
@@ -433,12 +574,13 @@ function usePresence(user) {
|
|
|
433
574
|
|
|
434
575
|
// src/hooks/use-collaborators.ts
|
|
435
576
|
var import_sync2 = require("@korajs/sync");
|
|
436
|
-
var
|
|
577
|
+
var import_react11 = require("react");
|
|
437
578
|
var EMPTY_ARRAY2 = [];
|
|
579
|
+
var getServerSnapshot4 = () => EMPTY_ARRAY2;
|
|
438
580
|
function useCollaborators() {
|
|
439
581
|
const { syncEngine } = useKoraContext();
|
|
440
|
-
const snapshotRef = (0,
|
|
441
|
-
const subscribe = (0,
|
|
582
|
+
const snapshotRef = (0, import_react11.useRef)(EMPTY_ARRAY2);
|
|
583
|
+
const subscribe = (0, import_react11.useCallback)(
|
|
442
584
|
(onStoreChange) => {
|
|
443
585
|
if (!syncEngine) {
|
|
444
586
|
snapshotRef.current = EMPTY_ARRAY2;
|
|
@@ -446,6 +588,7 @@ function useCollaborators() {
|
|
|
446
588
|
};
|
|
447
589
|
}
|
|
448
590
|
const awareness = syncEngine.getAwarenessManager();
|
|
591
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
449
592
|
return (0, import_sync2.subscribeRemoteAwarenessStates)(awareness, (states) => {
|
|
450
593
|
snapshotRef.current = states;
|
|
451
594
|
onStoreChange();
|
|
@@ -453,24 +596,27 @@ function useCollaborators() {
|
|
|
453
596
|
},
|
|
454
597
|
[syncEngine]
|
|
455
598
|
);
|
|
456
|
-
const getSnapshot = (0,
|
|
457
|
-
(0,
|
|
599
|
+
const getSnapshot = (0, import_react11.useCallback)(() => snapshotRef.current, []);
|
|
600
|
+
(0, import_react11.useEffect)(() => {
|
|
458
601
|
if (!syncEngine) {
|
|
459
602
|
snapshotRef.current = EMPTY_ARRAY2;
|
|
460
603
|
}
|
|
461
604
|
}, [syncEngine]);
|
|
462
|
-
return (0,
|
|
605
|
+
return (0, import_react11.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot4);
|
|
463
606
|
}
|
|
464
607
|
// Annotate the CommonJS export names for ESM import in node:
|
|
465
608
|
0 && (module.exports = {
|
|
466
609
|
AuthBoundKoraProvider,
|
|
467
610
|
KoraProvider,
|
|
611
|
+
classifyKoraInitializationError,
|
|
612
|
+
createKoraHooks,
|
|
468
613
|
useApp,
|
|
469
614
|
useCollaborators,
|
|
470
615
|
useCollection,
|
|
471
616
|
useMutation,
|
|
472
617
|
usePresence,
|
|
473
618
|
useQuery,
|
|
619
|
+
useQueryState,
|
|
474
620
|
useRichText,
|
|
475
621
|
useSyncStatus
|
|
476
622
|
});
|