edytor 0.1.0-next.21 → 0.1.0-next.23

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 (69) hide show
  1. package/README.md +1 -1
  2. package/dist/cloudflare/DocumentRoom.d.ts +302 -10
  3. package/dist/cloudflare/DocumentRoom.js +965 -88
  4. package/dist/cloudflare/index.d.ts +1 -1
  5. package/dist/cloudflare/index.js +1 -1
  6. package/dist/cloudflare/routeDocumentSocket.js +30 -6
  7. package/dist/collaboration/awarenessSelection.d.ts +7 -1
  8. package/dist/collaboration/awarenessSelection.js +7 -1
  9. package/dist/collaboration/index.d.ts +2 -2
  10. package/dist/collaboration/index.js +2 -2
  11. package/dist/collaboration/providers.d.ts +4 -0
  12. package/dist/collaboration/providers.js +4 -0
  13. package/dist/components/Edytor.svelte +14 -2
  14. package/dist/components/Edytor.svelte.d.ts +8 -1
  15. package/dist/crdt/admission.d.ts +6 -1
  16. package/dist/crdt/admission.js +9 -2
  17. package/dist/crdt/data.d.ts +3 -1
  18. package/dist/crdt/data.js +42 -32
  19. package/dist/crdt/document.d.ts +31 -8
  20. package/dist/crdt/document.js +53 -18
  21. package/dist/crdt/edytor-doc.d.ts +31 -3
  22. package/dist/crdt/edytor-doc.js +131 -14
  23. package/dist/crdt/engine.d.ts +6 -0
  24. package/dist/crdt/engine.js +7 -0
  25. package/dist/crdt/flow.d.ts +2 -0
  26. package/dist/crdt/flow.js +2 -2
  27. package/dist/crdt/index.d.ts +20 -9
  28. package/dist/crdt/index.js +3 -3
  29. package/dist/crdt/migration/migrate.js +9 -3
  30. package/dist/crdt/placement/model.d.ts +9 -1
  31. package/dist/crdt/placement/model.js +29 -10
  32. package/dist/crdt/placement/rank.d.ts +20 -0
  33. package/dist/crdt/placement/rank.js +79 -0
  34. package/dist/crdt/protocols/envelope.d.ts +20 -1
  35. package/dist/crdt/protocols/envelope.js +14 -2
  36. package/dist/crdt/providers/container.d.ts +15 -0
  37. package/dist/crdt/providers/container.js +17 -3
  38. package/dist/crdt/providers/index.d.ts +21 -1
  39. package/dist/crdt/providers/index.js +86 -3
  40. package/dist/crdt/providers/indexeddb.js +60 -40
  41. package/dist/crdt/providers/room.d.ts +18 -2
  42. package/dist/crdt/providers/room.js +29 -5
  43. package/dist/crdt/providers/websocket.d.ts +9 -1
  44. package/dist/crdt/providers/websocket.js +21 -6
  45. package/dist/crdt/storage.d.ts +21 -0
  46. package/dist/crdt/storage.js +37 -0
  47. package/dist/crdt/structs.d.ts +17 -0
  48. package/dist/crdt/structs.js +41 -0
  49. package/dist/crdt/text/model.d.ts +9 -1
  50. package/dist/crdt/text/model.js +24 -14
  51. package/dist/crdt/text/rows.d.ts +94 -0
  52. package/dist/crdt/text/rows.js +193 -0
  53. package/dist/crdt/text/runs.d.ts +30 -10
  54. package/dist/crdt/text/runs.js +1235 -264
  55. package/dist/edytor.svelte.d.ts +3 -1
  56. package/dist/edytor.svelte.js +9 -6
  57. package/dist/index.d.ts +1 -1
  58. package/dist/index.js +1 -1
  59. package/dist/plugins/image/ImageEmpty.svelte +19 -6
  60. package/dist/plugins/image/ImagePlugin.svelte +9 -3
  61. package/dist/plugins/image/ImagePlugin.svelte.d.ts +2 -2
  62. package/dist/plugins/image/image.d.ts +16 -0
  63. package/dist/plugins/image/image.js +19 -0
  64. package/dist/plugins/index.d.ts +1 -1
  65. package/dist/plugins/index.js +1 -1
  66. package/dist/plugins.d.ts +11 -0
  67. package/dist/session/commands.d.ts +1 -0
  68. package/dist/session/commands.js +4 -1
  69. package/package.json +1 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  Edytor aims to be for Svelte what Slate.js is for React: a heavily customizable editor with an API to build any kind of collaborative rich text editor.
17
17
 
18
- > **Work in progress.** Edytor is a pre-release (`0.1.0-next.21`, `edytor@next` on npm) and not ready for production; the API changes between releases without a compatibility layer. The untagged `edytor@0.0.11` on npm predates the current API. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
18
+ > **Work in progress.** Edytor is a pre-release (`0.1.0-next.23`, `edytor@next` on npm) and not ready for production; the API changes between releases without a compatibility layer. The untagged `edytor@0.0.11` on npm predates the current API. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
19
19
 
20
20
  ## Features
21
21
 
@@ -34,8 +34,9 @@
34
34
  * what storage does not.
35
35
  * - every INTEGRATED update is appended to SQLite as one record split into
36
36
  * rows ≤ `maxRowBytes` (2 MB row cap) in one `transactionSync`, then
37
- * broadcast; after `compactAfter` update records the rows are merged
38
- * (`mergeUpdates`) into one snapshot record, after the message is
37
+ * broadcast; after `compactAfter` update records the rows are replaced
38
+ * by one snapshot record of the live document's state (its healed
39
+ * state, deleted content collected: P2), after the message is
39
40
  * acknowledged — never inside the engine's `update` observer, where one
40
41
  * throw would silence every later emit. Memory never runs ahead of
41
42
  * storage: when an append fails, nothing is relayed or acknowledged, the
@@ -51,6 +52,11 @@
51
52
  * left out of the ack), the rest applied. Compaction reclaims the
52
53
  * waiting deletes of client ids no user registered and no socket holds;
53
54
  * `dropWaitingDeletes()` every one.
55
+ * - storage format (P5): the generation record says `storage: 'v2'` —
56
+ * snapshots in the v2 encoding, compressed in place after they are
57
+ * stored (`compressLater`), inflated at start (`inflate`); update and
58
+ * pending records stay v1. A container without `storage` (0.1.0-next.22)
59
+ * is all v1, and its next compaction rewrites it.
54
60
  * - a Step2 serves the STORED state: the engine's pending structs (waiting
55
61
  * for a dependency, never stored) are not served.
56
62
  * - a frame larger than `maxFrameBytes` (32 MiB) goes out as chunks the
@@ -89,13 +95,29 @@
89
95
  */
90
96
  import { DurableObject } from 'cloudflare:workers';
91
97
  import * as E from '../crdt/index.js';
92
- import type { AwarenessEntry, DocumentSemanticsConfig, EdytorDoc, JSONDoc, YDoc } from '../crdt/index.js';
98
+ import type { AwarenessEntry, DocumentSemanticsConfig, EdytorDoc, JSONBlock, JSONDoc, YDoc } from '../crdt/index.js';
93
99
  /** Under SQLite-backed Durable Objects' 2 MB row cap, with room for the other columns. */
94
100
  export declare const DEFAULT_MAX_ROW_BYTES: number;
95
101
  /** Update records before the rows are merged into one snapshot. */
96
102
  export declare const DEFAULT_COMPACT_AFTER = 500;
97
103
  /** ms between the first unsaved change and `onSave`. */
98
104
  export declare const DEFAULT_SAVE_AFTER = 2000;
105
+ /**
106
+ * Room quotas (H3), each refused with `4413` (`quota: <name>`). The
107
+ * document's size: what its records hold (uncompressed) and the updates the
108
+ * engine holds waiting — the live document holds about that, several
109
+ * times over in memory for text (an isolate has 128 MB). Raise or lower
110
+ * it for your documents.
111
+ */
112
+ export declare const DEFAULT_MAX_DOCUMENT_BYTES: number;
113
+ /** The largest frame a socket may send, reassembled from chunks (the platform caps one message at 32 MiB). */
114
+ export declare const DEFAULT_MAX_INBOUND_FRAME_BYTES: number;
115
+ /**
116
+ * Sync messages a socket may send per second, sustained; a burst of ten
117
+ * seconds' worth is allowed (a reconnect, a paste). Far above typing
118
+ * speed: a script should batch its edits into transactions.
119
+ */
120
+ export declare const DEFAULT_MAX_UPDATES_PER_SECOND = 50;
99
121
  /**
100
122
  * Delete ranges the room keeps waiting for items it does not hold (a
101
123
  * relayer's delete of an author's edit a restore lost). Every update the
@@ -128,6 +150,8 @@ export type SavedDocument = {
128
150
  };
129
151
  /** What `onLoad` may return: JSON, a bare v14 update, or `{ update, replicas }` (a `SavedDocument`). */
130
152
  export type LoadedDocument = JSONDoc | Uint8Array | Pick<SavedDocument, 'update' | 'replicas'>;
153
+ /** The header `routeDocumentSocket` sets on an authorized `lastUpdated` probe (H12). */
154
+ export declare const PROBE_HEADER = "X-Edytor-Probe";
131
155
  /** The transaction origin of the room's own edits (`transact`, `onLoad` seeds). */
132
156
  export declare const ROOM_ORIGIN: unique symbol;
133
157
  /**
@@ -150,6 +174,11 @@ export type DocumentRoomEnv = {
150
174
  EDYTOR_MAX_FRAME_BYTES?: string | number;
151
175
  EDYTOR_COMPACT_AFTER?: string | number;
152
176
  EDYTOR_SAVE_AFTER?: string | number;
177
+ EDYTOR_MAX_DOCUMENT_BYTES?: string | number;
178
+ EDYTOR_MAX_INBOUND_FRAME_BYTES?: string | number;
179
+ EDYTOR_MAX_UPDATES_PER_SECOND?: string | number;
180
+ /** `off`: the room logs nothing (default: one JSON line per compaction, quota hit, denial, fault). */
181
+ EDYTOR_LOG?: string;
153
182
  };
154
183
  /** What a socket is bound to: its verified user, its replica (Yjs client id), its access. */
155
184
  export type SocketIdentity = {
@@ -163,9 +192,76 @@ export type Attachment = SocketIdentity & {
163
192
  clock: number | null;
164
193
  };
165
194
  export type Refusal = {
166
- reason: 'generation' | 'schema' | 'malformed' | 'container' | 'identity' | 'replica' | 'read-only' | 'waiting' | 'storage' | 'internal' | 'orphan' | 'relayed';
195
+ reason: 'generation' | 'schema' | 'malformed' | 'container' | 'identity' | 'replica' | 'read-only' | 'waiting' | 'storage' | 'internal' | 'quota' | 'mark' | 'denied' | 'orphan' | 'relayed';
167
196
  detail: unknown;
168
197
  };
198
+ /** A duration series: how many, their total and longest (ms; see {@link RoomMetrics}). */
199
+ export type Timing = {
200
+ count: number;
201
+ totalMs: number;
202
+ maxMs: number;
203
+ lastMs: number;
204
+ };
205
+ /**
206
+ * The room's counters (H14), since this instance started (memory: a wake
207
+ * starts them again), read with `metrics()` (also over RPC). Times are
208
+ * what the runtime's clock reports: Workers advance it only across I/O, so
209
+ * in production synchronous work (a compaction, a fold) often reads `0`;
210
+ * sizes and counts are exact.
211
+ */
212
+ export type RoomMetrics = {
213
+ /** The document's size as the document quota measures it: its records, uncompressed. */
214
+ documentBytes: number;
215
+ /** What its rows take in storage (a snapshot compressed). */
216
+ storedBytes: number;
217
+ /** Logical records stored (the generation record included), and their rows. */
218
+ records: number;
219
+ rows: number;
220
+ /** Update records since the last compaction (compaction runs at `compactAfter`). */
221
+ updateRecords: number;
222
+ /** Bytes of updates held waiting for a dependency (in memory, never stored). */
223
+ waitingBytes: number;
224
+ /** The document's open sockets. */
225
+ sockets: number;
226
+ /** Compactions run, with their time. */
227
+ compaction: Timing & {
228
+ lastBytes: number;
229
+ };
230
+ /** Client sync frames folded (applied, indexed, stored and relayed), with their time. */
231
+ fold: Timing;
232
+ /** Frames sent to other sockets (relays, presence, acknowledgements to others), and their bytes. */
233
+ fanOut: {
234
+ messages: number;
235
+ bytes: number;
236
+ };
237
+ /** Writes refused by a quota (`quota` refusals) and frames `validate` denied. */
238
+ quotaHits: number;
239
+ validationDenials: number;
240
+ /** Every refusal, by reason (as `refusalCounts`). */
241
+ refusals: Partial<Record<Refusal['reason'], number>>;
242
+ /** When this instance started (ms since the epoch). */
243
+ since: number;
244
+ };
245
+ /** One line of the room's log (H14): a compaction, a quota hit, a denial, a fault. */
246
+ export type RoomLogEntry = {
247
+ edytor: 'compaction';
248
+ ms: number;
249
+ bytes: number;
250
+ records: number;
251
+ rows: number;
252
+ } | {
253
+ edytor: 'quota';
254
+ user: string;
255
+ quota: string;
256
+ } | {
257
+ edytor: 'denied';
258
+ user: string;
259
+ touched: number;
260
+ } | {
261
+ edytor: 'fault';
262
+ reason: 'storage' | 'internal';
263
+ detail: string;
264
+ };
169
265
  /** `pending`: deletes of items the room does not hold yet (they wait for them). */
170
266
  type RowKind = 'generation' | 'update' | 'snapshot' | 'pending';
171
267
  /**
@@ -177,12 +273,50 @@ type RowKind = 'generation' | 'update' | 'snapshot' | 'pending';
177
273
  * `isRefusal` in `crdt/providers/room.ts`).
178
274
  */
179
275
  export declare const closedSocket: (code: number, reason: string) => Response;
276
+ /** A stored record, reassembled: its kind, record number, bytes, and whether they are v2 (a v2 container's snapshot). */
277
+ export type StoredRecord = {
278
+ kind: RowKind;
279
+ record: number;
280
+ bytes: Uint8Array<ArrayBuffer>;
281
+ v2: boolean;
282
+ };
180
283
  /** Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating. */
181
284
  export declare const noTimers: <T>(fn: () => T) => T;
182
285
  /** A Yjs client id (a non-negative safe integer), or `null`. */
183
286
  export declare const parseReplica: (raw: unknown) => number | null;
184
287
  /** An awareness frame carrying `entries` — no `Awareness` instance involved. */
185
288
  export declare const presenceFrame: (entries: AwarenessEntry[]) => Uint8Array;
289
+ /** A block as `validate` reads it, before or after a frame. */
290
+ export type ValidatedBlock = {
291
+ id: string;
292
+ type: string;
293
+ data: Record<string, unknown>;
294
+ content: NonNullable<JSONBlock['content']>;
295
+ /** Its display parent (`null`: the document's root). */
296
+ parent: string | null;
297
+ };
298
+ /** One client frame the room applied, as `validate` reads it. */
299
+ export type FrameValidation = {
300
+ /** The sender's verified user. */
301
+ user: string;
302
+ /** The sender's replica (client id), when bound. */
303
+ replica: number | null;
304
+ /**
305
+ * The blocks the frame changed, from the facade's change report: shown
306
+ * or hidden (with their subtrees), given another parent or moved among
307
+ * their siblings (not shifted by a sibling's move), retyped, given
308
+ * other data, or other content.
309
+ */
310
+ touched: string[];
311
+ /** The frame changed the document's own data. */
312
+ dataChanged: boolean;
313
+ /** A block as it was before the frame (`null`: it did not show). */
314
+ before: (id: string) => ValidatedBlock | null;
315
+ /** A block as it is now (`null`: it does not show). */
316
+ after: (id: string) => ValidatedBlock | null;
317
+ /** The live document. Read it; never write from `validate` (defer a `transact` instead). */
318
+ facade: EdytorDoc;
319
+ };
186
320
  /** What {@link attachDocument} (and `DocumentRoom`) takes. */
187
321
  export type AttachDocumentOptions = {
188
322
  /**
@@ -211,6 +345,29 @@ export type AttachDocumentOptions = {
211
345
  maxFrameBytes?: number;
212
346
  /** Prefix of the document's SQL tables, beside your own (default `'edytor_'`). */
213
347
  tablePrefix?: string;
348
+ /** Largest document, in stored bytes (default {@link DEFAULT_MAX_DOCUMENT_BYTES}). */
349
+ maxDocumentBytes?: number;
350
+ /** Largest frame a socket may send, reassembled (default {@link DEFAULT_MAX_INBOUND_FRAME_BYTES}). */
351
+ maxInboundFrameBytes?: number;
352
+ /** Sync messages per second a socket may send (default {@link DEFAULT_MAX_UPDATES_PER_SECOND}). */
353
+ maxUpdatesPerSecond?: number;
354
+ /**
355
+ * Accept, then compensate (H2): after the room applied and stored a
356
+ * client frame that changed blocks, it is asked whether to keep it.
357
+ * `false` (or a throw) denies it: the room writes the frame's inverse as
358
+ * its own transaction — the history undo of exactly that frame
359
+ * (`room.validate.inverse`) — stores it and sends it to every socket,
360
+ * the sender's included, so every replica converges. The frame stays
361
+ * stored and acknowledged and the socket open. Synchronous; runs only
362
+ * when set (the room then keeps an index of every block's state).
363
+ */
364
+ validate?: (frame: FrameValidation) => boolean | void;
365
+ /**
366
+ * The room's log (H14): one entry per compaction, quota hit, denial or
367
+ * fault. Default: `console.log` of the entry as JSON (Workers Logs and
368
+ * `wrangler tail` collect it); `false` logs nothing.
369
+ */
370
+ log?: ((entry: RoomLogEntry) => void) | false;
214
371
  /**
215
372
  * The block roles `transact` edits obey — the document semantics your
216
373
  * clients' plugins declare (`defaultType` also names the block an empty
@@ -234,6 +391,9 @@ export declare class AttachedDocument {
234
391
  readonly maxFrameBytes: number;
235
392
  readonly compactAfter: number;
236
393
  readonly saveAfter: number;
394
+ readonly maxDocumentBytes: number;
395
+ readonly maxInboundFrameBytes: number;
396
+ readonly maxUpdatesPerSecond: number;
237
397
  private live;
238
398
  /** Why the stored container was refused (another generation, a torn record, `onLoad`…). */
239
399
  failure: Error | null;
@@ -252,6 +412,16 @@ export declare class AttachedDocument {
252
412
  };
253
413
  private nextRecord;
254
414
  private updates;
415
+ /** What the records hold, uncompressed (the generation record aside): the document quota's measure. */
416
+ private documentBytes;
417
+ /** The counters `metrics()` reads (H14). */
418
+ private readonly counters;
419
+ /** The index `validate` reads (built at its first frame, for the live document). */
420
+ private validation;
421
+ /** Each socket's update allowance (a token bucket; memory: a wake refills it). */
422
+ private readonly allowances;
423
+ /** Each socket's chunked frame in flight (memory: a wake loses it, and the socket is faulted). */
424
+ private readonly chunkReaders;
255
425
  /** A failed append: the live doc holds what storage does not, until it is rebuilt. */
256
426
  private unstored;
257
427
  /** Inside `transact`, its commit included (a nested call is refused but while `fn` runs). */
@@ -260,6 +430,12 @@ export declare class AttachedDocument {
260
430
  private running;
261
431
  /** Inside a client frame's apply, its events included: the frame's handler settles it. */
262
432
  private handling;
433
+ /** How the container stores its snapshots (its generation record says; a fresh one: this build's). */
434
+ private format;
435
+ /** The snapshot compression in flight (`compressed()`). */
436
+ private compressing;
437
+ /** The raw bytes of the compressed snapshot record (`records` reads it; only `start` can inflate). */
438
+ private inflated;
263
439
  /** The waiting deletes stored as `pending` records (the engine may hold more, in memory). */
264
440
  private storedWaiting;
265
441
  /** The failure is the storage's (a failed read, `onLoad`'s store down): the next dial starts again. */
@@ -272,6 +448,7 @@ export declare class AttachedDocument {
272
448
  private readonly options;
273
449
  private readonly rowsTable;
274
450
  private readonly replicasTable;
451
+ private readonly metaTable;
275
452
  private _lookups;
276
453
  constructor(ctx: DurableObjectState, options?: AttachDocumentOptions);
277
454
  /**
@@ -279,6 +456,12 @@ export declare class AttachedDocument {
279
456
  * out of `blockConcurrencyWhile` resets the object on every request.
280
457
  */
281
458
  private start;
459
+ /**
460
+ * Inflate a compressed snapshot record (P5) for `records`, which reads
461
+ * synchronously (a rebuild does): decompression is asynchronous, so
462
+ * only `start` can. One that does not inflate is a corrupt container.
463
+ */
464
+ private inflate;
282
465
  /** A read of the rows or `onLoad` failed (storage was down): start again. */
283
466
  private retryStart;
284
467
  private fail;
@@ -359,20 +542,43 @@ export declare class AttachedDocument {
359
542
  * engine refuses) is refused for good.
360
543
  */
361
544
  private load;
362
- /** Reassembled logical records in write order; a torn record throws. */
363
- records(): Array<{
364
- kind: RowKind;
365
- bytes: Uint8Array<ArrayBuffer>;
366
- }>;
545
+ /** The bytes of the stored `pending` records (uncompressed, as every record but a snapshot). */
546
+ private pendingBytes;
547
+ /**
548
+ * Reassembled logical records in write order (of one kind, with
549
+ * `only`); a torn record throws. A v2 container's snapshot is v2
550
+ * (`v2`), inflated when it is stored compressed (P5).
551
+ */
552
+ records(only?: RowKind): StoredRecord[];
367
553
  /**
368
554
  * One logical record split into rows ≤ `maxRowBytes` (callers run it in
369
555
  * a transaction). The first record of an empty container brings the
370
556
  * generation record with it.
371
557
  */
372
558
  private insert;
559
+ /** Record `record`'s rows (at the row positions `seqs`, when given). */
560
+ private writeRecord;
561
+ /**
562
+ * Compress snapshot `record` in place once it is stored (P5): gzip is
563
+ * asynchronous, and the store-before-ack path is not. Its rows are
564
+ * rewritten (at their positions) only while it is still the stored
565
+ * snapshot and only when that shrinks it; the raw bytes stay in memory
566
+ * (`inflated`) for a rebuild's synchronous read. A platform without
567
+ * `CompressionStream` keeps it raw.
568
+ */
569
+ private compressLater;
570
+ /**
571
+ * Resolves once the snapshot compression in flight, if any, is stored
572
+ * (P5): a compaction stores its snapshot raw, then compresses it in
573
+ * place. Wait for it before reading the rows' sizes.
574
+ */
575
+ compressed(): Promise<void>;
373
576
  /**
374
577
  * Compaction: the rows become the generation record + one chunked
375
- * snapshot, `mergeUpdates` of every stored record, atomically, + one
578
+ * snapshot of the live document's state (P2: the healed state, deleted
579
+ * content collected but the text a replica may copy again, P11 —
580
+ * never a merge of the records, which keeps every keystroke's struct
581
+ * and every deleted character), atomically, + one
376
582
  * `pending` record of the stored deletes still waiting. A waiting delete
377
583
  * of a client id no user registered and no socket holds is reclaimed,
378
584
  * in memory too: nothing can deliver its item (a made-up id) — the
@@ -385,6 +591,21 @@ export declare class AttachedDocument {
385
591
  compact(): {
386
592
  rows: number;
387
593
  };
594
+ /** Record now as the last stored change (callers run it in the change's transaction). */
595
+ private touch;
596
+ /**
597
+ * When the room last stored a change (ms since the epoch), or `null`
598
+ * for a room that stored none since 0.1.0-next.23 (H12). Cheap: one
599
+ * row, no document. `routeDocumentSocket` answers it over HTTP
600
+ * (`GET <room>?lastUpdated`), also over RPC.
601
+ */
602
+ lastUpdated(): number | null;
603
+ /** The room's counters (H14), also over RPC — see {@link RoomMetrics}. */
604
+ metrics(): RoomMetrics;
605
+ /** What the rows take: bytes, logical records, rows. */
606
+ private storage;
607
+ /** Write one log entry (H14) — the `log` option's, `console.log` by default. */
608
+ private log;
388
609
  /**
389
610
  * Drop every waiting delete, stored or in memory (callable over RPC):
390
611
  * the way out when a writer filled {@link MAX_WAITING_DELETES} with
@@ -469,11 +690,65 @@ export declare class AttachedDocument {
469
690
  /** Is `ws` one of this document's sockets? */
470
691
  owns(ws: WebSocket): boolean;
471
692
  webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): boolean;
693
+ /** One frame: whole as it came, or reassembled from its chunks. */
694
+ private onFrame;
695
+ /**
696
+ * One chunk of a frame too large to send whole (H6: a provider's
697
+ * reconnect diff): buffered per socket, the whole frame handled once its
698
+ * sequence ends. A sequence announcing more than `maxInboundFrameBytes`
699
+ * is a frame quota refusal, before anything is buffered. A part with no
700
+ * sequence started — the room woke between two chunks and lost the
701
+ * buffer — faults the socket (1011): its provider redials and resends.
702
+ */
703
+ private onChunk;
704
+ /**
705
+ * A quota refused the socket's frame (H3): it is not applied, and the
706
+ * socket is closed `4413` (`quota: <name>`), a refusal its provider
707
+ * reports (`onSyncRefused`) and does not redial. Never a dropped frame
708
+ * on a live socket: the sender's later frames would build on it and
709
+ * wait in the room's memory for good, unstored and unacknowledged; and
710
+ * a redial would resend it and meet the same quota.
711
+ */
712
+ private overQuota;
713
+ /**
714
+ * The update rate quota (H3): a token bucket per socket, refilled at
715
+ * `maxUpdatesPerSecond` up to ten seconds' worth. `false`: over it.
716
+ */
717
+ private allow;
718
+ /**
719
+ * The document quota (H3): would `incoming` bytes take the document
720
+ * past `maxDocumentBytes`? Its measure is what its records hold and what
721
+ * the engine holds waiting; update records count until compaction
722
+ * collapses them, so it compacts first when they would.
723
+ */
724
+ private overDocument;
472
725
  webSocketClose(ws: WebSocket, code: number, reason: string): boolean;
473
726
  webSocketError(ws: WebSocket): boolean;
474
727
  /** The departure the client may not have announced — from the attachment, so it works after a wake. */
475
728
  private depart;
476
729
  private onSync;
730
+ /** A client's Step2 or Update: admitted, applied, stored, relayed, validated, acknowledged. */
731
+ private onUpdate;
732
+ /** A shown block's state, read from the facade (`null`: it does not show). */
733
+ private blockState;
734
+ /**
735
+ * The index `validate` reads, for `doc`, set to record the frame of
736
+ * `origin`: every shown block's state and each parent's children, built
737
+ * once and kept from every change report. Its history (once the
738
+ * document is initialized: a history first initializes its document)
739
+ * records the frame for its compensation.
740
+ */
741
+ private validating;
742
+ /** Keep the index from one change report, recording what the frame being validated touched. */
743
+ private indexChange;
744
+ /**
745
+ * Ask `validate` about the frame just applied (H2); a denial is
746
+ * compensated by the room's own transaction: the history undo of that
747
+ * frame (`room.validate.inverse`), or, for the frame that initialized
748
+ * the document (no history recorded it), a delete of the blocks it
749
+ * added. Stored and sent to every socket, the sender's included.
750
+ */
751
+ private validateFrame;
477
752
  /**
478
753
  * A delete of items the room does not hold (a relayer's delete of an
479
754
  * author's edit a restore lost) waits in the engine for them: it is
@@ -549,6 +824,17 @@ export declare class DocumentRoom<Env = DocumentRoomEnv> extends DurableObject<E
549
824
  protected onLoad(): Promise<LoadedDocument | null | undefined>;
550
825
  /** Save — see {@link AttachDocumentOptions.onSave}. Not overridden: no alarm is ever set. */
551
826
  protected onSave(_document: SavedDocument): Promise<void>;
827
+ /**
828
+ * Validate a client frame — see {@link AttachDocumentOptions.validate}:
829
+ * `false` denies it, and the room writes its inverse. Not overridden: no
830
+ * frame is validated, and no block index is kept.
831
+ */
832
+ protected validate(_frame: FrameValidation): boolean | void;
833
+ /**
834
+ * One log entry (H14): `console.log` of it as JSON, unless the
835
+ * `EDYTOR_LOG` var is `off`. Override it to send entries elsewhere.
836
+ */
837
+ protected log(entry: RoomLogEntry): void;
552
838
  /**
553
839
  * Block roles — see {@link AttachDocumentOptions.semantics}. Read once,
554
840
  * at first use (after construction: it may return a subclass field).
@@ -564,6 +850,12 @@ export declare class DocumentRoom<Env = DocumentRoomEnv> extends DurableObject<E
564
850
  compact(): {
565
851
  rows: number;
566
852
  };
853
+ /** The room's counters, also over RPC — see {@link RoomMetrics}. */
854
+ metrics(): RoomMetrics;
855
+ /** When the room last stored a change, also over RPC — see {@link AttachedDocument.lastUpdated}. */
856
+ lastUpdated(): number | null;
857
+ /** The snapshot compression in flight, also over RPC — see {@link AttachedDocument.compressed}. */
858
+ compressed(): Promise<void>;
567
859
  /** Drop the stored waiting deletes, also over RPC — see {@link AttachedDocument.dropWaitingDeletes}. */
568
860
  dropWaitingDeletes(): {
569
861
  ranges: number;