@kedataindo/docflow-core 0.0.71 → 0.0.72

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.
@@ -0,0 +1,109 @@
1
+ import { AnyExtension } from '@tiptap/core';
2
+ import { Awareness } from 'y-protocols/awareness';
3
+ import { IndexeddbPersistence } from 'y-indexeddb';
4
+ import { WebrtcProvider } from 'y-webrtc';
5
+ import { WebsocketProvider } from 'y-websocket';
6
+ import * as Y from 'yjs';
7
+
8
+ interface AwarenessState {
9
+ clientId: number;
10
+ user: {
11
+ name: string;
12
+ color: string;
13
+ };
14
+ cursor?: {
15
+ from: number;
16
+ to: number;
17
+ } | null;
18
+ /**
19
+ * Phase 9 PR2 — *present* flag. False when the local user has navigated
20
+ * away from the editor route (the Yjs provider is still alive but the
21
+ * host has gated cursor emission off, per the §6 *\"TOC sidebar orphan
22
+ * wiring\"* / REST-heartbeat scope notes). Peers see this instantly
23
+ * via the awareness `change` event — far faster than the 15s REST
24
+ * heartbeat fallback. Defaults to true when the user is in the editor.
25
+ */
26
+ present: boolean;
27
+ }
28
+ interface CollaborationOptions {
29
+ room: string;
30
+ provider?: 'webrtc' | 'websocket';
31
+ websocketUrl?: string;
32
+ signaling?: string[];
33
+ user: {
34
+ name: string;
35
+ color: string;
36
+ };
37
+ onAwarenessChange?: (states: AwarenessState[]) => void;
38
+ initialStorageState?: Uint8Array;
39
+ /**
40
+ * Phase 9 PR2 — when false, the local user is treated as
41
+ * out-of-editor: their cursor field is nulled and `present` is false.
42
+ * Default is true (in editor). Toggle at runtime via
43
+ * `setLocalCursorEnabled()` returned from `createCollaboration`.
44
+ */
45
+ emitCursor?: boolean;
46
+ /**
47
+ * Phase 9 OF1 — when true, mirror the room's Y.Doc into IndexedDB
48
+ * (`docflow-<room>`) via y-indexeddb so edits survive offline and reload.
49
+ * Reconcile-on-reconnect is plain Yjs merge — no conflict resolution
50
+ * needed. The IndexedDB mirror is NEVER a seed source: the server (or the
51
+ * legacy-JSON seed path) still owns initial state; the mirror only
52
+ * contributes local offline edits via the normal Yjs update exchange.
53
+ */
54
+ offline?: boolean;
55
+ }
56
+ interface CollaborationSetup {
57
+ ydoc: Y.Doc;
58
+ provider: WebrtcProvider | WebsocketProvider | null;
59
+ awareness: Awareness;
60
+ /**
61
+ * Phase 9 OF1 — y-indexeddb persistence instance, present only when
62
+ * `options.offline` is true. Exposed so hosts/tests can `await
63
+ * persistence.whenSynced` before asserting the mirror contents. Do not
64
+ * use it as a seed source.
65
+ */
66
+ persistence?: IndexeddbPersistence;
67
+ /**
68
+ * Issue #117 — resolves after BOTH the offline mirror (if `offline`) AND
69
+ * the provider's first sync (if `websocket`) have completed. Hosts MUST
70
+ * `await setup.whenReady` before binding the editor, otherwise the
71
+ * TipTap view can render against the IndexedDB cache before the
72
+ * authoritative server state arrives, briefly showing — and on a
73
+ * disconnected peer potentially re-broadcasting — nodes the server has
74
+ * since deleted. Resolves immediately when neither side applies.
75
+ */
76
+ whenReady: Promise<void>;
77
+ destroy: () => void;
78
+ /**
79
+ * Phase 9 PR2 — toggle the local cursor emission + present flag.
80
+ * Pass `false` when the editor route unmounts, `true` when it remounts.
81
+ * Triggers an awareness `change` event so peers see the leave/rejoin
82
+ * instantly (no REST-heartbeat lag).
83
+ */
84
+ setLocalCursorEnabled: (enabled: boolean) => void;
85
+ }
86
+ /**
87
+ * Build the collaboration runtime for a room.
88
+ *
89
+ * Async by design (issue fe-aktifai#230): the network sync providers are
90
+ * imported lazily — `y-webrtc` only when `provider: 'webrtc'`, `y-websocket`
91
+ * only when `provider: 'websocket'`, `y-indexeddb` only when `offline` is
92
+ * set. The main docflow barrel therefore never pulls the collab stack into
93
+ * bundles where collaboration is disabled; hosts that opt in simply
94
+ * `await createCollaboration(...)` and pass the resulting `CollaborationSetup`
95
+ * to `createEditor` / `<DocsEditor>`.
96
+ */
97
+ declare function createCollaboration(options: CollaborationOptions): Promise<CollaborationSetup>;
98
+ /**
99
+ * Build the TipTap extensions that bind an editor to a collaboration room.
100
+ *
101
+ * Takes a prebuilt `CollaborationSetup` only (issue fe-aktifai#230): the
102
+ * raw-options convenience path was removed because it required a
103
+ * *synchronous* provider construction, which is impossible now that
104
+ * `createCollaboration` lazy-imports `y-webrtc`/`y-websocket` and is async.
105
+ * Hosts must `await createCollaboration(...)` first and pass the result.
106
+ */
107
+ declare function collaborationExtensions(setup: CollaborationSetup): AnyExtension[];
108
+
109
+ export { type AwarenessState as A, type CollaborationOptions as C, type CollaborationSetup as a, createCollaboration as b, collaborationExtensions as c };
@@ -0,0 +1,109 @@
1
+ import { AnyExtension } from '@tiptap/core';
2
+ import { Awareness } from 'y-protocols/awareness';
3
+ import { IndexeddbPersistence } from 'y-indexeddb';
4
+ import { WebrtcProvider } from 'y-webrtc';
5
+ import { WebsocketProvider } from 'y-websocket';
6
+ import * as Y from 'yjs';
7
+
8
+ interface AwarenessState {
9
+ clientId: number;
10
+ user: {
11
+ name: string;
12
+ color: string;
13
+ };
14
+ cursor?: {
15
+ from: number;
16
+ to: number;
17
+ } | null;
18
+ /**
19
+ * Phase 9 PR2 — *present* flag. False when the local user has navigated
20
+ * away from the editor route (the Yjs provider is still alive but the
21
+ * host has gated cursor emission off, per the §6 *\"TOC sidebar orphan
22
+ * wiring\"* / REST-heartbeat scope notes). Peers see this instantly
23
+ * via the awareness `change` event — far faster than the 15s REST
24
+ * heartbeat fallback. Defaults to true when the user is in the editor.
25
+ */
26
+ present: boolean;
27
+ }
28
+ interface CollaborationOptions {
29
+ room: string;
30
+ provider?: 'webrtc' | 'websocket';
31
+ websocketUrl?: string;
32
+ signaling?: string[];
33
+ user: {
34
+ name: string;
35
+ color: string;
36
+ };
37
+ onAwarenessChange?: (states: AwarenessState[]) => void;
38
+ initialStorageState?: Uint8Array;
39
+ /**
40
+ * Phase 9 PR2 — when false, the local user is treated as
41
+ * out-of-editor: their cursor field is nulled and `present` is false.
42
+ * Default is true (in editor). Toggle at runtime via
43
+ * `setLocalCursorEnabled()` returned from `createCollaboration`.
44
+ */
45
+ emitCursor?: boolean;
46
+ /**
47
+ * Phase 9 OF1 — when true, mirror the room's Y.Doc into IndexedDB
48
+ * (`docflow-<room>`) via y-indexeddb so edits survive offline and reload.
49
+ * Reconcile-on-reconnect is plain Yjs merge — no conflict resolution
50
+ * needed. The IndexedDB mirror is NEVER a seed source: the server (or the
51
+ * legacy-JSON seed path) still owns initial state; the mirror only
52
+ * contributes local offline edits via the normal Yjs update exchange.
53
+ */
54
+ offline?: boolean;
55
+ }
56
+ interface CollaborationSetup {
57
+ ydoc: Y.Doc;
58
+ provider: WebrtcProvider | WebsocketProvider | null;
59
+ awareness: Awareness;
60
+ /**
61
+ * Phase 9 OF1 — y-indexeddb persistence instance, present only when
62
+ * `options.offline` is true. Exposed so hosts/tests can `await
63
+ * persistence.whenSynced` before asserting the mirror contents. Do not
64
+ * use it as a seed source.
65
+ */
66
+ persistence?: IndexeddbPersistence;
67
+ /**
68
+ * Issue #117 — resolves after BOTH the offline mirror (if `offline`) AND
69
+ * the provider's first sync (if `websocket`) have completed. Hosts MUST
70
+ * `await setup.whenReady` before binding the editor, otherwise the
71
+ * TipTap view can render against the IndexedDB cache before the
72
+ * authoritative server state arrives, briefly showing — and on a
73
+ * disconnected peer potentially re-broadcasting — nodes the server has
74
+ * since deleted. Resolves immediately when neither side applies.
75
+ */
76
+ whenReady: Promise<void>;
77
+ destroy: () => void;
78
+ /**
79
+ * Phase 9 PR2 — toggle the local cursor emission + present flag.
80
+ * Pass `false` when the editor route unmounts, `true` when it remounts.
81
+ * Triggers an awareness `change` event so peers see the leave/rejoin
82
+ * instantly (no REST-heartbeat lag).
83
+ */
84
+ setLocalCursorEnabled: (enabled: boolean) => void;
85
+ }
86
+ /**
87
+ * Build the collaboration runtime for a room.
88
+ *
89
+ * Async by design (issue fe-aktifai#230): the network sync providers are
90
+ * imported lazily — `y-webrtc` only when `provider: 'webrtc'`, `y-websocket`
91
+ * only when `provider: 'websocket'`, `y-indexeddb` only when `offline` is
92
+ * set. The main docflow barrel therefore never pulls the collab stack into
93
+ * bundles where collaboration is disabled; hosts that opt in simply
94
+ * `await createCollaboration(...)` and pass the resulting `CollaborationSetup`
95
+ * to `createEditor` / `<DocsEditor>`.
96
+ */
97
+ declare function createCollaboration(options: CollaborationOptions): Promise<CollaborationSetup>;
98
+ /**
99
+ * Build the TipTap extensions that bind an editor to a collaboration room.
100
+ *
101
+ * Takes a prebuilt `CollaborationSetup` only (issue fe-aktifai#230): the
102
+ * raw-options convenience path was removed because it required a
103
+ * *synchronous* provider construction, which is impossible now that
104
+ * `createCollaboration` lazy-imports `y-webrtc`/`y-websocket` and is async.
105
+ * Hosts must `await createCollaboration(...)` first and pass the result.
106
+ */
107
+ declare function collaborationExtensions(setup: CollaborationSetup): AnyExtension[];
108
+
109
+ export { type AwarenessState as A, type CollaborationOptions as C, type CollaborationSetup as a, createCollaboration as b, collaborationExtensions as c };
@@ -0,0 +1,140 @@
1
+ // src/Collaboration.ts
2
+ import { Collaboration } from "@tiptap/extension-collaboration";
3
+ import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
4
+ import { Awareness } from "y-protocols/awareness";
5
+ import * as Y from "yjs";
6
+ var LOCAL_SIGNALING = ["ws://localhost:4444"];
7
+ var warnedLocalSignaling = false;
8
+ function resolveSignalingUrls(signaling) {
9
+ if (signaling && signaling.length > 0) return signaling;
10
+ if (!warnedLocalSignaling) {
11
+ warnedLocalSignaling = true;
12
+ console.warn(
13
+ '[docflow] webrtc provider has no `signaling` configured \u2014 falling back to localhost-only sync (ws://localhost:4444 + same-browser tabs). For production self-host, run your own signaling server and pass `signaling`, or use `provider: "websocket"` with your own websocketUrl.'
14
+ );
15
+ }
16
+ return LOCAL_SIGNALING;
17
+ }
18
+ var LOCAL_PRESENT_DEFAULT = true;
19
+ function createAwarenessStates(awareness) {
20
+ const states = [];
21
+ awareness.getStates().forEach((state, clientId) => {
22
+ const raw = state;
23
+ states.push({
24
+ clientId,
25
+ user: raw.user ?? { name: "", color: "" },
26
+ cursor: raw.cursor ?? null,
27
+ // `present` defaults to true for backwards compat (legacy states
28
+ // don't set it) and for the local user (always considered present
29
+ // until PR2 toggles it off).
30
+ present: raw.present ?? LOCAL_PRESENT_DEFAULT
31
+ });
32
+ });
33
+ return states;
34
+ }
35
+ async function createCollaboration(options) {
36
+ const ydoc = new Y.Doc();
37
+ if (options.initialStorageState) {
38
+ Y.applyUpdate(ydoc, options.initialStorageState);
39
+ }
40
+ let persistence;
41
+ if (options.offline) {
42
+ const { IndexeddbPersistence } = await import("y-indexeddb");
43
+ persistence = new IndexeddbPersistence(`docflow-${options.room}`, ydoc);
44
+ }
45
+ let provider = null;
46
+ if (options.provider === "webrtc") {
47
+ const { WebrtcProvider } = await import("y-webrtc");
48
+ provider = new WebrtcProvider(options.room, ydoc, {
49
+ signaling: resolveSignalingUrls(options.signaling)
50
+ });
51
+ } else if (options.provider === "websocket") {
52
+ if (!options.websocketUrl) {
53
+ throw new Error('[Collaboration] websocketUrl is required when provider is "websocket"');
54
+ }
55
+ const { WebsocketProvider } = await import("y-websocket");
56
+ provider = new WebsocketProvider(options.websocketUrl, options.room, ydoc);
57
+ }
58
+ const awareness = provider?.awareness ?? new Awareness(ydoc);
59
+ awareness.setLocalStateField("user", options.user);
60
+ let emitCursor = options.emitCursor ?? true;
61
+ const setLocalCursorEnabled = (enabled) => {
62
+ if (emitCursor === enabled) return;
63
+ emitCursor = enabled;
64
+ if (!enabled) {
65
+ awareness.setLocalStateField("cursor", null);
66
+ }
67
+ awareness.setLocalStateField("emitCursor", enabled);
68
+ };
69
+ awareness.setLocalStateField("present", true);
70
+ awareness.setLocalStateField("emitCursor", emitCursor);
71
+ let awarenessHandler;
72
+ if (options.onAwarenessChange) {
73
+ const notify = () => {
74
+ options.onAwarenessChange?.(createAwarenessStates(awareness));
75
+ };
76
+ awarenessHandler = notify;
77
+ awareness.on("change", notify);
78
+ notify();
79
+ }
80
+ const readyParts = [];
81
+ if (persistence) {
82
+ readyParts.push(
83
+ persistence.whenSynced.then(
84
+ () => void 0,
85
+ () => void 0
86
+ )
87
+ );
88
+ }
89
+ if (provider && options.provider === "websocket") {
90
+ const ws = provider;
91
+ readyParts.push(
92
+ new Promise((resolve) => {
93
+ let settled = false;
94
+ const done = () => {
95
+ if (settled) return;
96
+ settled = true;
97
+ resolve();
98
+ };
99
+ if (ws.synced) {
100
+ done();
101
+ return;
102
+ }
103
+ ws.once("sync", done);
104
+ ws.once("synced", done);
105
+ setTimeout(done, 5e3);
106
+ })
107
+ );
108
+ }
109
+ const whenReady = readyParts.length === 0 ? Promise.resolve() : Promise.all(readyParts).then(() => void 0);
110
+ const destroy = () => {
111
+ if (awarenessHandler) {
112
+ awareness.off("change", awarenessHandler);
113
+ }
114
+ awareness.setLocalState(null);
115
+ provider?.destroy?.();
116
+ awareness.destroy?.();
117
+ void persistence?.destroy();
118
+ ydoc.destroy();
119
+ };
120
+ return { ydoc, provider, awareness, persistence, whenReady, destroy, setLocalCursorEnabled };
121
+ }
122
+ function collaborationExtensions(setup) {
123
+ const extensions = [
124
+ Collaboration.configure({ document: setup.ydoc })
125
+ ];
126
+ if (setup.provider) {
127
+ extensions.push(
128
+ CollaborationCursor.configure({
129
+ provider: setup.provider,
130
+ user: setup.awareness.getLocalState()?.user ?? { name: "", color: "" }
131
+ })
132
+ );
133
+ }
134
+ return extensions;
135
+ }
136
+
137
+ export {
138
+ createCollaboration,
139
+ collaborationExtensions
140
+ };
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/collab/index.ts
31
+ var collab_exports = {};
32
+ __export(collab_exports, {
33
+ SubdocumentProvider: () => SubdocumentProvider,
34
+ collaborationExtensions: () => collaborationExtensions,
35
+ createCollaboration: () => createCollaboration
36
+ });
37
+ module.exports = __toCommonJS(collab_exports);
38
+
39
+ // src/SubdocumentProvider.ts
40
+ var import_awareness = require("y-protocols/awareness");
41
+ var import_y_websocket = require("y-websocket");
42
+ var Y = __toESM(require("yjs"), 1);
43
+ var SubdocumentProvider = class {
44
+ parentDoc;
45
+ subdocs;
46
+ awareness;
47
+ options;
48
+ states = /* @__PURE__ */ new Map();
49
+ activeQueue = [];
50
+ parentProvider = null;
51
+ constructor(options) {
52
+ this.options = {
53
+ maxActive: options.maxActive ?? 5,
54
+ onStateChange: options.onStateChange ?? (() => {
55
+ }),
56
+ ...options
57
+ };
58
+ this.parentDoc = new Y.Doc();
59
+ this.subdocs = this.parentDoc.getMap("subdocs");
60
+ this.awareness = new import_awareness.Awareness(this.parentDoc);
61
+ this.awareness.setLocalStateField("user", this.options.user);
62
+ this.parentProvider = new import_y_websocket.WebsocketProvider(
63
+ this.options.websocketUrl,
64
+ `${this.options.roomPrefix}/_parent`,
65
+ this.parentDoc
66
+ );
67
+ }
68
+ /** Create a new subdocument. Does NOT activate it. */
69
+ createSubdoc(id, initialState) {
70
+ if (this.states.has(id)) return this.states.get(id).doc;
71
+ const doc = new Y.Doc();
72
+ if (initialState) Y.applyUpdate(doc, initialState);
73
+ doc.getXmlFragment("default");
74
+ this.subdocs.set(id, doc);
75
+ this.states.set(id, {
76
+ id,
77
+ doc,
78
+ provider: null,
79
+ active: false
80
+ });
81
+ this.options.onStateChange(this.getAllStates());
82
+ return doc;
83
+ }
84
+ /** Activate a subdocument — connects it to the network for live sync. */
85
+ activate(id) {
86
+ const state = this.states.get(id);
87
+ if (!state || state.active) return;
88
+ if (this.activeQueue.length >= this.options.maxActive) {
89
+ const oldest = this.activeQueue.shift();
90
+ if (oldest) this.deactivate(oldest);
91
+ }
92
+ const doc = state.doc;
93
+ const roomName = `${this.options.roomPrefix}/${id}`;
94
+ const provider = new import_y_websocket.WebsocketProvider(
95
+ this.options.websocketUrl,
96
+ roomName,
97
+ doc
98
+ );
99
+ provider.awareness.setLocalStateField("user", this.options.user);
100
+ state.provider = provider;
101
+ state.active = true;
102
+ this.activeQueue.push(id);
103
+ this.options.onStateChange(this.getAllStates());
104
+ }
105
+ /** Deactivate a subdocument — disconnects it from the network. */
106
+ deactivate(id) {
107
+ const state = this.states.get(id);
108
+ if (!state || !state.active) return;
109
+ state.provider?.destroy();
110
+ state.provider = null;
111
+ state.active = false;
112
+ const idx = this.activeQueue.indexOf(id);
113
+ if (idx >= 0) this.activeQueue.splice(idx, 1);
114
+ this.options.onStateChange(this.getAllStates());
115
+ }
116
+ /** Get the Y.XmlFragment for a subdocument. */
117
+ getFragment(id) {
118
+ const doc = this.states.get(id)?.doc ?? this.subdocs.get(id);
119
+ if (!doc) return null;
120
+ return doc.getXmlFragment("default");
121
+ }
122
+ /** Check if a subdocument is currently active (syncing). */
123
+ isActive(id) {
124
+ return this.states.get(id)?.active ?? false;
125
+ }
126
+ /** Get all subdocument states. */
127
+ getAllStates() {
128
+ return Array.from(this.states.values());
129
+ }
130
+ /** Get IDs of all active subdocuments. */
131
+ getActiveIds() {
132
+ return [...this.activeQueue];
133
+ }
134
+ /** Destroy all subdocuments and providers. */
135
+ destroy() {
136
+ for (const [id] of this.states) {
137
+ this.deactivate(id);
138
+ }
139
+ this.parentProvider?.destroy();
140
+ this.parentDoc.destroy();
141
+ this.states.clear();
142
+ this.activeQueue.length = 0;
143
+ }
144
+ };
145
+
146
+ // src/Collaboration.ts
147
+ var import_extension_collaboration = require("@tiptap/extension-collaboration");
148
+ var import_extension_collaboration_cursor = require("@tiptap/extension-collaboration-cursor");
149
+ var import_awareness2 = require("y-protocols/awareness");
150
+ var Y2 = __toESM(require("yjs"), 1);
151
+ var LOCAL_SIGNALING = ["ws://localhost:4444"];
152
+ var warnedLocalSignaling = false;
153
+ function resolveSignalingUrls(signaling) {
154
+ if (signaling && signaling.length > 0) return signaling;
155
+ if (!warnedLocalSignaling) {
156
+ warnedLocalSignaling = true;
157
+ console.warn(
158
+ '[docflow] webrtc provider has no `signaling` configured \u2014 falling back to localhost-only sync (ws://localhost:4444 + same-browser tabs). For production self-host, run your own signaling server and pass `signaling`, or use `provider: "websocket"` with your own websocketUrl.'
159
+ );
160
+ }
161
+ return LOCAL_SIGNALING;
162
+ }
163
+ var LOCAL_PRESENT_DEFAULT = true;
164
+ function createAwarenessStates(awareness) {
165
+ const states = [];
166
+ awareness.getStates().forEach((state, clientId) => {
167
+ const raw = state;
168
+ states.push({
169
+ clientId,
170
+ user: raw.user ?? { name: "", color: "" },
171
+ cursor: raw.cursor ?? null,
172
+ // `present` defaults to true for backwards compat (legacy states
173
+ // don't set it) and for the local user (always considered present
174
+ // until PR2 toggles it off).
175
+ present: raw.present ?? LOCAL_PRESENT_DEFAULT
176
+ });
177
+ });
178
+ return states;
179
+ }
180
+ async function createCollaboration(options) {
181
+ const ydoc = new Y2.Doc();
182
+ if (options.initialStorageState) {
183
+ Y2.applyUpdate(ydoc, options.initialStorageState);
184
+ }
185
+ let persistence;
186
+ if (options.offline) {
187
+ const { IndexeddbPersistence } = await import("y-indexeddb");
188
+ persistence = new IndexeddbPersistence(`docflow-${options.room}`, ydoc);
189
+ }
190
+ let provider = null;
191
+ if (options.provider === "webrtc") {
192
+ const { WebrtcProvider } = await import("y-webrtc");
193
+ provider = new WebrtcProvider(options.room, ydoc, {
194
+ signaling: resolveSignalingUrls(options.signaling)
195
+ });
196
+ } else if (options.provider === "websocket") {
197
+ if (!options.websocketUrl) {
198
+ throw new Error('[Collaboration] websocketUrl is required when provider is "websocket"');
199
+ }
200
+ const { WebsocketProvider: WebsocketProvider2 } = await import("y-websocket");
201
+ provider = new WebsocketProvider2(options.websocketUrl, options.room, ydoc);
202
+ }
203
+ const awareness = provider?.awareness ?? new import_awareness2.Awareness(ydoc);
204
+ awareness.setLocalStateField("user", options.user);
205
+ let emitCursor = options.emitCursor ?? true;
206
+ const setLocalCursorEnabled = (enabled) => {
207
+ if (emitCursor === enabled) return;
208
+ emitCursor = enabled;
209
+ if (!enabled) {
210
+ awareness.setLocalStateField("cursor", null);
211
+ }
212
+ awareness.setLocalStateField("emitCursor", enabled);
213
+ };
214
+ awareness.setLocalStateField("present", true);
215
+ awareness.setLocalStateField("emitCursor", emitCursor);
216
+ let awarenessHandler;
217
+ if (options.onAwarenessChange) {
218
+ const notify = () => {
219
+ options.onAwarenessChange?.(createAwarenessStates(awareness));
220
+ };
221
+ awarenessHandler = notify;
222
+ awareness.on("change", notify);
223
+ notify();
224
+ }
225
+ const readyParts = [];
226
+ if (persistence) {
227
+ readyParts.push(
228
+ persistence.whenSynced.then(
229
+ () => void 0,
230
+ () => void 0
231
+ )
232
+ );
233
+ }
234
+ if (provider && options.provider === "websocket") {
235
+ const ws = provider;
236
+ readyParts.push(
237
+ new Promise((resolve) => {
238
+ let settled = false;
239
+ const done = () => {
240
+ if (settled) return;
241
+ settled = true;
242
+ resolve();
243
+ };
244
+ if (ws.synced) {
245
+ done();
246
+ return;
247
+ }
248
+ ws.once("sync", done);
249
+ ws.once("synced", done);
250
+ setTimeout(done, 5e3);
251
+ })
252
+ );
253
+ }
254
+ const whenReady = readyParts.length === 0 ? Promise.resolve() : Promise.all(readyParts).then(() => void 0);
255
+ const destroy = () => {
256
+ if (awarenessHandler) {
257
+ awareness.off("change", awarenessHandler);
258
+ }
259
+ awareness.setLocalState(null);
260
+ provider?.destroy?.();
261
+ awareness.destroy?.();
262
+ void persistence?.destroy();
263
+ ydoc.destroy();
264
+ };
265
+ return { ydoc, provider, awareness, persistence, whenReady, destroy, setLocalCursorEnabled };
266
+ }
267
+ function collaborationExtensions(setup) {
268
+ const extensions = [
269
+ import_extension_collaboration.Collaboration.configure({ document: setup.ydoc })
270
+ ];
271
+ if (setup.provider) {
272
+ extensions.push(
273
+ import_extension_collaboration_cursor.CollaborationCursor.configure({
274
+ provider: setup.provider,
275
+ user: setup.awareness.getLocalState()?.user ?? { name: "", color: "" }
276
+ })
277
+ );
278
+ }
279
+ return extensions;
280
+ }
281
+ // Annotate the CommonJS export names for ESM import in node:
282
+ 0 && (module.exports = {
283
+ SubdocumentProvider,
284
+ collaborationExtensions,
285
+ createCollaboration
286
+ });