edytor 1.0.0-rc.1 → 1.0.0-rc.2

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/cloudflare/DocumentRoom.d.ts +9 -60
  3. package/dist/cloudflare/DocumentRoom.js +6 -75
  4. package/dist/cloudflare/history.d.ts +2 -2
  5. package/dist/cloudflare/history.js +2 -2
  6. package/dist/cloudflare/room/access.js +1 -1
  7. package/dist/cloudflare/room/admission.js +1 -1
  8. package/dist/cloudflare/room/comments.d.ts +1 -1
  9. package/dist/cloudflare/room/comments.js +1 -1
  10. package/dist/cloudflare/room/context.d.ts +1 -1
  11. package/dist/cloudflare/room/context.js +1 -1
  12. package/dist/cloudflare/room/history.d.ts +1 -1
  13. package/dist/cloudflare/room/history.js +1 -1
  14. package/dist/cloudflare/room/moves.js +1 -1
  15. package/dist/cloudflare/room/presence.d.ts +1 -1
  16. package/dist/cloudflare/room/presence.js +1 -1
  17. package/dist/cloudflare/room/purge.js +1 -1
  18. package/dist/cloudflare/room/replicas.d.ts +1 -1
  19. package/dist/cloudflare/room/replicas.js +1 -1
  20. package/dist/cloudflare/room/scheduler.d.ts +1 -1
  21. package/dist/cloudflare/room/scheduler.js +2 -2
  22. package/dist/cloudflare/room/shared.d.ts +60 -0
  23. package/dist/cloudflare/room/shared.js +79 -0
  24. package/dist/cloudflare/room/storage.d.ts +1 -1
  25. package/dist/cloudflare/room/storage.js +1 -1
  26. package/dist/cloudflare/room/updates.js +1 -1
  27. package/dist/cloudflare/room/validation.d.ts +1 -1
  28. package/dist/cloudflare/room/validation.js +1 -1
  29. package/dist/cloudflare/routeDocumentSocket.js +1 -1
  30. package/dist/components/Edytor.svelte +2 -2
  31. package/dist/components/Text.svelte +1 -1
  32. package/dist/crdt/document.d.ts +0 -6
  33. package/dist/crdt/document.js +8 -16
  34. package/dist/crdt/protocols/envelope.d.ts +2 -2
  35. package/dist/crdt/providers/container.d.ts +2 -2
  36. package/dist/edytor.svelte.js +2 -2
  37. package/dist/events/onBeforeInput.js +1 -1
  38. package/dist/events/onDrop.js +1 -1
  39. package/dist/events/onFocus.js +8 -8
  40. package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -41
  41. package/dist/plugins/blockHandles/BlockHandleController.svelte.js +29 -287
  42. package/dist/plugins/blockHandles/dropIndicator.d.ts +37 -0
  43. package/dist/plugins/blockHandles/dropIndicator.js +252 -0
  44. package/dist/plugins/blockHandles/geometry.d.ts +28 -0
  45. package/dist/plugins/blockHandles/geometry.js +38 -0
  46. package/dist/selection/pointer.svelte.d.ts +32 -0
  47. package/dist/selection/pointer.svelte.js +316 -0
  48. package/dist/selection/selection.svelte.d.ts +0 -8
  49. package/dist/selection/selection.svelte.js +22 -289
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  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.
19
19
 
20
- > **Release candidate.** Edytor `1.0.0-rc.1` is the 1.0 release candidate (`edytor` on npm): its API is frozen, and a change to it before 1.0 is a listed breaking change. It has no production track record yet: read the [limitations](https://edytor.dev/docs/reference/limitations) before you build on it. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
20
+ > **Release candidate.** Edytor `1.0.0-rc.2` is the 1.0 release candidate (`edytor` on npm): its API is frozen, and a change to it before 1.0 is a listed breaking change. It has no production track record yet: read the [limitations](https://edytor.dev/docs/reference/limitations) before you build on it. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
21
21
 
22
22
  ## Features
23
23
 
@@ -47,7 +47,7 @@
47
47
  * replace: those deletes wait with them, in memory); compaction keeps
48
48
  * the pending records apart, and rewrites them as the deletes still
49
49
  * waiting; a frame that discards a forged stamp drops them. At most
50
- * {@link MAX_WAITING_DELETES} ranges wait, stored or in memory: a frame
50
+ * `MAX_WAITING_DELETES` ranges wait, stored or in memory: a frame
51
51
  * that would pass it has its waiting deletes dropped (refusal `waiting`,
52
52
  * left out of the ack), the rest applied. Compaction reclaims the
53
53
  * waiting deletes of client ids no user registered and no socket holds;
@@ -112,6 +112,7 @@ import type { CommitResult, ExportedBlocks, ImportReceipt, ImportRequest, LateEd
112
112
  import type { AwarenessEntry } from '../crdt/protocol.js';
113
113
  import type { DocumentSemanticsConfig, DocumentOperations, JSONBlock, JSONDoc, YDoc } from '../crdt/index.js';
114
114
  import type { CommentActor, CommentChange, CommentOutcome, CommentRequest, CommentSnapshot } from '../crdt/protocols/comments.js';
115
+ export { closedSocket, COMMENTS_HEADER, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, HISTORY_HEADER, HISTORY_KEY_HEADER, IDENTITY_HEADERS, MAX_REFUSALS, MAX_WAITING_DELETES, noTimers, PROBE_HEADER, PURGE_ORIGIN, RESTORE_ORIGIN, ROOM_ORIGIN, SOCKET_TAG } from './room/shared.js';
115
116
  /** Under SQLite-backed Durable Objects' 2 MB row cap, with room for the other columns. */
116
117
  export declare const DEFAULT_MAX_ROW_BYTES: number;
117
118
  /** Update records before the rows are merged into one snapshot. */
@@ -166,14 +167,6 @@ export declare const DEFAULT_MAX_PRESENCE_PER_SECOND = 50;
166
167
  * would send 50,000 frames a second.
167
168
  */
168
169
  export declare const DEFAULT_MAX_PRESENCE_FANOUT = 2000;
169
- /**
170
- * Delete ranges the room keeps waiting for items it does not hold (a
171
- * relayer's delete of an author's edit a restore lost). Every update the
172
- * engine applies re-reads them, so they are capped.
173
- */
174
- export declare const MAX_WAITING_DELETES = 1024;
175
- /** Refusals kept in `refusals` (the newest); `refusalCounts` counts every one. */
176
- export declare const MAX_REFUSALS = 100;
177
170
  /**
178
171
  * A client id and the user who owns it (the room's replica registry). An
179
172
  * empty `user` marks an id a restore without a registry left unowned: its
@@ -208,33 +201,6 @@ export type SavedDocument = {
208
201
  };
209
202
  /** What `onLoad` may return: JSON, a bare v14 update, or `{ update, replicas }` (a `SavedDocument`). */
210
203
  export type LoadedDocument = JSONDoc | Uint8Array | Pick<SavedDocument, 'update' | 'replicas'>;
211
- /** The header `routeDocumentSocket` sets on an authorized probe: `lastUpdated` or `snapshot`. */
212
- export declare const PROBE_HEADER = "X-Edytor-Probe";
213
- /** The transaction origin of the room's own edits (`transact`, `onLoad` seeds). */
214
- export declare const ROOM_ORIGIN: unique symbol;
215
- /** The transaction origin of a history restore: the room's restore history tracks it. */
216
- export declare const RESTORE_ORIGIN: unique symbol;
217
- /** The transaction origin of the room's purge: tracked by no history. */
218
- export declare const PURGE_ORIGIN: unique symbol;
219
- /** The header `routeDocumentHistory` sets on an authorized history request. */
220
- export declare const HISTORY_HEADER = "X-Edytor-History";
221
- /** The version key of a history `read` or `restore` request. */
222
- export declare const HISTORY_KEY_HEADER = "X-Edytor-History-Key";
223
- /** The header `routeDocumentComments` sets on an authorized comments request: `list` or `post`. */
224
- export declare const COMMENTS_HEADER = "X-Edytor-Comments";
225
- /**
226
- * Headers carrying the identity `routeDocumentSocket` verified — never the
227
- * client's. The user id is percent-encoded (`encodeURIComponent`): a header
228
- * value is trimmed and cannot carry every character, and a user id is any
229
- * string.
230
- */
231
- export declare const IDENTITY_HEADERS: {
232
- readonly user: "X-Edytor-User";
233
- readonly replica: "X-Edytor-Replica";
234
- readonly access: "X-Edytor-Access";
235
- /** When the credential expires, ms since the epoch (absent: never). */
236
- readonly expires: "X-Edytor-Expires";
237
- };
238
204
  /**
239
205
  * Optional knobs (strings, as `vars` arrive): row size, outgoing frame
240
206
  * limit and compaction threshold. The defaults are the platform limits.
@@ -434,15 +400,6 @@ export type RoomLogEntry = {
434
400
  };
435
401
  /** `pending`: deletes of items the room does not hold yet (they wait for them). */
436
402
  type RowKind = 'generation' | 'update' | 'snapshot' | 'pending';
437
- /**
438
- * An upgrade accepted and closed at once, so the client reads `code`: a
439
- * browser sees an HTTP error at the upgrade only as `1006`, which a
440
- * provider cannot tell from a network failure. Refuse a dial your Worker
441
- * turns away itself with it: the provider stops at `1008` or `4xxx`
442
- * (except `4401`, redialed), and redials after anything else (`CLOSE`,
443
- * `isRefusal` in `crdt/providers/room.ts`).
444
- */
445
- export declare const closedSocket: (code: number, reason: string) => Response;
446
403
  /** A stored record, reassembled: its kind, record number, bytes, and whether they are v2 (a v2 container's snapshot). */
447
404
  export type StoredRecord = {
448
405
  kind: RowKind;
@@ -450,8 +407,6 @@ export type StoredRecord = {
450
407
  bytes: Uint8Array<ArrayBuffer>;
451
408
  v2: boolean;
452
409
  };
453
- /** Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating. */
454
- export declare const noTimers: <T>(fn: () => T) => T;
455
410
  /** A block as `validate` reads it, before or after a frame. */
456
411
  export type ValidatedBlock = {
457
412
  id: string;
@@ -496,24 +451,20 @@ export type CommentOptions = {
496
451
  maxLength?: number;
497
452
  /**
498
453
  * The bytes the room's comments may hold (default
499
- * {@link DEFAULT_MAX_COMMENT_BYTES}, 4 MiB): each body and quote in UTF-8
454
+ * `DEFAULT_MAX_COMMENT_BYTES`, 4 MiB): each body and quote in UTF-8
500
455
  * plus 128 a comment. A thread or reply past it is refused `413`; a
501
456
  * delete frees its bytes.
502
457
  */
503
458
  maxBytes?: number;
504
459
  /**
505
460
  * Comment requests per second (default
506
- * {@link DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND}, a ten-second burst):
461
+ * `DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND`, a ten-second burst):
507
462
  * over HTTP per verified user (past it, `429`), and a socket's comment
508
463
  * messages per socket (past it, dropped). RPC calls are the host's own and
509
464
  * are not counted.
510
465
  */
511
466
  maxRequestsPerSecond?: number;
512
467
  };
513
- /** The bytes a room's comments hold at most, by default (4 MiB). */
514
- export declare const DEFAULT_MAX_COMMENT_BYTES: number;
515
- /** Comment requests per second and user (or socket), by default. */
516
- export declare const DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND = 2;
517
468
  /** What {@link attachRoom} (and `DocumentRoom`) takes. */
518
469
  export type AttachRoomOptions = {
519
470
  /**
@@ -599,7 +550,8 @@ export type AttachRoomOptions = {
599
550
  */
600
551
  semantics?: DocumentSemanticsConfig;
601
552
  /**
602
- * Version history in KV (`room.history.*`): the room writes its
553
+ * Version history, in the store `HistoryOptions.store` names (a KV namespace, an R2 bucket or
554
+ * the room's own storage): the room writes its
603
555
  * state twice a day — the morning's at local noon, the evening's at
604
556
  * midnight, in `timeZone` — when it changed, for `retentionDays`; read
605
557
  * and restore them with `listHistory`, `readHistory`, `restoreHistory`
@@ -622,8 +574,6 @@ export type AttachRoomOptions = {
622
574
  */
623
575
  comments?: CommentOptions | false;
624
576
  };
625
- /** The tag of the document's sockets: other sockets of the object are left to you. */
626
- export declare const SOCKET_TAG = "edytor";
627
577
  /**
628
578
  * One edytor document living in a Durable Object's storage: the room
629
579
  * logic, independent of the class that hosts it. Create it with
@@ -648,7 +598,7 @@ export declare class AttachedDocument {
648
598
  failure: Error | null;
649
599
  /** Latest presence entry per replica, for join snapshots (memory: refills after a wake). */
650
600
  presence: Map<number, E.AwarenessEntry>;
651
- /** The newest {@link MAX_REFUSALS} refusals since this instance started (diagnostics; memory only). */
601
+ /** The newest `MAX_REFUSALS` refusals since this instance started (diagnostics; memory only). */
652
602
  refusals: Refusal[];
653
603
  /** Every refusal since this instance started, by reason. */
654
604
  refusalCounts: Partial<Record<Refusal['reason'], number>>;
@@ -746,7 +696,7 @@ export declare class AttachedDocument {
746
696
  };
747
697
  /**
748
698
  * When the room last stored a change (ms since the epoch), or `null`
749
- * for a room that stored none since 0.1.0-next.23. Cheap: one
699
+ * for a room that stored none since it records the time. Cheap: one
750
700
  * row, no document. `routeDocumentSocket` answers it over HTTP
751
701
  * (`GET <room>?lastUpdated`), also over RPC.
752
702
  */
@@ -857,7 +807,7 @@ export declare class AttachedDocument {
857
807
  metrics(): RoomMetrics;
858
808
  /**
859
809
  * Drop every waiting delete, stored or in memory (callable over RPC):
860
- * the way out when a writer filled {@link MAX_WAITING_DELETES} with
810
+ * the way out when a writer filled `MAX_WAITING_DELETES` with
861
811
  * deletes of registered ids' items, which compaction keeps. A dropped
862
812
  * delete's item shows if it ever arrives — but for an entry a waiting
863
813
  * rewrite replaces, which that rewrite deletes should it integrate.
@@ -1056,4 +1006,3 @@ export declare class DocumentRoom<Env = DocumentRoomEnv> extends DurableObject<E
1056
1006
  get origin(): AttachedDocument['origin'];
1057
1007
  records(): ReturnType<AttachedDocument['records']>;
1058
1008
  }
1059
- export {};
@@ -47,7 +47,7 @@
47
47
  * replace: those deletes wait with them, in memory); compaction keeps
48
48
  * the pending records apart, and rewrites them as the deletes still
49
49
  * waiting; a frame that discards a forged stamp drops them. At most
50
- * {@link MAX_WAITING_DELETES} ranges wait, stored or in memory: a frame
50
+ * `MAX_WAITING_DELETES` ranges wait, stored or in memory: a frame
51
51
  * that would pass it has its waiting deletes dropped (refusal `waiting`,
52
52
  * left out of the ack), the rest applied. Compaction reclaims the
53
53
  * waiting deletes of client ids no user registered and no socket holds;
@@ -120,6 +120,8 @@ import { ReplicaRegistry } from './room/replicas.js';
120
120
  import { Scheduler } from './room/scheduler.js';
121
121
  import { RoomStorage } from './room/storage.js';
122
122
  import { RoomValidation } from './room/validation.js';
123
+ import { noTimers, SOCKET_TAG } from './room/shared.js';
124
+ export { closedSocket, COMMENTS_HEADER, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, HISTORY_HEADER, HISTORY_KEY_HEADER, IDENTITY_HEADERS, MAX_REFUSALS, MAX_WAITING_DELETES, noTimers, PROBE_HEADER, PURGE_ORIGIN, RESTORE_ORIGIN, ROOM_ORIGIN, SOCKET_TAG } from './room/shared.js';
123
125
  /** Under SQLite-backed Durable Objects' 2 MB row cap, with room for the other columns. */
124
126
  export const DEFAULT_MAX_ROW_BYTES = 2_000_000 - 4096;
125
127
  /** Update records before the rows are merged into one snapshot. */
@@ -177,75 +179,6 @@ export const DEFAULT_MAX_PRESENCE_FANOUT = 2000;
177
179
  /** Seconds of the update rate a socket may spend at once. */
178
180
  /** The ceiling of a quota knob (a host may raise the defaults up to it). */
179
181
  const QUOTA_CEILING = 2 ** 40;
180
- /**
181
- * Delete ranges the room keeps waiting for items it does not hold (a
182
- * relayer's delete of an author's edit a restore lost). Every update the
183
- * engine applies re-reads them, so they are capped.
184
- */
185
- export const MAX_WAITING_DELETES = 1024;
186
- /** Refusals kept in `refusals` (the newest); `refusalCounts` counts every one. */
187
- export const MAX_REFUSALS = 100;
188
- /** The header `routeDocumentSocket` sets on an authorized probe: `lastUpdated` or `snapshot`. */
189
- export const PROBE_HEADER = 'X-Edytor-Probe';
190
- /** The transaction origin of the room's own edits (`transact`, `onLoad` seeds). */
191
- export const ROOM_ORIGIN = Symbol('edytor-room');
192
- /** The transaction origin of a history restore: the room's restore history tracks it. */
193
- export const RESTORE_ORIGIN = Symbol('edytor-restore');
194
- /** The transaction origin of the room's purge: tracked by no history. */
195
- export const PURGE_ORIGIN = Symbol('edytor-purge');
196
- /** The header `routeDocumentHistory` sets on an authorized history request. */
197
- export const HISTORY_HEADER = 'X-Edytor-History';
198
- /** The version key of a history `read` or `restore` request. */
199
- export const HISTORY_KEY_HEADER = 'X-Edytor-History-Key';
200
- /** The header `routeDocumentComments` sets on an authorized comments request: `list` or `post`. */
201
- export const COMMENTS_HEADER = 'X-Edytor-Comments';
202
- /**
203
- * Headers carrying the identity `routeDocumentSocket` verified — never the
204
- * client's. The user id is percent-encoded (`encodeURIComponent`): a header
205
- * value is trimmed and cannot carry every character, and a user id is any
206
- * string.
207
- */
208
- export const IDENTITY_HEADERS = {
209
- user: 'X-Edytor-User',
210
- replica: 'X-Edytor-Replica',
211
- access: 'X-Edytor-Access',
212
- /** When the credential expires, ms since the epoch (absent: never). */
213
- expires: 'X-Edytor-Expires'
214
- };
215
- /**
216
- * An upgrade accepted and closed at once, so the client reads `code`: a
217
- * browser sees an HTTP error at the upgrade only as `1006`, which a
218
- * provider cannot tell from a network failure. Refuse a dial your Worker
219
- * turns away itself with it: the provider stops at `1008` or `4xxx`
220
- * (except `4401`, redialed), and redials after anything else (`CLOSE`,
221
- * `isRefusal` in `crdt/providers/room.ts`).
222
- */
223
- export const closedSocket = (code, reason) => {
224
- const [client, server] = Object.values(new WebSocketPair());
225
- server.accept();
226
- server.close(code, reason);
227
- return new Response(null, { status: 101, webSocket: client });
228
- };
229
- /** Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating. */
230
- export const noTimers = (fn) => {
231
- const g = globalThis;
232
- const saved = [g.setTimeout, g.setInterval];
233
- const forbidden = () => {
234
- throw new Error('room scheduled a timer (a Durable Object could not hibernate)');
235
- };
236
- g.setTimeout = forbidden;
237
- g.setInterval = forbidden;
238
- try {
239
- return fn();
240
- }
241
- finally {
242
- [g.setTimeout, g.setInterval] = saved;
243
- }
244
- };
245
- /** The bytes a room's comments hold at most, by default (4 MiB). */
246
- export const DEFAULT_MAX_COMMENT_BYTES = 4 * 1024 * 1024;
247
- /** Comment requests per second and user (or socket), by default. */
248
- export const DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND = 2;
249
182
  /** `EDYTOR_PURGE_AFTER_DAYS`: `off` (or `false`) never purges; a number of days; anything else the default. */
250
183
  const purgeAfterDays = (value) => {
251
184
  if (value === 'off' || value === 'false' || value === false)
@@ -253,8 +186,6 @@ const purgeAfterDays = (value) => {
253
186
  const n = Number(value);
254
187
  return Number.isInteger(n) && n > 0 ? n : undefined;
255
188
  };
256
- /** The tag of the document's sockets: other sockets of the object are left to you. */
257
- export const SOCKET_TAG = 'edytor';
258
189
  /**
259
190
  * One edytor document living in a Durable Object's storage: the room
260
191
  * logic, independent of the class that hosts it. Create it with
@@ -279,7 +210,7 @@ export class AttachedDocument {
279
210
  failure = null;
280
211
  /** Latest presence entry per replica, for join snapshots (memory: refills after a wake). */
281
212
  presence = new Map();
282
- /** The newest {@link MAX_REFUSALS} refusals since this instance started (diagnostics; memory only). */
213
+ /** The newest `MAX_REFUSALS` refusals since this instance started (diagnostics; memory only). */
283
214
  refusals = [];
284
215
  /** Every refusal since this instance started, by reason. */
285
216
  refusalCounts = {};
@@ -427,7 +358,7 @@ export class AttachedDocument {
427
358
  }
428
359
  /**
429
360
  * When the room last stored a change (ms since the epoch), or `null`
430
- * for a room that stored none since 0.1.0-next.23. Cheap: one
361
+ * for a room that stored none since it records the time. Cheap: one
431
362
  * row, no document. `routeDocumentSocket` answers it over HTTP
432
363
  * (`GET <room>?lastUpdated`), also over RPC.
433
364
  */
@@ -582,7 +513,7 @@ export class AttachedDocument {
582
513
  }
583
514
  /**
584
515
  * Drop every waiting delete, stored or in memory (callable over RPC):
585
- * the way out when a writer filled {@link MAX_WAITING_DELETES} with
516
+ * the way out when a writer filled `MAX_WAITING_DELETES` with
586
517
  * deletes of registered ids' items, which compaction keeps. A dropped
587
518
  * delete's item shows if it ever arrives — but for an entry a waiting
588
519
  * rewrite replaces, which that rewrite deletes should it integrate.
@@ -200,8 +200,8 @@ export declare const historyEntry: (room: string, name: string, metadata: unknow
200
200
  * Versions in a Workers KV namespace (the room history's first store): KV's
201
201
  * TTL expires them (`nativeTtl`, `expirationTtl` = the retention, at least
202
202
  * KV's 60 s), its listing's `expiration` is each one's `expiresAt`, its
203
- * metadata the room's. Values up to KV's 25 MiB. Versions written by
204
- * 0.1.0-next.24 and next.25 (a bare namespace as the store) read as they are.
203
+ * metadata the room's. Values up to KV's 25 MiB. Versions written by the
204
+ * pre-releases that took a bare namespace as the store read as they are.
205
205
  */
206
206
  export declare const kvHistory: (namespace: KVLike) => HistoryStore;
207
207
  /**
@@ -145,8 +145,8 @@ const finite = (value) => {
145
145
  * Versions in a Workers KV namespace (the room history's first store): KV's
146
146
  * TTL expires them (`nativeTtl`, `expirationTtl` = the retention, at least
147
147
  * KV's 60 s), its listing's `expiration` is each one's `expiresAt`, its
148
- * metadata the room's. Values up to KV's 25 MiB. Versions written by
149
- * 0.1.0-next.24 and next.25 (a bare namespace as the store) read as they are.
148
+ * metadata the room's. Values up to KV's 25 MiB. Versions written by the
149
+ * pre-releases that took a bare namespace as the store read as they are.
150
150
  */
151
151
  export const kvHistory = (namespace) => ({
152
152
  maxValueBytes: HISTORY_MAX_VALUE_BYTES,
@@ -6,7 +6,7 @@
6
6
  * revocation, a changed access, an expired credential.
7
7
  */
8
8
  import { CLOSE } from '../../crdt/providers/room.js';
9
- import { COMMENTS_HEADER, HISTORY_HEADER, HISTORY_KEY_HEADER, IDENTITY_HEADERS, PROBE_HEADER, SOCKET_TAG, closedSocket, noTimers } from '../DocumentRoom.js';
9
+ import { COMMENTS_HEADER, HISTORY_HEADER, HISTORY_KEY_HEADER, IDENTITY_HEADERS, PROBE_HEADER, SOCKET_TAG, closedSocket, noTimers } from './shared.js';
10
10
  import { STORAGE_FAILURE } from './context.js';
11
11
  import { readOnlyFrame, step1Frame } from './frames.js';
12
12
  import { StorageFault } from './replicas.js';
@@ -12,7 +12,7 @@
12
12
  import { Y } from '../../crdt/engine.js';
13
13
  import * as E from '../../crdt/protocol.js';
14
14
  import { ChunkLimitError, ChunkSequenceError, CLOSE } from '../../crdt/providers/room.js';
15
- import { MAX_WAITING_DELETES, noTimers } from '../DocumentRoom.js';
15
+ import { MAX_WAITING_DELETES, noTimers } from './shared.js';
16
16
  import { STORAGE_FAILURE, now, sync, tally } from './context.js';
17
17
  import { attributionWrites, BINDING_PREFIX, forgedAttributionDeletes, forgedDeletes, forgedWriters } from './forged.js';
18
18
  import { MalformedFrame, decode, readOnlyDenialFrame, savedFrame, step1Frame, storedStep2, updateFrame } from './frames.js';
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import * as E from '../../crdt/protocol.js';
10
10
  import { type CommentActor, type CommentOutcome, type CommentRequest, type CommentSnapshot } from '../../crdt/protocols/comments.js';
11
- import { type Attachment, type SocketIdentity } from '../DocumentRoom.js';
11
+ import type { Attachment, SocketIdentity } from '../DocumentRoom.js';
12
12
  import { type RoomContext } from './context.js';
13
13
  /** The largest request body `routeDocumentComments` forwards, in bytes. */
14
14
  export declare const MAX_COMMENT_REQUEST_BYTES: number;
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import * as E from '../../crdt/protocol.js';
10
10
  import { COMMENT_MARK, COMMENT_ROW_BYTES, COMMENT_STATUS, commentAnchors, decideComment, OVER_BYTE_QUOTA, parseCommentRequest, validCommentId } from '../../crdt/protocols/comments.js';
11
- import { DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, SOCKET_TAG, noTimers } from '../DocumentRoom.js';
11
+ import { DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, SOCKET_TAG, noTimers } from './shared.js';
12
12
  import { allowance } from './admission.js';
13
13
  import { knob } from './context.js';
14
14
  import { decode } from './frames.js';
@@ -10,7 +10,7 @@
10
10
  import { Y } from '../../crdt/engine.js';
11
11
  import * as E from '../../crdt/protocol.js';
12
12
  import type { DocumentSemanticsConfig, EdytorDoc, JSONDoc, YDoc } from '../../crdt/index.js';
13
- import { type AttachRoomOptions, type AttachedDocument, type Refusal, type RoomLogEntry, type Timing } from '../DocumentRoom.js';
13
+ import type { AttachRoomOptions, AttachedDocument, Refusal, RoomLogEntry, Timing } from '../DocumentRoom.js';
14
14
  import type { RoomAccess } from './access.js';
15
15
  import type { Admission } from './admission.js';
16
16
  import type { RoomComments } from './comments.js';
@@ -12,7 +12,7 @@ import * as E from '../../crdt/protocol.js';
12
12
  import { CLOSE } from '../../crdt/providers/room.js';
13
13
  import { asEngineDoc } from '../../crdt/structs.js';
14
14
  import { defaultSemantics, facadeConfigOf, semanticsDigest } from '../../crdt/semantics.js';
15
- import { MAX_REFUSALS, ROOM_ORIGIN, SOCKET_TAG, noTimers } from '../DocumentRoom.js';
15
+ import { MAX_REFUSALS, ROOM_ORIGIN, SOCKET_TAG, noTimers } from './shared.js';
16
16
  /** The engine API the room is built on (the Worker-safe CRDT entry). */
17
17
  export const crdt = E.bindCrdt(Y);
18
18
  export const sync = crdt.sync;
@@ -1,5 +1,5 @@
1
1
  import type { JSONDoc } from '../../crdt/index.js';
2
- import { type RestoreResult, type SocketIdentity } from '../DocumentRoom.js';
2
+ import type { RestoreResult, SocketIdentity } from '../DocumentRoom.js';
3
3
  import { type HistoryEntry, type HistoryOptions, type HistoryStore } from '../history.js';
4
4
  import { type Decoded, type RoomContext } from './context.js';
5
5
  /** The history settings, resolved. */
@@ -8,7 +8,7 @@ import { Y } from '../../crdt/engine.js';
8
8
  import * as E from '../../crdt/protocol.js';
9
9
  import { gunzip, packed } from '../../crdt/storage.js';
10
10
  import { asEngineDoc, collectable, collectNow, collects, engineOps, stepsOf } from '../../crdt/structs.js';
11
- import { PURGE_ORIGIN, RESTORE_ORIGIN, noTimers } from '../DocumentRoom.js';
11
+ import { PURGE_ORIGIN, RESTORE_ORIGIN, noTimers } from './shared.js';
12
12
  import { DEFAULT_RETENTION_DAYS, HISTORY_MAX_VALUE_BYTES, fitMetadata, historyEntry, historyKey, historyPrefix, parseHistoryKey, resolveHistoryStore, slotAt, slotEnd, validTimeZone } from '../history.js';
13
13
  import { DAY, crdt, knob } from './context.js';
14
14
  import { admit, deletesUpdate, liveState } from './updates.js';
@@ -8,7 +8,7 @@ import { Y } from '../../crdt/engine.js';
8
8
  import { DEL_PREFIX, REGISTRY_KEY } from '../../crdt/schema.js';
9
9
  import { asEngineDoc, attrItems } from '../../crdt/structs.js';
10
10
  import { toBlockSpec } from '../../utils/json.js';
11
- import { noTimers } from '../DocumentRoom.js';
11
+ import { noTimers } from './shared.js';
12
12
  import { DEFAULT_MOVE_GRACE_DAYS, contentOf, flattenMoved, hunk, mergeData, subtreesOf, threeWay, unitsOf } from '../move.js';
13
13
  import { DAY, crdt } from './context.js';
14
14
  /** A forward of late edits that failed runs again this much later (`room.move.late`). */
@@ -1,6 +1,6 @@
1
1
  import type { AwarenessEntry } from '../../crdt/protocol.js';
2
2
  import type { YDoc } from '../../crdt/index.js';
3
- import { type Attachment } from '../DocumentRoom.js';
3
+ import type { Attachment } from '../DocumentRoom.js';
4
4
  import { type RoomContext } from './context.js';
5
5
  export declare class RoomPresence {
6
6
  private readonly room;
@@ -7,7 +7,7 @@
7
7
  * the socket goes.
8
8
  */
9
9
  import { semanticsMismatch } from '../../crdt/semantics.js';
10
- import { SOCKET_TAG } from '../DocumentRoom.js';
10
+ import { SOCKET_TAG } from './shared.js';
11
11
  import { allowance } from './admission.js';
12
12
  import { encodeJSON } from './context.js';
13
13
  import { encodePresenceEntry, presenceFrame, presenceFrameOf } from './frames.js';
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { Y } from '../../crdt/engine.js';
8
8
  import { asEngineDoc } from '../../crdt/structs.js';
9
- import { PURGE_ORIGIN, noTimers } from '../DocumentRoom.js';
9
+ import { PURGE_ORIGIN, noTimers } from './shared.js';
10
10
  import { DEFAULT_RETENTION_DAYS } from '../history.js';
11
11
  import { DAY, crdt, knob } from './context.js';
12
12
  import { sameBytes } from './updates.js';
@@ -1,5 +1,5 @@
1
1
  import type { YDoc } from '../../crdt/index.js';
2
- import { type Attachment } from '../DocumentRoom.js';
2
+ import type { Attachment } from '../DocumentRoom.js';
3
3
  import type { RoomContext } from './context.js';
4
4
  /** A SQLite fault outside the append path (the replica registry). */
5
5
  export declare class StorageFault extends Error {
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { ATTRIBUTION_ROOT } from '../../crdt/schema.js';
9
9
  import { asEngineDoc } from '../../crdt/structs.js';
10
- import { ROOM_ORIGIN } from '../DocumentRoom.js';
10
+ import { ROOM_ORIGIN } from './shared.js';
11
11
  import { BINDING_PREFIX } from './forged.js';
12
12
  import { stateVector } from './updates.js';
13
13
  /** A SQLite fault outside the append path (the replica registry). */
@@ -34,6 +34,6 @@ export declare class Scheduler {
34
34
  unschedule(task: Task): void;
35
35
  /** Set the alarm to the earliest due time (none due: the alarm set is left, and finds nothing). */
36
36
  private arm;
37
- /** The due times stored (`due.<task>`); an alarm armed by 0.1.0-next.23 (none stored) is a due save. */
37
+ /** The due times stored (`due.<task>`); an alarm armed before the scheduler (none stored) is a due save. */
38
38
  readDues(): void;
39
39
  }
@@ -4,7 +4,7 @@
4
4
  * earliest. The tasks themselves are their owners' (`AttachedDocument`
5
5
  * wires them): this module decides only when each runs.
6
6
  */
7
- import { noTimers } from '../DocumentRoom.js';
7
+ import { noTimers } from './shared.js';
8
8
  const TASKS = ['history', 'save', 'purge', 'retention', 'forward', 'expiry'];
9
9
  export class Scheduler {
10
10
  room;
@@ -103,7 +103,7 @@ export class Scheduler {
103
103
  this.armed = next;
104
104
  void this.room.ctx.storage.setAlarm(next);
105
105
  }
106
- /** The due times stored (`due.<task>`); an alarm armed by 0.1.0-next.23 (none stored) is a due save. */
106
+ /** The due times stored (`due.<task>`); an alarm armed before the scheduler (none stored) is a due save. */
107
107
  readDues() {
108
108
  const rows = this.room.sql
109
109
  .exec(`SELECT key, value FROM ${this.room.tables.meta} WHERE key LIKE 'due.%'`)
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The room's shared constants and two helpers every room module reads:
3
+ * the headers the router sets, the transaction origins, the socket tag,
4
+ * the refusal caps and the comment defaults, `noTimers` and
5
+ * `closedSocket`. A leaf: it imports nothing of the room, so the room's
6
+ * modules read it without importing `DocumentRoom.ts` (which re-exports
7
+ * every name here). Worker-safe.
8
+ */
9
+ /**
10
+ * Delete ranges the room keeps waiting for items it does not hold (a
11
+ * relayer's delete of an author's edit a restore lost). Every update the
12
+ * engine applies re-reads them, so they are capped.
13
+ */
14
+ export declare const MAX_WAITING_DELETES = 1024;
15
+ /** Refusals kept in `refusals` (the newest); `refusalCounts` counts every one. */
16
+ export declare const MAX_REFUSALS = 100;
17
+ /** The header `routeDocumentSocket` sets on an authorized probe: `lastUpdated` or `snapshot`. */
18
+ export declare const PROBE_HEADER = "X-Edytor-Probe";
19
+ /** The transaction origin of the room's own edits (`transact`, `onLoad` seeds). */
20
+ export declare const ROOM_ORIGIN: unique symbol;
21
+ /** The transaction origin of a history restore: the room's restore history tracks it. */
22
+ export declare const RESTORE_ORIGIN: unique symbol;
23
+ /** The transaction origin of the room's purge: tracked by no history. */
24
+ export declare const PURGE_ORIGIN: unique symbol;
25
+ /** The header `routeDocumentHistory` sets on an authorized history request. */
26
+ export declare const HISTORY_HEADER = "X-Edytor-History";
27
+ /** The version key of a history `read` or `restore` request. */
28
+ export declare const HISTORY_KEY_HEADER = "X-Edytor-History-Key";
29
+ /** The header `routeDocumentComments` sets on an authorized comments request: `list` or `post`. */
30
+ export declare const COMMENTS_HEADER = "X-Edytor-Comments";
31
+ /**
32
+ * Headers carrying the identity `routeDocumentSocket` verified — never the
33
+ * client's. The user id is percent-encoded (`encodeURIComponent`): a header
34
+ * value is trimmed and cannot carry every character, and a user id is any
35
+ * string.
36
+ */
37
+ export declare const IDENTITY_HEADERS: {
38
+ readonly user: "X-Edytor-User";
39
+ readonly replica: "X-Edytor-Replica";
40
+ readonly access: "X-Edytor-Access";
41
+ /** When the credential expires, ms since the epoch (absent: never). */
42
+ readonly expires: "X-Edytor-Expires";
43
+ };
44
+ /**
45
+ * An upgrade accepted and closed at once, so the client reads `code`: a
46
+ * browser sees an HTTP error at the upgrade only as `1006`, which a
47
+ * provider cannot tell from a network failure. Refuse a dial your Worker
48
+ * turns away itself with it: the provider stops at `1008` or `4xxx`
49
+ * (except `4401`, redialed), and redials after anything else (`CLOSE`,
50
+ * `isRefusal` in `crdt/providers/room.ts`).
51
+ */
52
+ export declare const closedSocket: (code: number, reason: string) => Response;
53
+ /** Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating. */
54
+ export declare const noTimers: <T>(fn: () => T) => T;
55
+ /** The bytes a room's comments hold at most, by default (4 MiB). */
56
+ export declare const DEFAULT_MAX_COMMENT_BYTES: number;
57
+ /** Comment requests per second and user (or socket), by default. */
58
+ export declare const DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND = 2;
59
+ /** The tag of the document's sockets: other sockets of the object are left to you. */
60
+ export declare const SOCKET_TAG = "edytor";
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The room's shared constants and two helpers every room module reads:
3
+ * the headers the router sets, the transaction origins, the socket tag,
4
+ * the refusal caps and the comment defaults, `noTimers` and
5
+ * `closedSocket`. A leaf: it imports nothing of the room, so the room's
6
+ * modules read it without importing `DocumentRoom.ts` (which re-exports
7
+ * every name here). Worker-safe.
8
+ */
9
+ /**
10
+ * Delete ranges the room keeps waiting for items it does not hold (a
11
+ * relayer's delete of an author's edit a restore lost). Every update the
12
+ * engine applies re-reads them, so they are capped.
13
+ */
14
+ export const MAX_WAITING_DELETES = 1024;
15
+ /** Refusals kept in `refusals` (the newest); `refusalCounts` counts every one. */
16
+ export const MAX_REFUSALS = 100;
17
+ /** The header `routeDocumentSocket` sets on an authorized probe: `lastUpdated` or `snapshot`. */
18
+ export const PROBE_HEADER = 'X-Edytor-Probe';
19
+ /** The transaction origin of the room's own edits (`transact`, `onLoad` seeds). */
20
+ export const ROOM_ORIGIN = Symbol('edytor-room');
21
+ /** The transaction origin of a history restore: the room's restore history tracks it. */
22
+ export const RESTORE_ORIGIN = Symbol('edytor-restore');
23
+ /** The transaction origin of the room's purge: tracked by no history. */
24
+ export const PURGE_ORIGIN = Symbol('edytor-purge');
25
+ /** The header `routeDocumentHistory` sets on an authorized history request. */
26
+ export const HISTORY_HEADER = 'X-Edytor-History';
27
+ /** The version key of a history `read` or `restore` request. */
28
+ export const HISTORY_KEY_HEADER = 'X-Edytor-History-Key';
29
+ /** The header `routeDocumentComments` sets on an authorized comments request: `list` or `post`. */
30
+ export const COMMENTS_HEADER = 'X-Edytor-Comments';
31
+ /**
32
+ * Headers carrying the identity `routeDocumentSocket` verified — never the
33
+ * client's. The user id is percent-encoded (`encodeURIComponent`): a header
34
+ * value is trimmed and cannot carry every character, and a user id is any
35
+ * string.
36
+ */
37
+ export const IDENTITY_HEADERS = {
38
+ user: 'X-Edytor-User',
39
+ replica: 'X-Edytor-Replica',
40
+ access: 'X-Edytor-Access',
41
+ /** When the credential expires, ms since the epoch (absent: never). */
42
+ expires: 'X-Edytor-Expires'
43
+ };
44
+ /**
45
+ * An upgrade accepted and closed at once, so the client reads `code`: a
46
+ * browser sees an HTTP error at the upgrade only as `1006`, which a
47
+ * provider cannot tell from a network failure. Refuse a dial your Worker
48
+ * turns away itself with it: the provider stops at `1008` or `4xxx`
49
+ * (except `4401`, redialed), and redials after anything else (`CLOSE`,
50
+ * `isRefusal` in `crdt/providers/room.ts`).
51
+ */
52
+ export const closedSocket = (code, reason) => {
53
+ const [client, server] = Object.values(new WebSocketPair());
54
+ server.accept();
55
+ server.close(code, reason);
56
+ return new Response(null, { status: 101, webSocket: client });
57
+ };
58
+ /** Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating. */
59
+ export const noTimers = (fn) => {
60
+ const g = globalThis;
61
+ const saved = [g.setTimeout, g.setInterval];
62
+ const forbidden = () => {
63
+ throw new Error('room scheduled a timer (a Durable Object could not hibernate)');
64
+ };
65
+ g.setTimeout = forbidden;
66
+ g.setInterval = forbidden;
67
+ try {
68
+ return fn();
69
+ }
70
+ finally {
71
+ [g.setTimeout, g.setInterval] = saved;
72
+ }
73
+ };
74
+ /** The bytes a room's comments hold at most, by default (4 MiB). */
75
+ export const DEFAULT_MAX_COMMENT_BYTES = 4 * 1024 * 1024;
76
+ /** Comment requests per second and user (or socket), by default. */
77
+ export const DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND = 2;
78
+ /** The tag of the document's sockets: other sockets of the object are left to you. */
79
+ export const SOCKET_TAG = 'edytor';