edytor 5.0.0 → 7.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 (137) hide show
  1. package/README.md +3 -3
  2. package/dist/cloudflare/DocumentRoom.d.ts +77 -2
  3. package/dist/cloudflare/DocumentRoom.js +37 -1
  4. package/dist/cloudflare/index.d.ts +1 -1
  5. package/dist/cloudflare/index.js +1 -1
  6. package/dist/cloudflare/room/access.d.ts +6 -1
  7. package/dist/cloudflare/room/access.js +30 -4
  8. package/dist/cloudflare/room/admission.d.ts +11 -0
  9. package/dist/cloudflare/room/admission.js +43 -10
  10. package/dist/cloudflare/room/context.d.ts +73 -76
  11. package/dist/cloudflare/room/context.js +17 -6
  12. package/dist/cloudflare/room/forged.d.ts +1 -1
  13. package/dist/cloudflare/room/forged.js +11 -9
  14. package/dist/cloudflare/room/frames.d.ts +2 -0
  15. package/dist/cloudflare/room/frames.js +2 -0
  16. package/dist/cloudflare/room/presence.js +1 -4
  17. package/dist/cloudflare/room/reseed.d.ts +93 -0
  18. package/dist/cloudflare/room/reseed.js +500 -0
  19. package/dist/cloudflare/room/scheduler.d.ts +1 -1
  20. package/dist/cloudflare/room/scheduler.js +9 -1
  21. package/dist/cloudflare/room/shared.d.ts +10 -0
  22. package/dist/cloudflare/room/shared.js +10 -0
  23. package/dist/cloudflare/room/storage.d.ts +7 -0
  24. package/dist/cloudflare/room/storage.js +33 -1
  25. package/dist/components/Edytor.svelte +18 -3
  26. package/dist/components/Edytor.svelte.d.ts +10 -1
  27. package/dist/components/EdytorRoot.svelte +17 -0
  28. package/dist/components/EdytorRoot.svelte.d.ts +4 -0
  29. package/dist/components/Suggestion.svelte +1 -1
  30. package/dist/crdt/attribution/block.d.ts +3 -0
  31. package/dist/crdt/attribution/block.js +3 -2
  32. package/dist/crdt/doc/history.js +2 -3
  33. package/dist/crdt/doc/ops.d.ts +81 -0
  34. package/dist/crdt/doc/ops.js +1898 -0
  35. package/dist/crdt/doc/suggest.d.ts +6 -2
  36. package/dist/crdt/doc/suggest.js +6 -5
  37. package/dist/crdt/edytor-doc.d.ts +157 -157
  38. package/dist/crdt/edytor-doc.js +15 -43
  39. package/dist/crdt/engine-room.d.ts +4 -0
  40. package/dist/crdt/engine-room.js +6 -0
  41. package/dist/crdt/flow.d.ts +4 -46
  42. package/dist/crdt/flow.js +2 -262
  43. package/dist/crdt/index.d.ts +64 -75
  44. package/dist/crdt/operations.d.ts +1 -1
  45. package/dist/crdt/placement/resolve.d.ts +1 -1
  46. package/dist/crdt/protocol.d.ts +2 -2
  47. package/dist/crdt/protocol.js +2 -2
  48. package/dist/crdt/protocols/auth.d.ts +8 -1
  49. package/dist/crdt/protocols/auth.js +14 -1
  50. package/dist/crdt/protocols/awareness.js +46 -81
  51. package/dist/crdt/providers/index.d.ts +11 -22
  52. package/dist/crdt/providers/index.js +22 -3
  53. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  54. package/dist/crdt/providers/indexeddb.js +10 -16
  55. package/dist/crdt/providers/room.d.ts +26 -0
  56. package/dist/crdt/providers/room.js +41 -12
  57. package/dist/crdt/providers/websocket.d.ts +10 -14
  58. package/dist/crdt/providers/websocket.js +43 -23
  59. package/dist/crdt/rangeDelete.d.ts +2 -47
  60. package/dist/crdt/rangeDelete.js +1 -169
  61. package/dist/crdt/reseed.d.ts +127 -0
  62. package/dist/crdt/reseed.js +533 -0
  63. package/dist/crdt/restore.d.ts +13 -1
  64. package/dist/crdt/restore.js +8 -3
  65. package/dist/crdt/schema.d.ts +6 -0
  66. package/dist/crdt/schema.js +6 -0
  67. package/dist/crdt/structs.d.ts +8 -11
  68. package/dist/crdt/structs.js +25 -44
  69. package/dist/crdt/text/index/build.d.ts +119 -0
  70. package/dist/crdt/text/index/build.js +2801 -0
  71. package/dist/crdt/text/index/shared.d.ts +2 -6
  72. package/dist/crdt/text/index/shared.js +2 -5
  73. package/dist/crdt/text/model.js +2 -3
  74. package/dist/crdt/text/runs.d.ts +9 -12
  75. package/dist/crdt/text/runs.js +2 -165
  76. package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
  77. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  78. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
  79. package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
  80. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  81. package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
  82. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
  83. package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
  84. package/dist/events/domTextOffset.d.ts +2 -0
  85. package/dist/events/domTextOffset.js +8 -0
  86. package/dist/export/forms.d.ts +50 -50
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  90. package/dist/plugins/richtext/richTextOperations.js +26 -32
  91. package/dist/selection/selection.svelte.d.ts +1 -1
  92. package/dist/selection/selection.svelte.js +87 -106
  93. package/dist/selection/selection.utils.js +5 -8
  94. package/dist/session/commands.js +49 -51
  95. package/dist/session/handles.js +3 -7
  96. package/dist/surface/attributes.d.ts +3 -3
  97. package/dist/surface/attributes.js +2 -2
  98. package/dist/surface/observer.svelte.d.ts +7 -7
  99. package/dist/surface/observer.svelte.js +225 -336
  100. package/dist/surface/projector.svelte.js +2 -2
  101. package/dist/utils.d.ts +5 -0
  102. package/dist/utils.js +7 -0
  103. package/package.json +1 -1
  104. package/dist/crdt/doc/capability.d.ts +0 -56
  105. package/dist/crdt/doc/capability.js +0 -207
  106. package/dist/crdt/doc/content.d.ts +0 -16
  107. package/dist/crdt/doc/content.js +0 -77
  108. package/dist/crdt/doc/delete.d.ts +0 -15
  109. package/dist/crdt/doc/delete.js +0 -84
  110. package/dist/crdt/doc/layout.d.ts +0 -9
  111. package/dist/crdt/doc/layout.js +0 -178
  112. package/dist/crdt/doc/meta.d.ts +0 -19
  113. package/dist/crdt/doc/meta.js +0 -198
  114. package/dist/crdt/doc/moves.d.ts +0 -21
  115. package/dist/crdt/doc/moves.js +0 -274
  116. package/dist/crdt/doc/split-merge.d.ts +0 -10
  117. package/dist/crdt/doc/split-merge.js +0 -179
  118. package/dist/crdt/doc/steps.d.ts +0 -65
  119. package/dist/crdt/doc/steps.js +0 -311
  120. package/dist/crdt/text/index/cache.d.ts +0 -16
  121. package/dist/crdt/text/index/cache.js +0 -175
  122. package/dist/crdt/text/index/claims.d.ts +0 -18
  123. package/dist/crdt/text/index/claims.js +0 -203
  124. package/dist/crdt/text/index/fold.d.ts +0 -21
  125. package/dist/crdt/text/index/fold.js +0 -656
  126. package/dist/crdt/text/index/layout.d.ts +0 -22
  127. package/dist/crdt/text/index/layout.js +0 -258
  128. package/dist/crdt/text/index/placement.d.ts +0 -47
  129. package/dist/crdt/text/index/placement.js +0 -738
  130. package/dist/crdt/text/index/records.d.ts +0 -13
  131. package/dist/crdt/text/index/records.js +0 -139
  132. package/dist/crdt/text/index/report.d.ts +0 -18
  133. package/dist/crdt/text/index/report.js +0 -337
  134. package/dist/crdt/text/index/state.d.ts +0 -197
  135. package/dist/crdt/text/index/state.js +0 -116
  136. package/dist/crdt/text/index/streams.d.ts +0 -25
  137. package/dist/crdt/text/index/streams.js +0 -248
@@ -30,9 +30,9 @@ export { bindCrdt } from './index.js';
30
30
  export { SCHEMA_VERSION, META_KEY } from './edytor-doc.js';
31
31
  export { GENERATION, generationWord, frame, PROTOCOL_VERSION, GENERATION_RECORD, GenerationMismatchError, readProtocolVersion } from './protocols/envelope.js';
32
32
  // ── Message types, acknowledgements and chunks ─────────────────────────
33
- export { messageSync, messageAwareness, messageAuth, messageQueryAwareness, messageSaved, messageChunk, messageComments, MAX_FRAME_BYTES, chunkFrame, createChunkReader, ChunkError, CLOSE } from './providers/room.js';
33
+ export { messageSync, messageAwareness, messageAuth, messageQueryAwareness, messageSaved, messageChunk, messageComments, messageRebase, MAX_FRAME_BYTES, chunkFrame, createChunkReader, ChunkError, CLOSE } from './providers/room.js';
34
34
  export { messageYjsSyncStep1, messageYjsSyncStep2, messageYjsUpdate } from './protocols/sync.js';
35
- export { messageAccess, messagePermissionDenied, messageReadOnly, writeAccess, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
35
+ export { messageAccess, messageEpoch, messagePermissionDenied, messageReadOnly, writeAccess, writeEpoch, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
36
36
  export { commentsSubscribe, commentsSnapshot, commentsChange, writeCommentsSubscribe, writeCommentsSnapshot, writeCommentsChange, readCommentsMessage } from './protocols/comments.js';
37
37
  export { applyAwarenessUpdate, encodeAwarenessUpdate, readAwarenessEntries, writeAwarenessEntries } from './protocols/awareness.js';
38
38
  // Minimal wire codec for frame bodies (lib0 — the codec the frames are
@@ -28,6 +28,13 @@ export declare const writeReadOnly: (encoder: encoding.Encoder) => void;
28
28
  */
29
29
  export declare const messageAccess = 2;
30
30
  export declare const writeAccess: (encoder: encoding.Encoder, access: string) => void;
31
+ /**
32
+ * The room's epoch (`room.reseed.protocol`), sent to a socket that dialed an
33
+ * older one, instead of anything else: the room asks it for its old
34
+ * document (`messageRebase`), then closes it `4410`.
35
+ */
36
+ export declare const messageEpoch = 3;
37
+ export declare const writeEpoch: (encoder: encoding.Encoder, epoch: number) => void;
31
38
  export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
32
39
  /**
33
40
  * Decode one auth payload. Returns the auth message subtype so the caller
@@ -35,4 +42,4 @@ export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
35
42
  * do not speak is protocol skew, and this stack's contract is that skew is
36
43
  * reported, not silently dropped (same rule as unknown sync subtypes).
37
44
  */
38
- export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void, accessHandler?: (access: string) => void) => number;
45
+ export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void, accessHandler?: (access: string) => void, epochHandler?: (epoch: number) => void) => number;
@@ -36,13 +36,23 @@ export const writeAccess = (encoder, access) => {
36
36
  encoding.writeVarUint(encoder, messageAccess);
37
37
  encoding.writeVarString(encoder, access);
38
38
  };
39
+ /**
40
+ * The room's epoch (`room.reseed.protocol`), sent to a socket that dialed an
41
+ * older one, instead of anything else: the room asks it for its old
42
+ * document (`messageRebase`), then closes it `4410`.
43
+ */
44
+ export const messageEpoch = 3;
45
+ export const writeEpoch = (encoder, epoch) => {
46
+ encoding.writeVarUint(encoder, messageEpoch);
47
+ encoding.writeVarUint(encoder, epoch);
48
+ };
39
49
  /**
40
50
  * Decode one auth payload. Returns the auth message subtype so the caller
41
51
  * can surface unknown subtypes — a valid envelope carrying an auth type we
42
52
  * do not speak is protocol skew, and this stack's contract is that skew is
43
53
  * reported, not silently dropped (same rule as unknown sync subtypes).
44
54
  */
45
- export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler, accessHandler) => {
55
+ export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler, accessHandler, epochHandler) => {
46
56
  const authMessageType = decoding.readVarUint(decoder);
47
57
  switch (authMessageType) {
48
58
  case messagePermissionDenied:
@@ -54,6 +64,9 @@ export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHan
54
64
  break;
55
65
  case messageAccess:
56
66
  accessHandler?.(decoding.readVarString(decoder));
67
+ break;
68
+ case messageEpoch:
69
+ epochHandler?.(decoding.readVarUint(decoder));
57
70
  }
58
71
  return authMessageType;
59
72
  };
@@ -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;
@@ -248,6 +243,7 @@ export declare const bindProviders: (Y: EngineApi) => {
248
243
  deny(): void;
249
244
  allow(): void;
250
245
  };
246
+ _rebasing: boolean;
251
247
  messageHandlers: Record<number, import("./room.js").RoomMessageHandler</*elided*/ any>>;
252
248
  hasSynced: boolean;
253
249
  whenSynced: Promise<unknown>;
@@ -258,14 +254,7 @@ export declare const bindProviders: (Y: EngineApi) => {
258
254
  _resync: ReturnType<typeof setInterval> | undefined;
259
255
  _connectTimer: ReturnType<typeof setTimeout> | undefined;
260
256
  _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;
257
+ _unlisten: () => void;
269
258
  get url(): string;
270
259
  get unsaved(): number;
271
260
  get saved(): boolean;
@@ -275,11 +264,11 @@ export declare const bindProviders: (Y: EngineApi) => {
275
264
  destroy(): void;
276
265
  disconnect(): void;
277
266
  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;
267
+ 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
268
  _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;
269
+ 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];
270
+ 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;
271
+ 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
272
  };
284
273
  };
285
274
  watchComments: (server: string | null, roomName: string, listener: (message: import("../protocol.js").CommentMessage) => void) => (() => void);
@@ -1,6 +1,6 @@
1
1
  import { keepingReplaced } from '../incarnations.js';
2
2
  import { bindIndexeddbProvider } from './indexeddb.js';
3
- import { assertRoomId, SyncRefusedError } from './room.js';
3
+ import { assertRoomId, CLOSE, SyncRefusedError } from './room.js';
4
4
  import { bindWebsocketProvider } from './websocket.js';
5
5
  export const bindProviders = (Y) => {
6
6
  const idb = bindIndexeddbProvider(Y);
@@ -50,7 +50,18 @@ export const bindProviders = (Y) => {
50
50
  maxBackoffTime,
51
51
  connectTimeout,
52
52
  disableBc: disableBc || local !== undefined,
53
- maxFrameBytes
53
+ maxFrameBytes,
54
+ // With a local copy, the dial waits for it: the epoch it names is the copy's.
55
+ connect: local === undefined
56
+ });
57
+ const dial = () => provider._destroyed || provider.connect();
58
+ // A re-seed (`room.reseed.protocol`, a `4410` refusal): the local copy goes,
59
+ // its deletion waiting for every tab's connection to close.
60
+ provider.on('refused', ({ code }) => {
61
+ if (code !== CLOSE.reseeded || !loaded)
62
+ return;
63
+ void release?.();
64
+ void idb.clearDocument(persistName);
54
65
  });
55
66
  if (onExpired)
56
67
  provider.on('expired', (state) => onExpired(state));
@@ -94,9 +105,17 @@ export const bindProviders = (Y) => {
94
105
  });
95
106
  }
96
107
  let release = undefined;
108
+ // The store's load (or its failure) opens the dial.
109
+ const loaded = local &&
110
+ Object.assign((p) => local({
111
+ ...p,
112
+ synced: (x) => (p.synced(x), dial()),
113
+ failed: (error, x) => (p.failed?.(error, x), dial())
114
+ }), local);
97
115
  try {
98
116
  // Called directly (no `attach`), the store only persists: readiness is the socket's.
99
- release = local && (attach ? attach(local) : local({ doc, awareness, synced: () => { } }));
117
+ release =
118
+ loaded && (attach ? attach(loaded) : loaded({ doc, awareness, synced: () => { } }));
100
119
  }
101
120
  catch (error) {
102
121
  provider.destroy();
@@ -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();
@@ -61,6 +61,12 @@ export declare const messageChunk = 5;
61
61
  * room sends it only to a socket that subscribed.
62
62
  */
63
63
  export declare const messageComments = 6;
64
+ /**
65
+ * Client → server, after a re-seed (`room.reseed.protocol`): the client's
66
+ * whole old document, its unsaved edits in it, for the room to rebase onto
67
+ * the current epoch. Sent only when the room asks (its epoch notice).
68
+ */
69
+ export declare const messageRebase = 7;
64
70
  /** A WebSocket message limit on Cloudflare (32 MiB): a larger frame is sent as chunks. */
65
71
  export declare const MAX_FRAME_BYTES: number;
66
72
  /**
@@ -164,6 +170,14 @@ export declare const onPageLeave: (fn: () => void, phase: PageLeavePhase) => (()
164
170
  export declare const initLifecycle: (host: LifecycleHost, destroy: () => unknown, depart?: () => void) => void;
165
171
  /** The first sync — lifetime. Returns `true` only the first time. */
166
172
  export declare const markSynced: (host: LifecycleHost) => boolean;
173
+ /** Report `error` on `provider`'s error channel (`'message-error'`): a frame, a payload or a storage failure. */
174
+ export declare const fault: (provider: {
175
+ emit?(event: "message-error", args: [unknown, unknown]): void;
176
+ }, error: unknown) => void;
177
+ /** A message type no handler claims inside a valid envelope: protocol skew, reported, never dropped silently. */
178
+ export declare const unknownType: (provider: {
179
+ emit?(event: "message-error", args: [unknown, unknown]): void;
180
+ }, what: string, type: number) => void;
167
181
  /**
168
182
  * Terminal `'failed'` emission (the sync-failure contract): fires at
169
183
  * most once per provider, only while it has never synced — destroy before
@@ -195,6 +209,15 @@ export declare const CLOSE: {
195
209
  readonly denied: 4403;
196
210
  /** The dialed replica is bound to another user. */
197
211
  readonly replicaTaken: 4409;
212
+ /**
213
+ * The room re-seeded the document (`room.reseed.protocol`; the reason is
214
+ * the room's epoch): a refusal of this document, once a client with
215
+ * unsaved edits redialed and handed them over. The new epoch is a new
216
+ * document's. Also the answer to a copy the room cannot rebase (an
217
+ * epoch past the cuts it keeps, or a later one) and to an older epoch's
218
+ * content on a socket of the current one.
219
+ */
220
+ readonly reseeded: 4410;
198
221
  /**
199
222
  * A room quota refused the socket's write (`quota: document`,
200
223
  * `quota: rate`, `quota: frame`): the frame was not applied, and a
@@ -298,5 +321,8 @@ export declare const bindRoomProtocol: <P extends RoomProvider<P>>(syncProtocol:
298
321
  depart: (provider: P) => void;
299
322
  connectBc: (provider: P & BcMember) => void;
300
323
  disconnectBc: (provider: P & BcMember) => void;
324
+ listen: (provider: P & BcMember, onUpdate: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
325
+ local: boolean;
326
+ }) => void) => (() => void);
301
327
  };
302
328
  export {};