@voltro/local-first 0.53.0 → 0.55.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/CHANGELOG.md +335 -0
- package/README.md +1 -1
- package/dist/editor.d.ts +125 -0
- package/dist/editor.js +53 -0
- package/dist/index.d.ts +43 -11
- package/dist/index.js +2 -6
- package/dist/react.d.ts +111 -6
- package/dist/react.js +132 -60
- package/dist/{room-DZzqWoJq.js → room-24dGMM80.js} +84 -84
- package/package.json +7 -2
package/dist/index.d.ts
CHANGED
|
@@ -113,7 +113,7 @@ export declare interface CrdtBackend {
|
|
|
113
113
|
* holds). `crdtText()` remains the single-field specialisation over the same
|
|
114
114
|
* backend.
|
|
115
115
|
*/
|
|
116
|
-
declare interface CrdtDocHandle {
|
|
116
|
+
export declare interface CrdtDocHandle {
|
|
117
117
|
/**
|
|
118
118
|
* The underlying library document (a `Y.Doc` for the yjs backend), typed
|
|
119
119
|
* opaquely: an EDITOR BINDING needs the real object (Tiptap's Collaboration
|
|
@@ -134,9 +134,25 @@ declare interface CrdtDocHandle {
|
|
|
134
134
|
encodeUpdateSince: (sinceVector: Uint8Array) => CrdtState;
|
|
135
135
|
/** Fold an encoded state or incremental update into this doc, in place. */
|
|
136
136
|
applyState: (state: CrdtState) => void;
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
139
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe to update blobs this doc produces. Returns the unsubscribe.
|
|
139
|
+
*
|
|
140
|
+
* `local` distinguishes an edit made HERE from one produced by folding a
|
|
141
|
+
* remote state through `applyState`, and it is the difference between a
|
|
142
|
+
* working push loop and an echo: without it, applying a peer's update fires
|
|
143
|
+
* this handler, the app pushes it back, and every client re-broadcasts what
|
|
144
|
+
* it just received. Measured with three tabs open: one keystroke produced
|
|
145
|
+
* three server writes instead of one. It converges — the merge is
|
|
146
|
+
* idempotent — but the amplification scales with the session.
|
|
147
|
+
*
|
|
148
|
+
* It had to come from the BACKEND rather than an `applying` flag around
|
|
149
|
+
* `applyState` in app code: that flag is only correct while the backend emits
|
|
150
|
+
* synchronously, which `CrdtBackend` deliberately does not promise ("the
|
|
151
|
+
* backend decision lives behind our abstraction so it can change").
|
|
152
|
+
*/
|
|
153
|
+
onUpdate: (handler: (update: CrdtState, meta: {
|
|
154
|
+
readonly local: boolean;
|
|
155
|
+
}) => void) => () => void;
|
|
140
156
|
/**
|
|
141
157
|
* Encode a stable ANCHOR at `index` of the named text field — a position
|
|
142
158
|
* that survives concurrent edits (the primitive an inline-comment UI pins
|
|
@@ -765,14 +781,30 @@ export declare interface ResolveWithPolicyOptions {
|
|
|
765
781
|
}
|
|
766
782
|
|
|
767
783
|
/**
|
|
768
|
-
* The genuinely REMAINING runtime
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
784
|
+
* The genuinely REMAINING runtime seam — the two app-level NAMES nothing can
|
|
785
|
+
* derive, not un-built framework code: the mutation tag that writes the
|
|
786
|
+
* `crdtText()` column and the reactive query tag that streams the row, which
|
|
787
|
+
* `useCrdtText` is pointed at. The lifecycle around them ships; see the DONE
|
|
788
|
+
* entry above.
|
|
789
|
+
*
|
|
790
|
+
* **Two entries left this list, in opposite directions, and the distinction is
|
|
791
|
+
* the reason this comment is longer than the array.** A seam is work somebody
|
|
792
|
+
* still has to do. Neither of these was:
|
|
793
|
+
*
|
|
794
|
+
* - `presence-broker-binding` is BUILT (`usePresenceChannel` in
|
|
795
|
+
* `@voltro/plugin-presence/web`, riding the framework's own presence lane;
|
|
796
|
+
* shape pinned by `presence/channelParity.test-d.ts`). The DONE entry above
|
|
797
|
+
* has said so since plan 02 phase 1 while this array said the opposite —
|
|
798
|
+
* one file claiming both, which is worse than either claim alone.
|
|
799
|
+
* - `wasm-sqlite-durable-adapter` was REJECTED, with the reasoning recorded
|
|
800
|
+
* in `mirror/queryMirror.ts`: the client's query surface is `(tag, input)`
|
|
801
|
+
* and predicates never exist client-side, so a browser SQL engine would
|
|
802
|
+
* evaluate a language the client never sees. A decision we made is not a
|
|
803
|
+
* gap in the framework, and listing it as one invites somebody to close it.
|
|
804
|
+
* The `KvStore` seam still admits a SQLite BACKING (mobile's expo-sqlite
|
|
805
|
+
* adapter takes exactly that) — that is a different, already-open door.
|
|
774
806
|
*/
|
|
775
|
-
export declare const RUNTIME_SEAMS: readonly ["sync-transport-app-tags"
|
|
807
|
+
export declare const RUNTIME_SEAMS: readonly ["sync-transport-app-tags"];
|
|
776
808
|
|
|
777
809
|
export declare type RuntimeSeam = (typeof RUNTIME_SEAMS)[number];
|
|
778
810
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./room-
|
|
1
|
+
import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./room-24dGMM80.js";
|
|
2
2
|
//#region src/conflict/policy.ts
|
|
3
3
|
var g = (e) => {
|
|
4
4
|
if (e.writer !== void 0) return `w:${e.writer}`;
|
|
@@ -283,10 +283,6 @@ var g = (e) => {
|
|
|
283
283
|
"sync-transport-react-binding",
|
|
284
284
|
"presence-room",
|
|
285
285
|
"localFirst-mixin-discovery"
|
|
286
|
-
], Q = [
|
|
287
|
-
"sync-transport-app-tags",
|
|
288
|
-
"presence-broker-binding",
|
|
289
|
-
"wasm-sqlite-durable-adapter"
|
|
290
|
-
];
|
|
286
|
+
], Q = ["sync-transport-app-tags"];
|
|
291
287
|
//#endregion
|
|
292
288
|
export { Z as IMPLEMENTED_SINCE_SEAMS, Q as RUNTIME_SEAMS, u as canDrain, y as conflictPolicy, f as connectionReducer, h as crdtMergeCell, n as crdtText, L as createDurableKv, N as createInMemoryKv, x as createInMemoryPersistence, Y as createInMemoryPresenceBus, X as createInMemoryPresenceChannel, I as createIndexedDbKv, O as createIndexedDbPersistence, m as createPresenceRoom, W as createQueryMirror, G as createSubscriptionMirrorBinding, l as createSyncClient, r as decodeCrdtText, d as defaultCrdtBackend, o as deriveSyncStatus, c as emptyCrdtState, e as initialConnection, p as initialSyncQueue, _ as lastWriteWins, A as loadPersistedSyncQueue, a as mergeCrdtStates, M as outboxPersistence, t as outstandingCount, q as resolveWithPolicy, s as syncQueueReducer, J as withDrainLock, i as yjsBackend };
|
package/dist/react.d.ts
CHANGED
|
@@ -78,9 +78,25 @@ declare interface CrdtDocHandle {
|
|
|
78
78
|
encodeUpdateSince: (sinceVector: Uint8Array) => CrdtState;
|
|
79
79
|
/** Fold an encoded state or incremental update into this doc, in place. */
|
|
80
80
|
applyState: (state: CrdtState) => void;
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Subscribe to update blobs this doc produces. Returns the unsubscribe.
|
|
83
|
+
*
|
|
84
|
+
* `local` distinguishes an edit made HERE from one produced by folding a
|
|
85
|
+
* remote state through `applyState`, and it is the difference between a
|
|
86
|
+
* working push loop and an echo: without it, applying a peer's update fires
|
|
87
|
+
* this handler, the app pushes it back, and every client re-broadcasts what
|
|
88
|
+
* it just received. Measured with three tabs open: one keystroke produced
|
|
89
|
+
* three server writes instead of one. It converges — the merge is
|
|
90
|
+
* idempotent — but the amplification scales with the session.
|
|
91
|
+
*
|
|
92
|
+
* It had to come from the BACKEND rather than an `applying` flag around
|
|
93
|
+
* `applyState` in app code: that flag is only correct while the backend emits
|
|
94
|
+
* synchronously, which `CrdtBackend` deliberately does not promise ("the
|
|
95
|
+
* backend decision lives behind our abstraction so it can change").
|
|
96
|
+
*/
|
|
97
|
+
onUpdate: (handler: (update: CrdtState, meta: {
|
|
98
|
+
readonly local: boolean;
|
|
99
|
+
}) => void) => () => void;
|
|
84
100
|
/**
|
|
85
101
|
* Encode a stable ANCHOR at `index` of the named text field — a position
|
|
86
102
|
* that survives concurrent edits (the primitive an inline-comment UI pins
|
|
@@ -100,9 +116,9 @@ declare interface CrdtDocKey {
|
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
/**
|
|
103
|
-
* One local edit, as
|
|
104
|
-
*
|
|
105
|
-
*
|
|
119
|
+
* One local edit, as a hook's `push` option receives it: the cell it belongs
|
|
120
|
+
* to, the encoded update to write, and how many times delivering it has already
|
|
121
|
+
* failed.
|
|
106
122
|
*
|
|
107
123
|
* `attempts` is carried because the sync client deliberately has no built-in
|
|
108
124
|
* backoff delay — spacing or jittering a retry is the transport's lever, and it
|
|
@@ -258,6 +274,95 @@ export declare interface UseConnectionStatus {
|
|
|
258
274
|
*/
|
|
259
275
|
export declare const useConnectionStatus: (outstanding?: number) => UseConnectionStatus;
|
|
260
276
|
|
|
277
|
+
export declare interface UseCrdtDoc {
|
|
278
|
+
/**
|
|
279
|
+
* The live document — pass it straight to `useCrdtEditor({ doc })`.
|
|
280
|
+
*
|
|
281
|
+
* `null` until the mount effect has run. That is not a papercut to design
|
|
282
|
+
* away: constructing it during render is what made the editor crash a
|
|
283
|
+
* prerender and leak an instance under StrictMode. Render the editor in a
|
|
284
|
+
* child component so its `useCrdtEditor` call is not conditional:
|
|
285
|
+
* `{doc.doc !== null ? <Editor doc={doc.doc} /> : null}`.
|
|
286
|
+
*
|
|
287
|
+
* Its encoded state is one call away (`doc.encodeState()`), which is why
|
|
288
|
+
* this hook exposes no separate `state` field — there would be two answers
|
|
289
|
+
* to one question.
|
|
290
|
+
*/
|
|
291
|
+
readonly doc: CrdtDocHandle | null;
|
|
292
|
+
/**
|
|
293
|
+
* `true` once an authoritative state has been folded in. Distinguishes an
|
|
294
|
+
* EMPTY document from one that has not arrived yet — the same distinction
|
|
295
|
+
* `remote: null` makes on the way in, surfaced so a UI can show a skeleton
|
|
296
|
+
* instead of an empty editor a user might start typing into.
|
|
297
|
+
*/
|
|
298
|
+
readonly loaded: boolean;
|
|
299
|
+
/** Writes not yet acknowledged. `0` means every local edit has landed. */
|
|
300
|
+
readonly outstanding: number;
|
|
301
|
+
/** `outstanding === 0`. */
|
|
302
|
+
readonly synced: boolean;
|
|
303
|
+
/** Report reachability. Going online drains the queue. */
|
|
304
|
+
readonly setOnline: (online: boolean) => void;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* A collaborative rich-text document bound to one `crdtDoc()` cell.
|
|
309
|
+
*
|
|
310
|
+
* ```tsx
|
|
311
|
+
* const row = useSubscription('app', 'documents.byId', { id })
|
|
312
|
+
* const save = useMutation('app', 'documents.setBody')
|
|
313
|
+
* const shared = useCrdtDoc({
|
|
314
|
+
* cell: { table: 'documents', id, column: 'body' },
|
|
315
|
+
* remote: row.data?.body ?? null,
|
|
316
|
+
* push: (w) => save.mutate({ id: w.id, update: w.update }),
|
|
317
|
+
* })
|
|
318
|
+
*
|
|
319
|
+
* // In a CHILD component, so `useCrdtEditor` is never called conditionally:
|
|
320
|
+
* const editor = useCrdtEditor({ doc })
|
|
321
|
+
* return <EditorContent editor={editor} />
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
export declare const useCrdtDoc: (options: UseCrdtDocOptions) => UseCrdtDoc;
|
|
325
|
+
|
|
326
|
+
export declare interface UseCrdtDocOptions {
|
|
327
|
+
/** The cell: table + row id + the `crdtDoc()` column. */
|
|
328
|
+
readonly cell: CrdtDocKey;
|
|
329
|
+
/**
|
|
330
|
+
* The authoritative state as the app currently sees it — the column's value
|
|
331
|
+
* from the reactive query streaming this row.
|
|
332
|
+
*
|
|
333
|
+
* `null`/`undefined` means "not loaded yet" and is folded as nothing, NOT as
|
|
334
|
+
* an empty document: treating a loading row as empty would let the first
|
|
335
|
+
* keystroke race the load and push a state that erases the stored document.
|
|
336
|
+
*/
|
|
337
|
+
readonly remote?: CrdtState | null | undefined;
|
|
338
|
+
/**
|
|
339
|
+
* Deliver a local edit to the server. Bind it to the mutation that writes the
|
|
340
|
+
* `crdtDoc()` column; the server folds it into the stored state
|
|
341
|
+
* authoritatively, so this never has to read-modify-write.
|
|
342
|
+
*
|
|
343
|
+
* What it receives is an INCREMENTAL Yjs update, not the full document — a
|
|
344
|
+
* one-character edit in a 100 KB document is a few dozen bytes.
|
|
345
|
+
*
|
|
346
|
+
* REJECT to trigger the queue's retry — a resolved promise is an ack.
|
|
347
|
+
*/
|
|
348
|
+
readonly push: (write: CrdtPush) => Promise<unknown>;
|
|
349
|
+
/**
|
|
350
|
+
* Durable local storage. Omit for an ephemeral (in-memory) client.
|
|
351
|
+
*
|
|
352
|
+
* Must be a STABLE reference — it is a construction option, so a new object
|
|
353
|
+
* each render rebuilds the sync client AND the document, dropping the queue.
|
|
354
|
+
* Build it once (`await createIndexedDbPersistence(…)`) and hold it in state
|
|
355
|
+
* or a module constant. Same for `backend`, `online` and `maxAttempts`.
|
|
356
|
+
*/
|
|
357
|
+
readonly adapter?: PersistenceAdapter;
|
|
358
|
+
/** CRDT backend for the local optimistic merge. Defaults to Yjs. */
|
|
359
|
+
readonly backend?: CrdtBackend;
|
|
360
|
+
/** Seed the connection belief (default `true`). */
|
|
361
|
+
readonly online?: boolean;
|
|
362
|
+
/** Stop re-driving a write after this many failed attempts. Default 5. */
|
|
363
|
+
readonly maxAttempts?: number;
|
|
364
|
+
}
|
|
365
|
+
|
|
261
366
|
export declare interface UseCrdtText {
|
|
262
367
|
/** The merged text — local edits and every peer's, converged. */
|
|
263
368
|
readonly text: string;
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, c as t, i as n, l as r, n as i, p as a, r as o, s, t as c } from "./room-
|
|
1
|
+
import { a as e, c as t, i as n, l as r, n as i, p as a, r as o, s, t as c } from "./room-24dGMM80.js";
|
|
2
2
|
import { useCallback as l, useEffect as u, useMemo as d, useReducer as f, useRef as p, useState as m } from "react";
|
|
3
3
|
//#region src/react/useSyncQueue.ts
|
|
4
4
|
var h = (e = !0) => {
|
|
@@ -97,7 +97,43 @@ var h = (e = !0) => {
|
|
|
97
97
|
_,
|
|
98
98
|
f
|
|
99
99
|
]);
|
|
100
|
-
}, x = (e
|
|
100
|
+
}, x = (e) => {
|
|
101
|
+
let { cell: t } = e, n = null, r = i({
|
|
102
|
+
transport: {
|
|
103
|
+
kind: "sync-transport",
|
|
104
|
+
push: async (t) => {
|
|
105
|
+
await e.push({
|
|
106
|
+
table: t.payload.table,
|
|
107
|
+
id: t.payload.id,
|
|
108
|
+
column: t.payload.column,
|
|
109
|
+
update: t.payload.update,
|
|
110
|
+
attempts: t.attempts
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
onRemoteState: (e) => (n = (n) => {
|
|
114
|
+
e({
|
|
115
|
+
...t,
|
|
116
|
+
state: n
|
|
117
|
+
});
|
|
118
|
+
}, () => {
|
|
119
|
+
n = null;
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
...e.adapter === void 0 ? {} : { adapter: e.adapter },
|
|
123
|
+
...e.backend === void 0 ? {} : { backend: e.backend },
|
|
124
|
+
...e.online === void 0 ? {} : { online: e.online },
|
|
125
|
+
...e.maxAttempts === void 0 ? {} : { maxAttempts: e.maxAttempts }
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
client: r,
|
|
129
|
+
deliverRemote: (e) => {
|
|
130
|
+
n?.(e);
|
|
131
|
+
},
|
|
132
|
+
close: () => {
|
|
133
|
+
r.close();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, S = (e) => `${e.table}\u0000${e.id}\u0000${e.column}`, C = (e, t) => {
|
|
101
137
|
if (e === t) return null;
|
|
102
138
|
let n = Math.min(e.length, t.length), r = 0;
|
|
103
139
|
for (; r < n && e[r] === t[r];) r += 1;
|
|
@@ -108,71 +144,53 @@ var h = (e = !0) => {
|
|
|
108
144
|
deleteLength: e.length - r - i,
|
|
109
145
|
insert: t.slice(r, t.length - i)
|
|
110
146
|
};
|
|
111
|
-
},
|
|
147
|
+
}, w = {
|
|
112
148
|
text: "",
|
|
113
149
|
state: void 0,
|
|
114
150
|
outstanding: 0
|
|
115
|
-
},
|
|
116
|
-
let { cell: t, remote: n, adapter: r, online:
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
|
|
120
|
-
let
|
|
151
|
+
}, T = (e) => {
|
|
152
|
+
let { cell: t, remote: n, adapter: r, online: i, maxAttempts: o } = e, s = e.backend ?? a, c = S(t), f = p(e.push);
|
|
153
|
+
f.current = e.push;
|
|
154
|
+
let h = p(t);
|
|
155
|
+
h.current = t;
|
|
156
|
+
let g = p(null), _ = p(null), v = p(null), [y, b] = m(w);
|
|
121
157
|
u(() => {
|
|
122
|
-
let t =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
push: async (e) => {
|
|
126
|
-
await h.current({
|
|
127
|
-
table: e.payload.table,
|
|
128
|
-
id: e.payload.id,
|
|
129
|
-
column: e.payload.column,
|
|
130
|
-
update: e.payload.update,
|
|
131
|
-
attempts: e.attempts
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
onRemoteState: (e) => (v.current = (n) => {
|
|
135
|
-
e({
|
|
136
|
-
...t,
|
|
137
|
-
state: n
|
|
138
|
-
});
|
|
139
|
-
}, () => {
|
|
140
|
-
v.current = null;
|
|
141
|
-
})
|
|
142
|
-
},
|
|
158
|
+
let t = h.current, n = x({
|
|
159
|
+
cell: t,
|
|
160
|
+
push: (e) => f.current(e),
|
|
143
161
|
...r === void 0 ? {} : { adapter: r },
|
|
144
162
|
...e.backend === void 0 ? {} : { backend: e.backend },
|
|
145
|
-
...
|
|
146
|
-
...
|
|
147
|
-
});
|
|
148
|
-
_.current = n,
|
|
149
|
-
let
|
|
150
|
-
let e =
|
|
151
|
-
|
|
163
|
+
...i === void 0 ? {} : { online: i },
|
|
164
|
+
...o === void 0 ? {} : { maxAttempts: o }
|
|
165
|
+
}), a = n.client;
|
|
166
|
+
g.current = a, _.current = n.deliverRemote, v.current = s.createText();
|
|
167
|
+
let c = () => {
|
|
168
|
+
let e = a.getText(t), n = a.outstanding();
|
|
169
|
+
b((r) => r.text === e && r.outstanding === n ? r : {
|
|
152
170
|
text: e,
|
|
153
|
-
state:
|
|
154
|
-
outstanding:
|
|
171
|
+
state: a.getState(t),
|
|
172
|
+
outstanding: n
|
|
155
173
|
});
|
|
156
|
-
}, l =
|
|
157
|
-
return
|
|
158
|
-
l(), n.close(),
|
|
174
|
+
}, l = a.subscribe(c);
|
|
175
|
+
return c(), () => {
|
|
176
|
+
l(), n.close(), g.current = null, v.current = null, _.current = null, b(w);
|
|
159
177
|
};
|
|
160
178
|
}, [
|
|
161
|
-
f,
|
|
162
|
-
r,
|
|
163
179
|
c,
|
|
180
|
+
r,
|
|
181
|
+
s,
|
|
164
182
|
e.backend,
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
i,
|
|
184
|
+
o
|
|
167
185
|
]), u(() => {
|
|
168
|
-
n != null &&
|
|
169
|
-
}, [
|
|
186
|
+
n != null && _.current?.(n);
|
|
187
|
+
}, [c, n]);
|
|
170
188
|
let T = l((e) => {
|
|
171
|
-
let t =
|
|
189
|
+
let t = g.current, n = v.current;
|
|
172
190
|
if (t === null || n === null) return;
|
|
173
|
-
let r = t.getState(
|
|
191
|
+
let r = t.getState(h.current);
|
|
174
192
|
r !== void 0 && n.applyState(r), e(n), t.enqueue({
|
|
175
|
-
...
|
|
193
|
+
...h.current,
|
|
176
194
|
update: n.encodeState()
|
|
177
195
|
});
|
|
178
196
|
}, []), E = l((e, t) => {
|
|
@@ -180,31 +198,85 @@ var h = (e = !0) => {
|
|
|
180
198
|
}, [T]), D = l((e, t) => {
|
|
181
199
|
t <= 0 || T((n) => n.delete(e, t));
|
|
182
200
|
}, [T]), O = l((e) => {
|
|
183
|
-
let t =
|
|
201
|
+
let t = g.current;
|
|
184
202
|
if (t === null) return;
|
|
185
|
-
let n =
|
|
203
|
+
let n = C(t.getText(h.current), e);
|
|
186
204
|
n !== null && T((e) => {
|
|
187
205
|
n.deleteLength > 0 && e.delete(n.index, n.deleteLength), n.insert !== "" && e.insert(n.index, n.insert);
|
|
188
206
|
});
|
|
189
207
|
}, [T]), k = l((e) => {
|
|
190
|
-
|
|
208
|
+
g.current?.setOnline(e);
|
|
191
209
|
}, []);
|
|
192
210
|
return d(() => ({
|
|
193
|
-
text:
|
|
194
|
-
state:
|
|
195
|
-
outstanding:
|
|
196
|
-
synced:
|
|
211
|
+
text: y.text,
|
|
212
|
+
state: y.state,
|
|
213
|
+
outstanding: y.outstanding,
|
|
214
|
+
synced: y.outstanding === 0,
|
|
197
215
|
insert: E,
|
|
198
216
|
delete: D,
|
|
199
217
|
setText: O,
|
|
200
218
|
setOnline: k
|
|
201
219
|
}), [
|
|
202
|
-
|
|
220
|
+
y,
|
|
203
221
|
E,
|
|
204
222
|
D,
|
|
205
223
|
O,
|
|
206
224
|
k
|
|
207
225
|
]);
|
|
226
|
+
}, E = (e) => {
|
|
227
|
+
let { cell: t, remote: n, adapter: r, online: i, maxAttempts: o } = e, s = e.backend ?? a, c = S(t), f = p(e.push);
|
|
228
|
+
f.current = e.push;
|
|
229
|
+
let h = p(t);
|
|
230
|
+
h.current = t;
|
|
231
|
+
let g = p(n);
|
|
232
|
+
g.current = n;
|
|
233
|
+
let _ = p(null), v = p(null), y = p(null), [b, C] = m(null), [w, T] = m(!1), [E, D] = m(0);
|
|
234
|
+
u(() => {
|
|
235
|
+
let t = h.current, n = x({
|
|
236
|
+
cell: t,
|
|
237
|
+
push: (e) => f.current(e),
|
|
238
|
+
...r === void 0 ? {} : { adapter: r },
|
|
239
|
+
...e.backend === void 0 ? {} : { backend: e.backend },
|
|
240
|
+
...i === void 0 ? {} : { online: i },
|
|
241
|
+
...o === void 0 ? {} : { maxAttempts: o }
|
|
242
|
+
}), a = n.client, c = s.createDoc();
|
|
243
|
+
_.current = a, y.current = c, v.current = n.deliverRemote;
|
|
244
|
+
let l = c.onUpdate((e, t) => {
|
|
245
|
+
t.local && a.enqueue({
|
|
246
|
+
...h.current,
|
|
247
|
+
update: e
|
|
248
|
+
});
|
|
249
|
+
}), u = () => {
|
|
250
|
+
D(a.outstanding());
|
|
251
|
+
}, d = a.subscribe(u), p = g.current;
|
|
252
|
+
return p != null && (n.deliverRemote(p), c.applyState(p), T(!0)), C(c), u(), () => {
|
|
253
|
+
l(), d(), n.close(), _.current = null, y.current = null, v.current = null, C(null), T(!1), D(0);
|
|
254
|
+
};
|
|
255
|
+
}, [
|
|
256
|
+
c,
|
|
257
|
+
r,
|
|
258
|
+
s,
|
|
259
|
+
e.backend,
|
|
260
|
+
i,
|
|
261
|
+
o
|
|
262
|
+
]), u(() => {
|
|
263
|
+
n != null && (v.current?.(n), y.current?.applyState(n), T(!0));
|
|
264
|
+
}, [c, n]);
|
|
265
|
+
let O = l((e) => {
|
|
266
|
+
_.current?.setOnline(e);
|
|
267
|
+
}, []);
|
|
268
|
+
return d(() => ({
|
|
269
|
+
doc: b,
|
|
270
|
+
loaded: w,
|
|
271
|
+
outstanding: E,
|
|
272
|
+
synced: E === 0,
|
|
273
|
+
setOnline: O
|
|
274
|
+
}), [
|
|
275
|
+
b,
|
|
276
|
+
w,
|
|
277
|
+
E,
|
|
278
|
+
O
|
|
279
|
+
]);
|
|
208
280
|
};
|
|
209
281
|
//#endregion
|
|
210
|
-
export {
|
|
282
|
+
export { C as crdtTextEdit, _ as useConnectionStatus, E as useCrdtDoc, T as useCrdtText, b as usePresence, h as useSyncQueue };
|