edytor 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +3 -3
  2. package/dist/cloudflare/DocumentRoom.d.ts +77 -2
  3. package/dist/cloudflare/DocumentRoom.js +37 -1
  4. package/dist/cloudflare/index.d.ts +1 -1
  5. package/dist/cloudflare/index.js +1 -1
  6. package/dist/cloudflare/room/access.d.ts +6 -1
  7. package/dist/cloudflare/room/access.js +30 -4
  8. package/dist/cloudflare/room/admission.d.ts +11 -0
  9. package/dist/cloudflare/room/admission.js +43 -10
  10. package/dist/cloudflare/room/context.d.ts +11 -2
  11. package/dist/cloudflare/room/context.js +17 -6
  12. package/dist/cloudflare/room/forged.d.ts +1 -1
  13. package/dist/cloudflare/room/forged.js +11 -9
  14. package/dist/cloudflare/room/frames.d.ts +2 -0
  15. package/dist/cloudflare/room/frames.js +2 -0
  16. package/dist/cloudflare/room/presence.js +1 -4
  17. package/dist/cloudflare/room/reseed.d.ts +93 -0
  18. package/dist/cloudflare/room/reseed.js +500 -0
  19. package/dist/cloudflare/room/scheduler.d.ts +1 -1
  20. package/dist/cloudflare/room/scheduler.js +9 -1
  21. package/dist/cloudflare/room/shared.d.ts +10 -0
  22. package/dist/cloudflare/room/shared.js +10 -0
  23. package/dist/cloudflare/room/storage.d.ts +7 -0
  24. package/dist/cloudflare/room/storage.js +33 -1
  25. package/dist/components/Edytor.svelte +18 -3
  26. package/dist/components/Edytor.svelte.d.ts +10 -1
  27. package/dist/components/EdytorRoot.svelte +17 -0
  28. package/dist/components/EdytorRoot.svelte.d.ts +4 -0
  29. package/dist/components/Suggestion.svelte +1 -1
  30. package/dist/crdt/attribution/block.d.ts +3 -0
  31. package/dist/crdt/attribution/block.js +3 -2
  32. package/dist/crdt/doc/suggest.d.ts +6 -2
  33. package/dist/crdt/doc/suggest.js +6 -5
  34. package/dist/crdt/edytor-doc.d.ts +1 -1
  35. package/dist/crdt/edytor-doc.js +1 -1
  36. package/dist/crdt/engine-room.d.ts +4 -0
  37. package/dist/crdt/engine-room.js +6 -0
  38. package/dist/crdt/index.d.ts +2 -1
  39. package/dist/crdt/protocol.d.ts +2 -2
  40. package/dist/crdt/protocol.js +2 -2
  41. package/dist/crdt/protocols/auth.d.ts +8 -1
  42. package/dist/crdt/protocols/auth.js +14 -1
  43. package/dist/crdt/providers/index.d.ts +1 -0
  44. package/dist/crdt/providers/index.js +22 -3
  45. package/dist/crdt/providers/room.d.ts +15 -0
  46. package/dist/crdt/providers/room.js +15 -0
  47. package/dist/crdt/providers/websocket.d.ts +4 -2
  48. package/dist/crdt/providers/websocket.js +36 -9
  49. package/dist/crdt/reseed.d.ts +127 -0
  50. package/dist/crdt/reseed.js +533 -0
  51. package/dist/crdt/restore.d.ts +13 -1
  52. package/dist/crdt/restore.js +8 -3
  53. package/dist/crdt/schema.d.ts +6 -0
  54. package/dist/crdt/schema.js +6 -0
  55. package/dist/crdt/structs.d.ts +8 -11
  56. package/dist/crdt/structs.js +25 -44
  57. package/dist/crdt/text/index/build.d.ts +3 -9
  58. package/dist/crdt/text/index/build.js +87 -181
  59. package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
  60. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  61. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
  62. package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
  63. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  64. package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
  65. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
  66. package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/selection/selection.svelte.d.ts +1 -1
  70. package/dist/selection/selection.svelte.js +87 -106
  71. package/dist/session/commands.js +49 -51
  72. package/dist/surface/attributes.d.ts +3 -3
  73. package/dist/surface/attributes.js +2 -2
  74. package/dist/surface/observer.svelte.d.ts +7 -7
  75. package/dist/surface/observer.svelte.js +213 -309
  76. package/package.json +1 -1
@@ -9,7 +9,7 @@ export declare const isId: (value: unknown) => value is Id;
9
9
  * The clients of a frame (but `skip`) that write a per-writer block mark
10
10
  * of another writer: a struct whose key is `del.<n>` or `wd.<n>` on a
11
11
  * block node, from a client other than `n`. Only `n` writes its mark.
12
- * A struct in the purge horizon's root forges the room's.
12
+ * A struct in the purge horizon's root, or the epoch's, forges the room's.
13
13
  */
14
14
  export declare const forgedWriters: (doc: YDoc, structs: Struct[], skip: Set<number>) => Set<number>;
15
15
  /**
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Forged writes: what a frame writes or deletes that its sender may not —
3
3
  * another writer's per-writer block mark (`room.marks.writer`), the purge
4
- * horizon (the room's alone), another replica's binding or another user's
4
+ * horizon and the epoch (the room's alone), another replica's binding or another user's
5
5
  * profile in the attribution root (`room.attribution.trust`). Pure reads of
6
6
  * the room document and the frame; `Admission` strips or drops what they
7
7
  * find.
8
8
  */
9
9
  import { Y } from '../../crdt/engine.js';
10
- import { ATTRIBUTION_ROOT, DEL_PREFIX, HORIZON_ROOT, REGISTRY_KEY, SUGGEST_ROOT, WITHDRAW_PREFIX } from '../../crdt/schema.js';
10
+ import { ATTRIBUTION_ROOT, DEL_PREFIX, EPOCH_ROOT, HORIZON_ROOT, REGISTRY_KEY, SUGGEST_ROOT, WITHDRAW_PREFIX } from '../../crdt/schema.js';
11
11
  import { heldClock, runsOf, storedStruct, structAt } from './updates.js';
12
12
  // ── Per-writer marks ──────────────────────────────────────────
13
13
  /** The writer `n` of a per-writer block mark key (`del.<n>`, `wd.<n>`), or `null`. */
@@ -63,14 +63,16 @@ const isBlockNode = (doc, frame, parent) => {
63
63
  const item = parent?._item;
64
64
  return item?.parent === registry && registry !== undefined;
65
65
  };
66
- /** Is `parent` (a {@link Place}'s) the purge horizon's root, which only the room writes? */
67
- const isHorizon = (doc, parent) => parent === HORIZON_ROOT ||
68
- (parent != null && parent === doc.share.get(HORIZON_ROOT));
66
+ /**
67
+ * Is `parent` (a {@link Place}'s) a root only the room writes: the purge
68
+ * horizon's, or the epoch's (`room.reseed.protocol`)?
69
+ */
70
+ const isRoomRoot = (doc, parent) => [HORIZON_ROOT, EPOCH_ROOT].some((root) => parent === root || (parent != null && parent === doc.share.get(root)));
69
71
  /**
70
72
  * The clients of a frame (but `skip`) that write a per-writer block mark
71
73
  * of another writer: a struct whose key is `del.<n>` or `wd.<n>` on a
72
74
  * block node, from a client other than `n`. Only `n` writes its mark.
73
- * A struct in the purge horizon's root forges the room's.
75
+ * A struct in the purge horizon's root, or the epoch's, forges the room's.
74
76
  */
75
77
  export const forgedWriters = (doc, structs, skip) => {
76
78
  const frame = runsOf(structs);
@@ -83,7 +85,7 @@ export const forgedWriters = (doc, structs, skip) => {
83
85
  continue;
84
86
  const place = placeOf(doc, frame, struct.id);
85
87
  const writer = markWriter(place?.key);
86
- if ((place !== null && isHorizon(doc, place.parent)) ||
88
+ if ((place !== null && isRoomRoot(doc, place.parent)) ||
87
89
  (writer !== null && writer !== client && isBlockNode(doc, frame, place.parent))) {
88
90
  forged.add(client);
89
91
  }
@@ -113,8 +115,8 @@ export const forgedDeletes = (doc, ds, mayDelete) => {
113
115
  break;
114
116
  if (!(struct instanceof Y.Item) || struct.deleted)
115
117
  continue;
116
- // The purge horizon is the room's alone.
117
- if (!isHorizon(doc, struct.parent)) {
118
+ // The purge horizon and the epoch are the room's alone.
119
+ if (!isRoomRoot(doc, struct.parent)) {
118
120
  const writer = markWriter(struct.parentSub);
119
121
  if (writer === null || mayDelete(writer))
120
122
  continue;
@@ -29,6 +29,8 @@ export declare const readOnlyFrame: () => Uint8Array;
29
29
  export declare const readOnlyDenialFrame: () => Uint8Array;
30
30
  /** The denial of what a suggest-only socket may not write (stripped): it stays, and still syncs. */
31
31
  export declare const suggestOnlyDenialFrame: () => Uint8Array;
32
+ /** The room's epoch, to a socket that dialed an older one (`room.reseed.protocol`). */
33
+ export declare const epochFrame: (epoch: number) => Uint8Array;
32
34
  /** The socket's access, at the join and at a change in place (`write`, `suggest`, `comment`, `read`). */
33
35
  export declare const accessFrame: (access: string) => Uint8Array;
34
36
  /** A Step2 of what the room STORED: the engine's pending store (never stored) is left out. */
@@ -51,6 +51,8 @@ export const readOnlyFrame = () => E.frame(E.messageAuth, (e) => E.writeReadOnly
51
51
  export const readOnlyDenialFrame = () => E.frame(E.messageAuth, (e) => E.writePermissionDenied(e, READ_ONLY_DENIAL));
52
52
  /** The denial of what a suggest-only socket may not write (stripped): it stays, and still syncs. */
53
53
  export const suggestOnlyDenialFrame = () => E.frame(E.messageAuth, (e) => E.writePermissionDenied(e, SUGGEST_ONLY_DENIAL));
54
+ /** The room's epoch, to a socket that dialed an older one (`room.reseed.protocol`). */
55
+ export const epochFrame = (epoch) => E.frame(E.messageAuth, (e) => E.writeEpoch(e, epoch));
54
56
  /** The socket's access, at the join and at a change in place (`write`, `suggest`, `comment`, `read`). */
55
57
  export const accessFrame = (access) => E.frame(E.messageAuth, (e) => E.writeAccess(e, access));
56
58
  /** A Step2 of what the room STORED: the engine's pending store (never stored) is left out. */
@@ -7,7 +7,6 @@
7
7
  * the socket goes.
8
8
  */
9
9
  import { semanticsMismatch } from '../../crdt/semantics.js';
10
- import { SOCKET_TAG } from './shared.js';
11
10
  import { allowance } from './admission.js';
12
11
  import { encodeJSON } from './context.js';
13
12
  import { encodePresenceEntry, presenceFrame, presenceFrameOf } from './frames.js';
@@ -142,9 +141,7 @@ export class RoomPresence {
142
141
  * replica, and {@link drain} sends each recipient's in one frame.
143
142
  */
144
143
  fanOut(entries) {
145
- const sockets = this.room.ctx
146
- .getWebSockets(SOCKET_TAG)
147
- .filter((socket) => socket.readyState === WebSocket.OPEN);
144
+ const sockets = this.room.sockets();
148
145
  if (sockets.length === 0)
149
146
  return;
150
147
  if (this.waiting.size === 0 && this.take(sockets.length)) {
@@ -0,0 +1,93 @@
1
+ import * as E from '../../crdt/protocol.js';
2
+ import type { YDoc } from '../../crdt/index.js';
3
+ import type { Attachment, SocketIdentity } from '../DocumentRoom.js';
4
+ import type { Decoded } from './context.js';
5
+ import { type RoomContext } from './context.js';
6
+ /** An epoch a dial names (`?epoch=`; `new`: a document that holds nothing yet), or `null`. */
7
+ export declare const parseEpoch: (raw: string | null) => number | "new" | null;
8
+ export declare class RoomReseed {
9
+ #private;
10
+ private readonly room;
11
+ constructor(room: RoomContext);
12
+ /** The re-seed's settings (`null`: off). */
13
+ get config(): {
14
+ at: number;
15
+ idleMs: number;
16
+ gain: number;
17
+ force: number;
18
+ graceDays: number;
19
+ } | null;
20
+ /** The room's epoch: what its document says (`epoch.n`, which only the room writes), 0 before a re-seed. */
21
+ get epoch(): number;
22
+ /**
23
+ * A change was stored at `at` (`room.reseed.timing`): past the
24
+ * threshold the re-seed is due once nobody writes for `idleMs` (each
25
+ * change pushes it back), past `force` at once.
26
+ */
27
+ noteChange(at: number): void;
28
+ /**
29
+ * The `reseed` task: the cuts past their grace go (`room.reseed.grace`),
30
+ * then it re-seeds when still due (past the threshold and the floor); a
31
+ * re-seed that failed is due again at the first change a minute later or
32
+ * more. It is due again when the oldest cut left expires.
33
+ */
34
+ task(): void;
35
+ /**
36
+ * Re-seed now (`room.reseed.cut`): the new epoch replaces the rows, in
37
+ * one storage transaction with its cut; the replica registry, the
38
+ * purge's epochs and the last restore's step go (they name the old
39
+ * identities); the live document is rebuilt from the rows and every
40
+ * socket closed `4410`. `idle` (the task, once nobody wrote) waits while
41
+ * a suggestion is pending (an idle period later); `full` (the task, past
42
+ * `force` of the quota) drops them; both do nothing when the new epoch
43
+ * would give back less than `gain` (and wait for the document to grow by
44
+ * a quarter of `at` before trying again). `manual` (`reseed()`) runs
45
+ * whatever it gives back and drops them. Answers the new epoch, or `null`.
46
+ */
47
+ run(mode: 'idle' | 'full' | 'manual'): number | null;
48
+ /**
49
+ * The door (`RoomAccess.accept`, `room.reseed.protocol`): a dial of
50
+ * epoch `asked` is current, stale (an older epoch whose cuts are kept),
51
+ * or closed: none, in a room that re-seeded, `1008` (a client that
52
+ * predates the re-seed); any other epoch the room cannot rebase (past
53
+ * the cuts kept, or later than its own: the room lost it), `4410`, so
54
+ * the client drops its copy and loads the room's.
55
+ */
56
+ door(asked: number | 'new' | null): 'current' | 'stale' | {
57
+ code: number;
58
+ reason: string;
59
+ };
60
+ /**
61
+ * Whether `decoded`, from a socket of the current epoch, holds an older
62
+ * epoch's content (`room.reseed.protocol`): a struct of a client the
63
+ * cuts kept name, which the document holds nothing of (a tab took it
64
+ * from another tab of the old epoch). Its socket is closed `4410`.
65
+ */
66
+ holdsOld(decoded: Decoded, doc: YDoc): boolean;
67
+ /** The largest frame a socket of an older epoch may send: its whole old document. */
68
+ get staleFrameBytes(): number;
69
+ /**
70
+ * A stale socket joined: one that may write is told the room's epoch
71
+ * (its provider then hands over its old document); any other has no
72
+ * edit to merge and is closed `4410`. Past its user's rebase rate it is
73
+ * closed `1011` before it uploads anything: its provider dials again
74
+ * later.
75
+ */
76
+ join(ws: WebSocket, identity: SocketIdentity): void;
77
+ /**
78
+ * A stale socket's message: its old document (`messageRebase`) is
79
+ * rebased onto a copy of the current one, and what that wrote is
80
+ * admitted as an update of its user, from a client id of its own (the
81
+ * admission's quotas, access, validation and attribution, as any
82
+ * update); then it is closed `4410`. The merged state is kept per user
83
+ * and cut (`merged:<user>`), so another tab's upload merges nothing
84
+ * twice and another user's cannot claim it. A hand-over that cannot be
85
+ * read or rebased is dropped, its socket closed `4410` all the same (its
86
+ * client loads the room's document); past the rebase rate (one per user
87
+ * every ten seconds), `1011`, and the provider hands over again later.
88
+ * Anything else a stale socket sends is dropped.
89
+ */
90
+ onStale(ws: WebSocket, attachment: Attachment, type: number, decoder: E.Decoder): void;
91
+ /** Drop the cuts older than the grace, with what their rebases merged (`room.reseed.grace`). */
92
+ prune(at: number): void;
93
+ }