edytor 5.0.0 → 6.0.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.
Files changed (75) hide show
  1. package/README.md +2 -2
  2. package/dist/cloudflare/room/context.d.ts +62 -74
  3. package/dist/crdt/doc/history.js +2 -3
  4. package/dist/crdt/doc/ops.d.ts +81 -0
  5. package/dist/crdt/doc/ops.js +1898 -0
  6. package/dist/crdt/edytor-doc.d.ts +156 -156
  7. package/dist/crdt/edytor-doc.js +14 -42
  8. package/dist/crdt/flow.d.ts +4 -46
  9. package/dist/crdt/flow.js +2 -262
  10. package/dist/crdt/index.d.ts +62 -74
  11. package/dist/crdt/operations.d.ts +1 -1
  12. package/dist/crdt/placement/resolve.d.ts +1 -1
  13. package/dist/crdt/protocols/awareness.js +46 -81
  14. package/dist/crdt/providers/index.d.ts +10 -22
  15. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  16. package/dist/crdt/providers/indexeddb.js +10 -16
  17. package/dist/crdt/providers/room.d.ts +11 -0
  18. package/dist/crdt/providers/room.js +26 -12
  19. package/dist/crdt/providers/websocket.d.ts +6 -12
  20. package/dist/crdt/providers/websocket.js +8 -15
  21. package/dist/crdt/rangeDelete.d.ts +2 -47
  22. package/dist/crdt/rangeDelete.js +1 -169
  23. package/dist/crdt/text/index/build.d.ts +125 -0
  24. package/dist/crdt/text/index/build.js +2895 -0
  25. package/dist/crdt/text/index/shared.d.ts +2 -6
  26. package/dist/crdt/text/index/shared.js +2 -5
  27. package/dist/crdt/text/model.js +2 -3
  28. package/dist/crdt/text/runs.d.ts +9 -12
  29. package/dist/crdt/text/runs.js +2 -165
  30. package/dist/events/domTextOffset.d.ts +2 -0
  31. package/dist/events/domTextOffset.js +8 -0
  32. package/dist/export/forms.d.ts +50 -50
  33. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  34. package/dist/plugins/richtext/richTextOperations.js +26 -32
  35. package/dist/selection/selection.utils.js +5 -8
  36. package/dist/session/handles.js +3 -7
  37. package/dist/surface/observer.svelte.js +15 -30
  38. package/dist/surface/projector.svelte.js +2 -2
  39. package/dist/utils.d.ts +5 -0
  40. package/dist/utils.js +7 -0
  41. package/package.json +1 -1
  42. package/dist/crdt/doc/capability.d.ts +0 -56
  43. package/dist/crdt/doc/capability.js +0 -207
  44. package/dist/crdt/doc/content.d.ts +0 -16
  45. package/dist/crdt/doc/content.js +0 -77
  46. package/dist/crdt/doc/delete.d.ts +0 -15
  47. package/dist/crdt/doc/delete.js +0 -84
  48. package/dist/crdt/doc/layout.d.ts +0 -9
  49. package/dist/crdt/doc/layout.js +0 -178
  50. package/dist/crdt/doc/meta.d.ts +0 -19
  51. package/dist/crdt/doc/meta.js +0 -198
  52. package/dist/crdt/doc/moves.d.ts +0 -21
  53. package/dist/crdt/doc/moves.js +0 -274
  54. package/dist/crdt/doc/split-merge.d.ts +0 -10
  55. package/dist/crdt/doc/split-merge.js +0 -179
  56. package/dist/crdt/doc/steps.d.ts +0 -65
  57. package/dist/crdt/doc/steps.js +0 -311
  58. package/dist/crdt/text/index/cache.d.ts +0 -16
  59. package/dist/crdt/text/index/cache.js +0 -175
  60. package/dist/crdt/text/index/claims.d.ts +0 -18
  61. package/dist/crdt/text/index/claims.js +0 -203
  62. package/dist/crdt/text/index/fold.d.ts +0 -21
  63. package/dist/crdt/text/index/fold.js +0 -656
  64. package/dist/crdt/text/index/layout.d.ts +0 -22
  65. package/dist/crdt/text/index/layout.js +0 -258
  66. package/dist/crdt/text/index/placement.d.ts +0 -47
  67. package/dist/crdt/text/index/placement.js +0 -738
  68. package/dist/crdt/text/index/records.d.ts +0 -13
  69. package/dist/crdt/text/index/records.js +0 -139
  70. package/dist/crdt/text/index/report.d.ts +0 -18
  71. package/dist/crdt/text/index/report.js +0 -337
  72. package/dist/crdt/text/index/state.d.ts +0 -197
  73. package/dist/crdt/text/index/state.js +0 -116
  74. package/dist/crdt/text/index/streams.d.ts +0 -25
  75. package/dist/crdt/text/index/streams.js +0 -248
@@ -94,41 +94,19 @@ export class Awareness extends IsolatedObservable {
94
94
  }
95
95
  setLocalState(state) {
96
96
  const clientID = this.clientID;
97
- const currLocalMeta = this.meta.get(clientID);
98
- const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
99
97
  const prevState = this.states.get(clientID);
100
- if (state === null) {
101
- this.states.delete(clientID);
102
- }
98
+ put(this, clientID, (this.meta.get(clientID)?.clock ?? -1) + 1, state, time.getUnixTime());
99
+ const changes = noChanges();
100
+ if (state === null)
101
+ changes.removed.push(clientID);
102
+ else if (prevState == null)
103
+ changes.added.push(clientID);
103
104
  else {
104
- this.states.set(clientID, state);
105
- }
106
- this.meta.set(clientID, {
107
- clock,
108
- lastUpdated: time.getUnixTime()
109
- });
110
- const added = [];
111
- const updated = [];
112
- const filteredUpdated = [];
113
- const removed = [];
114
- if (state === null) {
115
- removed.push(clientID);
105
+ changes.updated.push(clientID);
106
+ if (!jsonEquals(prevState, state))
107
+ changes.changed.push(clientID);
116
108
  }
117
- else if (prevState == null) {
118
- if (state != null) {
119
- added.push(clientID);
120
- }
121
- }
122
- else {
123
- updated.push(clientID);
124
- if (!jsonEquals(prevState, state)) {
125
- filteredUpdated.push(clientID);
126
- }
127
- }
128
- if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
129
- this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
130
- }
131
- this.emit('update', [{ added, updated, removed }, 'local']);
109
+ emitChanges(this, changes, 'local', true);
132
110
  }
133
111
  setLocalStateField(field, value) {
134
112
  const state = this.getLocalState();
@@ -143,30 +121,40 @@ export class Awareness extends IsolatedObservable {
143
121
  return this.states;
144
122
  }
145
123
  }
124
+ const noChanges = () => ({ added: [], updated: [], changed: [], removed: [] });
125
+ /** Write client `clientID`'s `state` (`null`: removed) at `clock`, heard `now`. */
126
+ const put = (awareness, clientID, clock, state, now) => {
127
+ if (state === null)
128
+ awareness.states.delete(clientID);
129
+ else
130
+ awareness.states.set(clientID, state);
131
+ awareness.meta.set(clientID, { clock, lastUpdated: now });
132
+ };
133
+ /**
134
+ * Emit a change: `change` when a client was added, removed or changed its
135
+ * state, `update` when one was touched at all (`always`: even when none was).
136
+ */
137
+ const emitChanges = (awareness, { added, updated, changed, removed }, origin, always = false) => {
138
+ if (added.length > 0 || changed.length > 0 || removed.length > 0)
139
+ awareness.emit('change', [{ added, updated: changed, removed }, origin]);
140
+ if (always || added.length > 0 || updated.length > 0 || removed.length > 0)
141
+ awareness.emit('update', [{ added, updated, removed }, origin]);
142
+ };
146
143
  /**
147
144
  * Mark (remote) clients as inactive and remove them from the list of active
148
145
  * peers. This change is propagated to remote clients.
149
146
  */
150
147
  export const removeAwarenessStates = (awareness, clients, origin) => {
151
- const removed = [];
152
- for (let i = 0; i < clients.length; i++) {
153
- const clientID = clients[i];
148
+ const changes = noChanges();
149
+ for (const clientID of clients) {
154
150
  if (awareness.states.has(clientID)) {
155
151
  awareness.states.delete(clientID);
156
- if (clientID === awareness.clientID) {
157
- const curMeta = awareness.meta.get(clientID);
158
- awareness.meta.set(clientID, {
159
- clock: curMeta.clock + 1,
160
- lastUpdated: time.getUnixTime()
161
- });
162
- }
163
- removed.push(clientID);
152
+ if (clientID === awareness.clientID)
153
+ put(awareness, clientID, awareness.meta.get(clientID).clock + 1, null, time.getUnixTime());
154
+ changes.removed.push(clientID);
164
155
  }
165
156
  }
166
- if (removed.length > 0) {
167
- awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
168
- awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
169
- }
157
+ emitChanges(awareness, changes, origin);
170
158
  };
171
159
  /** The update carrying `clients`' entries of `awareness` (their states in `states`). */
172
160
  export const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => writeAwarenessEntries(clients.map((clientID) => ({
@@ -202,36 +190,19 @@ export const writeAwarenessEntries = (entries) => encoding.encode((encoder) => {
202
190
  */
203
191
  export const applyAwarenessUpdate = (awareness, update, origin) => {
204
192
  const timestamp = time.getUnixTime();
205
- const added = [];
206
- const updated = [];
207
- const filteredUpdated = [];
208
- const removed = [];
209
- for (const entry of readAwarenessEntries(update)) {
210
- const { clientID, state } = entry;
211
- let { clock } = entry;
193
+ const changes = noChanges();
194
+ const { added, updated, changed, removed } = changes;
195
+ for (const { clientID, clock, state } of readAwarenessEntries(update)) {
212
196
  const clientMeta = awareness.meta.get(clientID);
213
197
  const prevState = awareness.states.get(clientID);
214
198
  const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
215
199
  if (currClock < clock ||
216
200
  (currClock === clock && state === null && awareness.states.has(clientID))) {
217
- if (state === null) {
218
- // never let a remote client remove this local state
219
- if (clientID === awareness.clientID && awareness.getLocalState() != null) {
220
- // remote client removed the local state. Do not remove state. Broadcast a message
221
- // indicating that this client still exists by increasing the clock
222
- clock++;
223
- }
224
- else {
225
- awareness.states.delete(clientID);
226
- }
227
- }
228
- else {
229
- awareness.states.set(clientID, state);
230
- }
231
- awareness.meta.set(clientID, {
232
- clock,
233
- lastUpdated: timestamp
234
- });
201
+ // Never let a remote client remove this local state: keep it and
202
+ // broadcast that this client still exists, by increasing the clock.
203
+ const local = awareness.getLocalState();
204
+ const keep = state === null && clientID === awareness.clientID && local != null;
205
+ put(awareness, clientID, keep ? clock + 1 : clock, keep ? local : state, timestamp);
235
206
  if (clientMeta === undefined && state !== null) {
236
207
  added.push(clientID);
237
208
  }
@@ -239,17 +210,11 @@ export const applyAwarenessUpdate = (awareness, update, origin) => {
239
210
  removed.push(clientID);
240
211
  }
241
212
  else if (state !== null) {
242
- if (!jsonEquals(state, prevState)) {
243
- filteredUpdated.push(clientID);
244
- }
213
+ if (!jsonEquals(state, prevState))
214
+ changed.push(clientID);
245
215
  updated.push(clientID);
246
216
  }
247
217
  }
248
218
  }
249
- if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
250
- awareness.emit('change', [{ added, updated: filteredUpdated, removed }, origin]);
251
- }
252
- if (added.length > 0 || updated.length > 0 || removed.length > 0) {
253
- awareness.emit('update', [{ added, updated, removed }, origin]);
254
- }
219
+ emitChanges(awareness, changes, origin);
255
220
  };
@@ -147,17 +147,12 @@ export declare const bindProviders: (Y: EngineApi) => {
147
147
  loadError: unknown;
148
148
  _storeTimeout: number;
149
149
  _storeTimeoutId: ReturnType<typeof setTimeout> | null;
150
- _storeUpdate: (update: Uint8Array, origin: unknown) => void;
150
+ _unlisten: () => void;
151
151
  awareness: Awareness;
152
152
  bcconnected: boolean;
153
153
  disableBc: boolean;
154
154
  _ownsAwareness: boolean;
155
155
  _bcSubscriber: (data: ArrayBuffer | Uint8Array, origin: unknown) => void;
156
- _awarenessUpdateHandler: (updates: {
157
- added: number[];
158
- updated: number[];
159
- removed: number[];
160
- }, origin: unknown) => void;
161
156
  get synced(): boolean;
162
157
  messageHandlers: Record<number, import("./room.js").RoomMessageHandler</*elided*/ any>>;
163
158
  readMessage: (buf: Uint8Array, emitSynced: boolean) => import("lib0-v14/encoding").Encoder;
@@ -165,7 +160,7 @@ export declare const bindProviders: (Y: EngineApi) => {
165
160
  connectBc(): void;
166
161
  disconnectBc(): void;
167
162
  destroy(): Promise<void>;
168
- emit<N extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
163
+ emit<N extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
169
164
  synced: (provider: /*elided*/ any) => void;
170
165
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
171
166
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -174,7 +169,7 @@ export declare const bindProviders: (Y: EngineApi) => {
174
169
  failed: (error: unknown, provider: /*elided*/ any) => void;
175
170
  }[N]>): void;
176
171
  _observers: Map<string, Set<any>>;
177
- on<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
172
+ on<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
178
173
  synced: (provider: /*elided*/ any) => void;
179
174
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
180
175
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -189,7 +184,7 @@ export declare const bindProviders: (Y: EngineApi) => {
189
184
  'schema-mismatch': (detail: import("./room.js").SchemaMismatchDetail, provider: /*elided*/ any) => void;
190
185
  failed: (error: unknown, provider: /*elided*/ any) => void;
191
186
  }[NAME];
192
- once<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
187
+ once<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
193
188
  synced: (provider: /*elided*/ any) => void;
194
189
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
195
190
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -197,7 +192,7 @@ export declare const bindProviders: (Y: EngineApi) => {
197
192
  'schema-mismatch': (detail: import("./room.js").SchemaMismatchDetail, provider: /*elided*/ any) => void;
198
193
  failed: (error: unknown, provider: /*elided*/ any) => void;
199
194
  }[NAME]): void;
200
- off<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
195
+ off<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
201
196
  synced: (provider: /*elided*/ any) => void;
202
197
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
203
198
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -258,14 +253,7 @@ export declare const bindProviders: (Y: EngineApi) => {
258
253
  _resync: ReturnType<typeof setInterval> | undefined;
259
254
  _connectTimer: ReturnType<typeof setTimeout> | undefined;
260
255
  _checkInterval: ReturnType<typeof setInterval>;
261
- _updateHandler: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
262
- local: boolean;
263
- }) => void;
264
- _awarenessUpdateHandler: (updates: {
265
- added: number[];
266
- updated: number[];
267
- removed: number[];
268
- }, origin: unknown) => void;
256
+ _unlisten: () => void;
269
257
  get url(): string;
270
258
  get unsaved(): number;
271
259
  get saved(): boolean;
@@ -275,11 +263,11 @@ export declare const bindProviders: (Y: EngineApi) => {
275
263
  destroy(): void;
276
264
  disconnect(): void;
277
265
  connect(): void;
278
- emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
266
+ emit<N extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
279
267
  _observers: Map<string, Set<any>>;
280
- on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
281
- once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
282
- off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
268
+ on<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
269
+ once<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
270
+ off<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
283
271
  };
284
272
  };
285
273
  watchComments: (server: string | null, roomName: string, listener: (message: import("../protocol.js").CommentMessage) => void) => (() => void);
@@ -45,17 +45,13 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
45
45
  loadError: unknown;
46
46
  _storeTimeout: number;
47
47
  _storeTimeoutId: ReturnType<typeof setTimeout> | null;
48
- _storeUpdate: (update: Uint8Array, origin: unknown) => void;
48
+ /** @internal Removes the document and presence listeners (`room.listen`). */
49
+ _unlisten: () => void;
49
50
  awareness: Awareness;
50
51
  bcconnected: boolean;
51
52
  disableBc: boolean;
52
53
  _ownsAwareness: boolean;
53
54
  _bcSubscriber: (data: ArrayBuffer | Uint8Array, origin: unknown) => void;
54
- _awarenessUpdateHandler: (updates: {
55
- added: number[];
56
- updated: number[];
57
- removed: number[];
58
- }, origin: unknown) => void;
59
55
  /** The lifetime hydration claim. */
60
56
  get synced(): boolean;
61
57
  /**
@@ -78,7 +74,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
78
74
  * close the DB. Does NOT remove stored data — `clearDocument` does.
79
75
  */
80
76
  destroy(): Promise<void>;
81
- emit<N extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
77
+ emit<N extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
82
78
  synced: (provider: /*elided*/ any) => void;
83
79
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
84
80
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -101,7 +97,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
101
97
  failed: (error: unknown, provider: /*elided*/ any) => void;
102
98
  }[N]>): void;
103
99
  _observers: Map<string, Set<any>>;
104
- on<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
100
+ on<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
105
101
  synced: (provider: /*elided*/ any) => void;
106
102
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
107
103
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -144,7 +140,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
144
140
  */
145
141
  failed: (error: unknown, provider: /*elided*/ any) => void;
146
142
  }[NAME];
147
- once<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
143
+ once<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
148
144
  synced: (provider: /*elided*/ any) => void;
149
145
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
150
146
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -166,7 +162,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
166
162
  */
167
163
  failed: (error: unknown, provider: /*elided*/ any) => void;
168
164
  }[NAME]): void;
169
- off<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
165
+ off<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
170
166
  synced: (provider: /*elided*/ any) => void;
171
167
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
172
168
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -41,7 +41,7 @@ import { IsolatedObservable } from '../protocols/observable.js';
41
41
  import { bindSync } from '../protocols/sync.js';
42
42
  import { generationDbName } from '../protocols/envelope.js';
43
43
  import { CUSTOM as customStoreName, encodeRow, lastKey, openContainer, readRow, rtop, snapshotRow, storesOf, UPDATES as updatesStoreName, verifyOrStamp } from './container.js';
44
- import { beginDestroy, bindRoomProtocol, tabs, emitFailed, initLifecycle, markSynced, quarantined } from './room.js';
44
+ import { beginDestroy, bindRoomProtocol, tabs, emitFailed, fault, initLifecycle, markSynced, quarantined } from './room.js';
45
45
  export const PREFERRED_TRIM_SIZE = 500;
46
46
  /**
47
47
  * Bind the IndexedDB persistence provider to the vendored v14 engine module.
@@ -175,13 +175,13 @@ export const bindIndexeddbProvider = (Y) => {
175
175
  loadError = null;
176
176
  _storeTimeout;
177
177
  _storeTimeoutId;
178
- _storeUpdate;
178
+ /** @internal Removes the document and presence listeners (`room.listen`). */
179
+ _unlisten;
179
180
  awareness;
180
181
  bcconnected = false;
181
182
  disableBc;
182
183
  _ownsAwareness;
183
184
  _bcSubscriber;
184
- _awarenessUpdateHandler;
185
185
  constructor(name, doc, options = {}) {
186
186
  super();
187
187
  this.doc = doc;
@@ -196,8 +196,6 @@ export const bindIndexeddbProvider = (Y) => {
196
196
  this._db = openContainer(this.dbName);
197
197
  this.destroy = this.destroy.bind(this);
198
198
  initLifecycle(this, this.destroy, () => room.depart(this));
199
- this._bcSubscriber = room.bcSubscriber(this);
200
- this._awarenessUpdateHandler = room.awarenessUpdateHandler(this);
201
199
  // Fail closed: no sync, no BC room join, whenSynced rejects, and
202
200
  // the terminal 'failed' fires — the provider can never reach
203
201
  // synced once loading its persisted state has failed.
@@ -216,7 +214,7 @@ export const bindIndexeddbProvider = (Y) => {
216
214
  // A read-only doc's pre-hydration state is never persisted.
217
215
  const beforeApplyUpdatesCallback = (updatesStore) => {
218
216
  if (!quarantined(doc)) {
219
- rtop(updatesStore.add(encodeRow(Y.encodeStateAsUpdate(doc)))).catch((error) => this.emit('message-error', [error, this]));
217
+ rtop(updatesStore.add(encodeRow(Y.encodeStateAsUpdate(doc)))).catch((error) => fault(this, error));
220
218
  }
221
219
  };
222
220
  // Hydrated: join the room and claim `synced` (lifetime).
@@ -243,7 +241,7 @@ export const bindIndexeddbProvider = (Y) => {
243
241
  .catch(onLoadError);
244
242
  this._storeTimeout = 1000;
245
243
  this._storeTimeoutId = null;
246
- this._storeUpdate = (update, origin) => {
244
+ const storeUpdate = (update, origin) => {
247
245
  // Outbound quarantine: a read-only doc is never persisted
248
246
  // (`broadcastUpdate` applies the same rule to the room).
249
247
  if (origin === this || quarantined(doc))
@@ -253,9 +251,7 @@ export const bindIndexeddbProvider = (Y) => {
253
251
  const [updatesStore] = storesOf(this.db, [updatesStoreName]);
254
252
  // Storage failures must surface — never an unobserved
255
253
  // rejection (the error channel observes every failure mode).
256
- rtop(updatesStore.add(encodeRow(update))).catch((error) => {
257
- this.emit('message-error', [error, this]);
258
- });
254
+ rtop(updatesStore.add(encodeRow(update))).catch((error) => fault(this, error));
259
255
  if (++this._dbsize >= PREFERRED_TRIM_SIZE) {
260
256
  if (this._storeTimeoutId !== null) {
261
257
  clearTimeout(this._storeTimeoutId);
@@ -264,7 +260,7 @@ export const bindIndexeddbProvider = (Y) => {
264
260
  this._storeTimeoutId = null;
265
261
  // The timed caller has no promise consumer — surface
266
262
  // compaction/storage failures on the error channel.
267
- storeState(this, false).catch((error) => this.emit('message-error', [error, this]));
263
+ storeState(this, false).catch((error) => fault(this, error));
268
264
  }, this._storeTimeout);
269
265
  }
270
266
  }
@@ -272,14 +268,13 @@ export const bindIndexeddbProvider = (Y) => {
272
268
  // A synchronous transact failure (e.g. a handle closed
273
269
  // mid-session) still lands on the error channel rather
274
270
  // than propagating into the doc's update dispatch.
275
- this.emit('message-error', [error, this]);
271
+ fault(this, error);
276
272
  }
277
273
  }
278
274
  // Broadcast the update to other tabs
279
275
  room.broadcastUpdate(this, update);
280
276
  };
281
- doc.on('update', this._storeUpdate);
282
- this.awareness.on('update', this._awarenessUpdateHandler);
277
+ this._unlisten = room.listen(this, storeUpdate);
283
278
  const onDocDestroy = () => void this.destroy();
284
279
  destroyWithDoc.set(this, onDocDestroy);
285
280
  doc.on('destroy', onDocDestroy);
@@ -324,11 +319,10 @@ export const bindIndexeddbProvider = (Y) => {
324
319
  if (this._storeTimeoutId) {
325
320
  clearTimeout(this._storeTimeoutId);
326
321
  }
327
- this.doc.off('update', this._storeUpdate);
322
+ this._unlisten();
328
323
  const onDocDestroy = destroyWithDoc.get(this);
329
324
  if (onDocDestroy)
330
325
  this.doc.off('destroy', onDocDestroy);
331
- this.awareness.off('update', this._awarenessUpdateHandler);
332
326
  this.disconnectBc();
333
327
  if (this._ownsAwareness) {
334
328
  this.awareness.destroy();
@@ -164,6 +164,14 @@ export declare const onPageLeave: (fn: () => void, phase: PageLeavePhase) => (()
164
164
  export declare const initLifecycle: (host: LifecycleHost, destroy: () => unknown, depart?: () => void) => void;
165
165
  /** The first sync — lifetime. Returns `true` only the first time. */
166
166
  export declare const markSynced: (host: LifecycleHost) => boolean;
167
+ /** Report `error` on `provider`'s error channel (`'message-error'`): a frame, a payload or a storage failure. */
168
+ export declare const fault: (provider: {
169
+ emit?(event: "message-error", args: [unknown, unknown]): void;
170
+ }, error: unknown) => void;
171
+ /** A message type no handler claims inside a valid envelope: protocol skew, reported, never dropped silently. */
172
+ export declare const unknownType: (provider: {
173
+ emit?(event: "message-error", args: [unknown, unknown]): void;
174
+ }, what: string, type: number) => void;
167
175
  /**
168
176
  * Terminal `'failed'` emission (the sync-failure contract): fires at
169
177
  * most once per provider, only while it has never synced — destroy before
@@ -298,5 +306,8 @@ export declare const bindRoomProtocol: <P extends RoomProvider<P>>(syncProtocol:
298
306
  depart: (provider: P) => void;
299
307
  connectBc: (provider: P & BcMember) => void;
300
308
  disconnectBc: (provider: P & BcMember) => void;
309
+ listen: (provider: P & BcMember, onUpdate: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
310
+ local: boolean;
311
+ }) => void) => (() => void);
301
312
  };
302
313
  export {};
@@ -293,6 +293,10 @@ export const markSynced = (host) => {
293
293
  host._settleSynced[0](host);
294
294
  return true;
295
295
  };
296
+ /** Report `error` on `provider`'s error channel (`'message-error'`): a frame, a payload or a storage failure. */
297
+ export const fault = (provider, error) => provider.emit?.('message-error', [error, provider]);
298
+ /** A message type no handler claims inside a valid envelope: protocol skew, reported, never dropped silently. */
299
+ export const unknownType = (provider, what, type) => fault(provider, new Error(`Unknown ${what} message type ${type}`));
296
300
  /**
297
301
  * Terminal `'failed'` emission (the sync-failure contract): fires at
298
302
  * most once per provider, only while it has never synced — destroy before
@@ -425,13 +429,13 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
425
429
  // (`discarded`) and reported the same way.
426
430
  const update = decoding.readVarUint8Array(decoder);
427
431
  const origin = emitSynced ? provider : (behavior.tabOrigin?.(provider) ?? provider);
428
- const onError = (error) => provider.emit?.('message-error', [error, provider]);
432
+ const onError = (error) => fault(provider, error);
429
433
  const { applied, problem, discarded } = syncProtocol.applyRemote(provider.doc, update, origin, onError);
430
434
  const refused = problem ?? discarded;
431
435
  if (refused) {
432
436
  const docName = behavior.docName(provider);
433
437
  provider.emit?.('schema-mismatch', [{ docName, problem: refused }, provider]);
434
- provider.emit?.('message-error', [new SchemaMismatchError(docName, refused), provider]);
438
+ fault(provider, new SchemaMismatchError(docName, refused));
435
439
  }
436
440
  // Only an ACCEPTED Step2 is a held member state — a refused
437
441
  // one never claims the handshake.
@@ -441,10 +445,7 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
441
445
  return;
442
446
  }
443
447
  // Unknown sync subtype inside a valid envelope — observable.
444
- provider.emit?.('message-error', [
445
- new Error(`Unknown sync message type ${syncMessageType}`),
446
- provider
447
- ]);
448
+ unknownType(provider, 'sync', syncMessageType);
448
449
  },
449
450
  [messageQueryAwareness]: (encoder, _decoder, provider) => {
450
451
  encoding.writeVarUint(encoder, messageAwareness);
@@ -477,10 +478,7 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
477
478
  if (!messageHandler) {
478
479
  // Fail closed + observable: a VALID envelope carrying a message
479
480
  // type no handler claims is protocol skew — never silent.
480
- provider.emit?.('message-error', [
481
- new Error(`Unknown v14 message type ${messageType}`),
482
- provider
483
- ]);
481
+ unknownType(provider, 'v14', messageType);
484
482
  return encoder;
485
483
  }
486
484
  const followUp = messageHandler(encoder, decoder, provider, emitSynced);
@@ -503,7 +501,7 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
503
501
  catch (error) {
504
502
  // same-tab publishes are delivered synchronously — a malformed
505
503
  // message must not propagate into the publisher's call stack.
506
- provider.emit?.('message-error', [error, provider]);
504
+ fault(provider, error);
507
505
  }
508
506
  };
509
507
  };
@@ -554,6 +552,21 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
554
552
  };
555
553
  /** The departure announcement — our presence, removed — on every transport the provider speaks. */
556
554
  const depart = (provider) => behavior.broadcast(provider, awarenessFrame(provider.awareness, [provider.doc.clientID], new Map()));
555
+ /**
556
+ * Wire `provider` to its room: its BroadcastChannel subscriber, `onUpdate`
557
+ * on its document's updates and its presence changes to the room
558
+ * (`awarenessUpdateHandler`). The teardown removes both listeners.
559
+ */
560
+ const listen = (provider, onUpdate) => {
561
+ provider._bcSubscriber = bcSubscriber(provider);
562
+ const onPresence = awarenessUpdateHandler(provider);
563
+ provider.doc.on('update', onUpdate);
564
+ provider.awareness.on('update', onPresence);
565
+ return () => {
566
+ provider.awareness.off('update', onPresence);
567
+ provider.doc.off('update', onUpdate);
568
+ };
569
+ };
557
570
  /** Leave the room: announce the presence removal, then unsubscribe the BC channel. */
558
571
  const disconnectBc = (provider) => {
559
572
  depart(provider);
@@ -572,6 +585,7 @@ export const bindRoomProtocol = (syncProtocol, behavior) => {
572
585
  hello,
573
586
  depart,
574
587
  connectBc,
575
- disconnectBc
588
+ disconnectBc,
589
+ listen
576
590
  };
577
591
  };
@@ -185,14 +185,8 @@ export declare const bindWebsocketProvider: (Y: EngineApi) => {
185
185
  /** Closes the current dial if it has not opened in time. */
186
186
  _connectTimer: ReturnType<typeof setTimeout> | undefined;
187
187
  _checkInterval: ReturnType<typeof setInterval>;
188
- _updateHandler: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
189
- local: boolean;
190
- }) => void;
191
- _awarenessUpdateHandler: (updates: {
192
- added: number[];
193
- updated: number[];
194
- removed: number[];
195
- }, origin: unknown) => void;
188
+ /** @internal Removes the document and presence listeners (`room.listen`). */
189
+ _unlisten: () => void;
196
190
  /**
197
191
  * The dial URL: the room name as one encoded path segment (any id —
198
192
  * `/`, `%`, `#`, `?` included — reaches its own room; the server
@@ -223,11 +217,11 @@ export declare const bindWebsocketProvider: (Y: EngineApi) => {
223
217
  destroy(): void;
224
218
  disconnect(): void;
225
219
  connect(): void;
226
- emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
220
+ emit<N extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
227
221
  _observers: Map<string, Set<any>>;
228
- on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
229
- once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
230
- off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
222
+ on<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
223
+ once<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
224
+ off<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
231
225
  };
232
226
  };
233
227
  watchComments: (server: string | null, roomName: string, listener: (message: CommentMessage) => void) => (() => void);