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.
- package/README.md +2 -2
- package/dist/cloudflare/room/context.d.ts +62 -74
- package/dist/crdt/doc/history.js +2 -3
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/edytor-doc.d.ts +156 -156
- package/dist/crdt/edytor-doc.js +14 -42
- package/dist/crdt/flow.d.ts +4 -46
- package/dist/crdt/flow.js +2 -262
- package/dist/crdt/index.d.ts +62 -74
- package/dist/crdt/operations.d.ts +1 -1
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/providers/index.d.ts +10 -22
- package/dist/crdt/providers/indexeddb.d.ts +6 -10
- package/dist/crdt/providers/indexeddb.js +10 -16
- package/dist/crdt/providers/room.d.ts +11 -0
- package/dist/crdt/providers/room.js +26 -12
- package/dist/crdt/providers/websocket.d.ts +6 -12
- package/dist/crdt/providers/websocket.js +8 -15
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/text/index/build.d.ts +125 -0
- package/dist/crdt/text/index/build.js +2895 -0
- package/dist/crdt/text/index/shared.d.ts +2 -6
- package/dist/crdt/text/index/shared.js +2 -5
- package/dist/crdt/text/model.js +2 -3
- package/dist/crdt/text/runs.d.ts +9 -12
- package/dist/crdt/text/runs.js +2 -165
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/export/forms.d.ts +50 -50
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/selection/selection.utils.js +5 -8
- package/dist/session/handles.js +3 -7
- package/dist/surface/observer.svelte.js +15 -30
- package/dist/surface/projector.svelte.js +2 -2
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +1 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -207
- package/dist/crdt/doc/content.d.ts +0 -16
- package/dist/crdt/doc/content.js +0 -77
- package/dist/crdt/doc/delete.d.ts +0 -15
- package/dist/crdt/doc/delete.js +0 -84
- package/dist/crdt/doc/layout.d.ts +0 -9
- package/dist/crdt/doc/layout.js +0 -178
- package/dist/crdt/doc/meta.d.ts +0 -19
- package/dist/crdt/doc/meta.js +0 -198
- package/dist/crdt/doc/moves.d.ts +0 -21
- package/dist/crdt/doc/moves.js +0 -274
- package/dist/crdt/doc/split-merge.d.ts +0 -10
- package/dist/crdt/doc/split-merge.js +0 -179
- package/dist/crdt/doc/steps.d.ts +0 -65
- package/dist/crdt/doc/steps.js +0 -311
- package/dist/crdt/text/index/cache.d.ts +0 -16
- package/dist/crdt/text/index/cache.js +0 -175
- package/dist/crdt/text/index/claims.d.ts +0 -18
- package/dist/crdt/text/index/claims.js +0 -203
- package/dist/crdt/text/index/fold.d.ts +0 -21
- package/dist/crdt/text/index/fold.js +0 -656
- package/dist/crdt/text/index/layout.d.ts +0 -22
- package/dist/crdt/text/index/layout.js +0 -258
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -738
- package/dist/crdt/text/index/records.d.ts +0 -13
- package/dist/crdt/text/index/records.js +0 -139
- package/dist/crdt/text/index/report.d.ts +0 -18
- package/dist/crdt/text/index/report.js +0 -337
- package/dist/crdt/text/index/state.d.ts +0 -197
- package/dist/crdt/text/index/state.js +0 -116
- package/dist/crdt/text/index/streams.d.ts +0 -25
- 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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
|
152
|
-
for (
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
206
|
-
const updated =
|
|
207
|
-
const
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
244
|
-
}
|
|
213
|
+
if (!jsonEquals(state, prevState))
|
|
214
|
+
changed.push(clientID);
|
|
245
215
|
updated.push(clientID);
|
|
246
216
|
}
|
|
247
217
|
}
|
|
248
218
|
}
|
|
249
|
-
|
|
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
|
-
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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
|
-
|
|
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" | "
|
|
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" | "
|
|
281
|
-
once<NAME extends "refused" | "status" | "failed" | "
|
|
282
|
-
off<NAME extends "refused" | "status" | "failed" | "
|
|
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
|
-
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
189
|
-
|
|
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" | "
|
|
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" | "
|
|
229
|
-
once<NAME extends "refused" | "status" | "failed" | "
|
|
230
|
-
off<NAME extends "refused" | "status" | "failed" | "
|
|
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);
|