edytor 5.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +3 -3
  2. package/dist/cloudflare/DocumentRoom.d.ts +77 -2
  3. package/dist/cloudflare/DocumentRoom.js +37 -1
  4. package/dist/cloudflare/index.d.ts +1 -1
  5. package/dist/cloudflare/index.js +1 -1
  6. package/dist/cloudflare/room/access.d.ts +6 -1
  7. package/dist/cloudflare/room/access.js +30 -4
  8. package/dist/cloudflare/room/admission.d.ts +11 -0
  9. package/dist/cloudflare/room/admission.js +43 -10
  10. package/dist/cloudflare/room/context.d.ts +73 -76
  11. package/dist/cloudflare/room/context.js +17 -6
  12. package/dist/cloudflare/room/forged.d.ts +1 -1
  13. package/dist/cloudflare/room/forged.js +11 -9
  14. package/dist/cloudflare/room/frames.d.ts +2 -0
  15. package/dist/cloudflare/room/frames.js +2 -0
  16. package/dist/cloudflare/room/presence.js +1 -4
  17. package/dist/cloudflare/room/reseed.d.ts +93 -0
  18. package/dist/cloudflare/room/reseed.js +500 -0
  19. package/dist/cloudflare/room/scheduler.d.ts +1 -1
  20. package/dist/cloudflare/room/scheduler.js +9 -1
  21. package/dist/cloudflare/room/shared.d.ts +10 -0
  22. package/dist/cloudflare/room/shared.js +10 -0
  23. package/dist/cloudflare/room/storage.d.ts +7 -0
  24. package/dist/cloudflare/room/storage.js +33 -1
  25. package/dist/components/Edytor.svelte +18 -3
  26. package/dist/components/Edytor.svelte.d.ts +10 -1
  27. package/dist/components/EdytorRoot.svelte +17 -0
  28. package/dist/components/EdytorRoot.svelte.d.ts +4 -0
  29. package/dist/components/Suggestion.svelte +1 -1
  30. package/dist/crdt/attribution/block.d.ts +3 -0
  31. package/dist/crdt/attribution/block.js +3 -2
  32. package/dist/crdt/doc/history.js +2 -3
  33. package/dist/crdt/doc/ops.d.ts +81 -0
  34. package/dist/crdt/doc/ops.js +1898 -0
  35. package/dist/crdt/doc/suggest.d.ts +6 -2
  36. package/dist/crdt/doc/suggest.js +6 -5
  37. package/dist/crdt/edytor-doc.d.ts +157 -157
  38. package/dist/crdt/edytor-doc.js +15 -43
  39. package/dist/crdt/engine-room.d.ts +4 -0
  40. package/dist/crdt/engine-room.js +6 -0
  41. package/dist/crdt/flow.d.ts +4 -46
  42. package/dist/crdt/flow.js +2 -262
  43. package/dist/crdt/index.d.ts +64 -75
  44. package/dist/crdt/operations.d.ts +1 -1
  45. package/dist/crdt/placement/resolve.d.ts +1 -1
  46. package/dist/crdt/protocol.d.ts +2 -2
  47. package/dist/crdt/protocol.js +2 -2
  48. package/dist/crdt/protocols/auth.d.ts +8 -1
  49. package/dist/crdt/protocols/auth.js +14 -1
  50. package/dist/crdt/protocols/awareness.js +46 -81
  51. package/dist/crdt/providers/index.d.ts +11 -22
  52. package/dist/crdt/providers/index.js +22 -3
  53. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  54. package/dist/crdt/providers/indexeddb.js +10 -16
  55. package/dist/crdt/providers/room.d.ts +26 -0
  56. package/dist/crdt/providers/room.js +41 -12
  57. package/dist/crdt/providers/websocket.d.ts +10 -14
  58. package/dist/crdt/providers/websocket.js +43 -23
  59. package/dist/crdt/rangeDelete.d.ts +2 -47
  60. package/dist/crdt/rangeDelete.js +1 -169
  61. package/dist/crdt/reseed.d.ts +127 -0
  62. package/dist/crdt/reseed.js +533 -0
  63. package/dist/crdt/restore.d.ts +13 -1
  64. package/dist/crdt/restore.js +8 -3
  65. package/dist/crdt/schema.d.ts +6 -0
  66. package/dist/crdt/schema.js +6 -0
  67. package/dist/crdt/structs.d.ts +8 -11
  68. package/dist/crdt/structs.js +25 -44
  69. package/dist/crdt/text/index/build.d.ts +119 -0
  70. package/dist/crdt/text/index/build.js +2801 -0
  71. package/dist/crdt/text/index/shared.d.ts +2 -6
  72. package/dist/crdt/text/index/shared.js +2 -5
  73. package/dist/crdt/text/model.js +2 -3
  74. package/dist/crdt/text/runs.d.ts +9 -12
  75. package/dist/crdt/text/runs.js +2 -165
  76. package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
  77. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  78. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
  79. package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
  80. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  81. package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
  82. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
  83. package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
  84. package/dist/events/domTextOffset.d.ts +2 -0
  85. package/dist/events/domTextOffset.js +8 -0
  86. package/dist/export/forms.d.ts +50 -50
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  90. package/dist/plugins/richtext/richTextOperations.js +26 -32
  91. package/dist/selection/selection.svelte.d.ts +1 -1
  92. package/dist/selection/selection.svelte.js +87 -106
  93. package/dist/selection/selection.utils.js +5 -8
  94. package/dist/session/commands.js +49 -51
  95. package/dist/session/handles.js +3 -7
  96. package/dist/surface/attributes.d.ts +3 -3
  97. package/dist/surface/attributes.js +2 -2
  98. package/dist/surface/observer.svelte.d.ts +7 -7
  99. package/dist/surface/observer.svelte.js +225 -336
  100. package/dist/surface/projector.svelte.js +2 -2
  101. package/dist/utils.d.ts +5 -0
  102. package/dist/utils.js +7 -0
  103. package/package.json +1 -1
  104. package/dist/crdt/doc/capability.d.ts +0 -56
  105. package/dist/crdt/doc/capability.js +0 -207
  106. package/dist/crdt/doc/content.d.ts +0 -16
  107. package/dist/crdt/doc/content.js +0 -77
  108. package/dist/crdt/doc/delete.d.ts +0 -15
  109. package/dist/crdt/doc/delete.js +0 -84
  110. package/dist/crdt/doc/layout.d.ts +0 -9
  111. package/dist/crdt/doc/layout.js +0 -178
  112. package/dist/crdt/doc/meta.d.ts +0 -19
  113. package/dist/crdt/doc/meta.js +0 -198
  114. package/dist/crdt/doc/moves.d.ts +0 -21
  115. package/dist/crdt/doc/moves.js +0 -274
  116. package/dist/crdt/doc/split-merge.d.ts +0 -10
  117. package/dist/crdt/doc/split-merge.js +0 -179
  118. package/dist/crdt/doc/steps.d.ts +0 -65
  119. package/dist/crdt/doc/steps.js +0 -311
  120. package/dist/crdt/text/index/cache.d.ts +0 -16
  121. package/dist/crdt/text/index/cache.js +0 -175
  122. package/dist/crdt/text/index/claims.d.ts +0 -18
  123. package/dist/crdt/text/index/claims.js +0 -203
  124. package/dist/crdt/text/index/fold.d.ts +0 -21
  125. package/dist/crdt/text/index/fold.js +0 -656
  126. package/dist/crdt/text/index/layout.d.ts +0 -22
  127. package/dist/crdt/text/index/layout.js +0 -258
  128. package/dist/crdt/text/index/placement.d.ts +0 -47
  129. package/dist/crdt/text/index/placement.js +0 -738
  130. package/dist/crdt/text/index/records.d.ts +0 -13
  131. package/dist/crdt/text/index/records.js +0 -139
  132. package/dist/crdt/text/index/report.d.ts +0 -18
  133. package/dist/crdt/text/index/report.js +0 -337
  134. package/dist/crdt/text/index/state.d.ts +0 -197
  135. package/dist/crdt/text/index/state.js +0 -116
  136. package/dist/crdt/text/index/streams.d.ts +0 -25
  137. package/dist/crdt/text/index/streams.js +0 -248
@@ -1,7 +1,7 @@
1
1
  import type { EngineNode } from '../../engine-api.js';
2
2
  import type { BlockId, BlockRec } from '../../placement/model.js';
3
3
  import type { ContentRun, DisplayRoles } from '../runs.js';
4
- import type { SelfChecks } from './state.js';
4
+ import type { IndexFacts, SelfChecks } from './build.js';
5
5
  /**
6
6
  * The facet name of a block's registry entry itself (inserted or removed) —
7
7
  * every other facet is named by the block attr it entered through.
@@ -65,12 +65,8 @@ export declare const sameShape: (roles: DisplayRoles, a: string, b: string) => b
65
65
  */
66
66
  export declare const indexChecks: {
67
67
  on: boolean;
68
- of?: (ix: never) => SelfChecks;
68
+ of?: (facts: IndexFacts) => SelfChecks;
69
69
  };
70
- /** `ix`'s checker ({@link indexChecks}), built on its first check. */
71
- export declare const checksOf: (ix: {
72
- checks: SelfChecks | null;
73
- }) => SelfChecks;
74
70
  /** Add `value` to `key`'s set in `index`. */
75
71
  export declare const addTo: <K, V>(index: Map<K, Set<V>>, key: K, value: V) => void;
76
72
  /** Remove `value` from `key`'s set in `index` (the set goes once empty). */
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { cloneJsonSafe } from '../../../utils/json.js';
7
7
  import { CONTENT, DATA, DATA_LEAF_PREFIX, ID, LAST_CHANGED_ATTR, TYPE } from '../../schema.js';
8
+ import { getOr } from '../../../utils.js';
8
9
  /**
9
10
  * The facet name of a block's registry entry itself (inserted or removed) —
10
11
  * every other facet is named by the block attr it entered through.
@@ -93,13 +94,9 @@ export const sameShape = (roles, a, b) => {
93
94
  * difference; after every report, the published tree.
94
95
  */
95
96
  export const indexChecks = { on: false };
96
- /** `ix`'s checker ({@link indexChecks}), built on its first check. */
97
- export const checksOf = (ix) => (ix.checks ??= indexChecks.of(ix));
98
97
  /** Add `value` to `key`'s set in `index`. */
99
98
  export const addTo = (index, key, value) => {
100
- let set = index.get(key);
101
- if (set === undefined)
102
- index.set(key, (set = new Set()));
99
+ const set = getOr(index, key, () => new Set());
103
100
  set.add(value);
104
101
  };
105
102
  /** Remove `value` from `key`'s set in `index` (the set goes once empty). */
@@ -7,6 +7,7 @@ import { fitMarks, writeMarks } from './marks.js';
7
7
  import { nodeStart } from './items.js';
8
8
  import { readRange } from './ranges.js';
9
9
  import { claimsIn, delimiters, displayOf, locate, ownedLength, pieces, placeText, rangesOf, removedKeys, scanText } from './streams.js';
10
+ import { getOr } from '../../utils.js';
10
11
  export { isBoundary, isClaimItem, isRowMark, nodeStart } from './items.js';
11
12
  export { canonKey, deepFreeze, inlineItemOf, protectItems, readRange } from './ranges.js';
12
13
  export { byId, claimsIn, delimiters, displayOf, locate, ownedLength, pieces, placeText, removedKeys, scanText } from './streams.js';
@@ -107,9 +108,7 @@ export const readSegs = (segs, stats) => {
107
108
  const out = [];
108
109
  const cursors = new Map();
109
110
  for (const seg of segs) {
110
- let cur = cursors.get(seg.text);
111
- if (cur === undefined)
112
- cursors.set(seg.text, (cur = openRangeCursor(seg.text)));
111
+ const cur = getOr(cursors, seg.text, () => openRangeCursor(seg.text));
113
112
  readRange(cur, seg.i0, seg.i1, stats, out);
114
113
  }
115
114
  return out;
@@ -53,17 +53,16 @@
53
53
  * advancing the published tree in place (after a whole rebuild, the
54
54
  * reachable tree is compared).
55
55
  *
56
- * The index is built from parts under `index/`, each over the shared state
57
- * (`index/state.ts`) and the parts before it: the claim graph
58
- * (`claims.ts`), the stream table and each block's claims (`streams.ts`),
59
- * the layout rules (`layout.ts`), placements and the
60
- * children index (`placement.ts`), block records (`records.ts`), the run
61
- * cache (`cache.ts`), the fold (`fold.ts`), the change report
62
- * (`report.ts`); the self-checks a test lane installs are `indexChecks`. This module declares the
63
- * index's types and wires the parts into the doc's one index.
56
+ * The index is one closure (`index/build.ts`) over its state: the claim
57
+ * graph, the stream table and each block's claims, the layout rules,
58
+ * placements and the children index, block records, the run cache, the fold
59
+ * and the change report; the self-checks a test lane installs are
60
+ * `indexChecks`. This module declares the index's types.
64
61
  */
65
- import type { EngineApi, EngineDoc } from '../engine-api.js';
62
+ import type { EngineApi } from '../engine-api.js';
66
63
  import type { BlockId, ContentItem, ModelView, ProjectedBlock } from '../placement/model.js';
64
+ import { indexOf } from './index/build.js';
65
+ export { indexOf };
67
66
  export { CONTENT_ATTR, ENTRY_FACET, SEQ_FACET, indexChecks } from './index/shared.js';
68
67
  /**
69
68
  * One visible run of a block — the maintained form of `ContentItem`.
@@ -257,9 +256,7 @@ export type DisplayRoles = {
257
256
  of: (type: string | null) => KindRole;
258
257
  declared: () => DeclaredKinds;
259
258
  };
260
- /** The doc's index — built on first use, torn down when the doc is destroyed. */
261
- export declare const indexOf: (doc: EngineDoc) => RunView;
262
259
  /** The index binding (the public binding's `runs`): {@link indexOf}. */
263
260
  export declare const bindRuns: (_Y: EngineApi) => {
264
- attach: (doc: EngineDoc) => RunView;
261
+ attach: (doc: import("../engine-api.js").EngineDoc) => RunView;
265
262
  };
@@ -1,168 +1,5 @@
1
- import { isNodeLike } from '../schema.js';
2
- import { cloneJsonSafe } from '../../utils/json.js';
3
- import { indexState } from './index/state.js';
4
- import { indexClaims } from './index/claims.js';
5
- import { indexStreams } from './index/streams.js';
6
- import { indexLayout } from './index/layout.js';
7
- import { indexPlacement } from './index/placement.js';
8
- import { indexRecords } from './index/records.js';
9
- import { indexCache } from './index/cache.js';
10
- import { indexFold } from './index/fold.js';
11
- import { indexReporter } from './index/report.js';
1
+ import { indexOf } from './index/build.js';
2
+ export { indexOf };
12
3
  export { CONTENT_ATTR, ENTRY_FACET, SEQ_FACET, indexChecks } from './index/shared.js';
13
- /** One index per engine doc, shared by every binding (the doc's lifetime). */
14
- const indexes = new WeakMap();
15
- /** The doc's index — built on first use, torn down when the doc is destroyed. */
16
- export const indexOf = (doc) => {
17
- let index = indexes.get(doc);
18
- if (index === undefined)
19
- indexes.set(doc, (index = buildView(doc)));
20
- return index;
21
- };
22
4
  /** The index binding (the public binding's `runs`): {@link indexOf}. */
23
5
  export const bindRuns = (_Y) => ({ attach: indexOf });
24
- const buildView = (doc) => {
25
- // The index's parts, each over the state and the parts before it (`index/*`).
26
- const ix0 = indexState(doc);
27
- const ix1 = Object.assign(ix0, indexClaims(ix0));
28
- const ix2 = Object.assign(ix1, indexStreams(ix1));
29
- const ix4 = Object.assign(ix2, indexLayout(ix2));
30
- const ix5 = Object.assign(ix4, indexPlacement(ix4));
31
- const ix6 = Object.assign(ix5, indexRecords(ix5));
32
- const ix7 = Object.assign(ix6, indexCache(ix6));
33
- const ix8 = Object.assign(ix7, indexFold(ix7));
34
- const ix = Object.assign(ix8, indexReporter(ix8));
35
- const { registry, blocks, rows, homeOfText, shells, placementsMap, frames, displays, ownShim, ensurePlacements, typeOf, order, rebuildTable, scanRow, applyReclaims, updateBlockRec, syncIncarnations, intern, debug, runs, settle, syncPending, syncAll, openTx, onCommit, committed, itemsOf, projectBlock, publish, onUpdate, onReport, hasSubscribers } = ix;
36
- const ctx = {
37
- blocks,
38
- own: ownShim,
39
- get placements() {
40
- ensurePlacements();
41
- return placementsMap;
42
- },
43
- get kids() {
44
- ensurePlacements();
45
- return ix.kidsMap;
46
- },
47
- get order() {
48
- ensurePlacements();
49
- return order();
50
- },
51
- // The publication boundary shares THIS interner, so a payload
52
- // emitted by `project()`/`contentItems()` is `===` the runs' one.
53
- intern,
54
- displays,
55
- accepted: (id) => {
56
- ensurePlacements();
57
- return ix.rejected.has(id) ? null : (ix.edges.get(id) ?? null);
58
- }
59
- };
60
- // ── initial scan: index every existing block ────────────────────
61
- registry.forEachAttr((v, id) => {
62
- if (!isNodeLike(v))
63
- return;
64
- updateBlockRec(id);
65
- for (const b of [id, ...syncIncarnations(id)]) {
66
- const row = scanRow(b);
67
- if (row !== undefined) {
68
- rows.set(b, row);
69
- homeOfText.set(row.text, b);
70
- }
71
- }
72
- });
73
- rebuildTable(new Set());
74
- applyReclaims(null);
75
- settle(shells, null);
76
- // Every commit folds once, before the document's `afterTransaction` listeners.
77
- doc.on('commit', onCommit);
78
- /** Derived state lives exactly as long as the doc. */
79
- const teardown = () => {
80
- doc.off('commit', onCommit);
81
- doc.off('destroy', teardown);
82
- doc.off('update', onUpdate);
83
- indexes.delete(doc);
84
- };
85
- doc.on('destroy', teardown);
86
- // ── public surface ───────────────────────────────────────────────
87
- const view = {
88
- version: () => {
89
- syncAll();
90
- return ix.version;
91
- },
92
- runs,
93
- contentItems: (b) => {
94
- syncAll();
95
- return blocks.get(b)?.deleted === false ? itemsOf(b) : [];
96
- },
97
- // Inline atoms always carry `data` (`{}` when absent) — the public
98
- // `JSONInlineBlock` shape of `edytor.value`.
99
- contentJSON: (b) => runs(b).map((r) => r.kind === 'text'
100
- ? { text: r.text, ...(r.marks !== undefined && { marks: cloneJsonSafe(r.marks) }) }
101
- : { id: r.id, type: r.type, data: r.data === undefined ? {} : cloneJsonSafe(r.data) }),
102
- view: (tr) => {
103
- syncAll();
104
- if (tr !== undefined && !committed.has(tr))
105
- syncPending(tr);
106
- return ctx;
107
- },
108
- displayType: (id) => {
109
- if (ix.roles === null)
110
- return undefined;
111
- syncAll();
112
- ensurePlacements();
113
- const shown = blocks.has(id) ? typeOf(id) : undefined;
114
- return shown === blocks.get(id)?.type ? undefined : shown;
115
- },
116
- dissolved: (id) => {
117
- syncAll();
118
- ensurePlacements();
119
- return ix.dissolved.has(id);
120
- },
121
- project: (root) => {
122
- syncAll();
123
- ensurePlacements();
124
- return root === undefined
125
- ? (ix.kidsMap.get(null) ?? []).map((k) => projectBlock(k.id))
126
- : [projectBlock(root)];
127
- },
128
- roles: (next) => {
129
- ix.roles = next;
130
- ix.placementFull = true;
131
- ix.version++;
132
- // No commit carries a role change: report it now (not inside a
133
- // transaction, whose commit reports it), so every view follows.
134
- if (hasSubscribers() && !openTx())
135
- publish(null, false);
136
- },
137
- track: () => {
138
- syncAll();
139
- const f = { touched: new Map(), wrote: false };
140
- frames.add(f);
141
- return {
142
- end: () => {
143
- syncAll();
144
- frames.delete(f);
145
- return f;
146
- },
147
- quiet: (write) => {
148
- syncAll();
149
- const g = { touched: new Map(), wrote: false };
150
- frames.delete(f);
151
- frames.add(g);
152
- try {
153
- write();
154
- syncAll();
155
- }
156
- finally {
157
- frames.delete(g);
158
- frames.add(f);
159
- f.wrote ||= g.wrote;
160
- }
161
- }
162
- };
163
- },
164
- onReport,
165
- debug
166
- };
167
- return view;
168
- };
@@ -33,7 +33,7 @@ is no longer a dependency of any kind: `tests/packed-consumer/smoke.js` and
33
33
  ## Layout
34
34
 
35
35
  ```text
36
- src/ upstream src/, plus patches YP1 (import specifiers), YP4, YP5, YP7, YP8 (pruning), YP9–YP15, YP16 (no delta layer), YP17 (one update encoding), YP18 (no undo manager), YP19 (only what edytor calls), YP20 (hold records), YP21 (claim items), YP22 (move records), YP23 (a block set's trim), YP24 (a string table per update), YP25 (paired marks written in parts), YP26 (a client table per update), YP27 (the parent kind in the info byte), YP28 (large integers in content values), YP29 (a collected mark or claim keeps its class) and YP30 (a transaction's `beforeCommit`)
36
+ src/ upstream src/, plus patches YP1 (import specifiers), YP4, YP5, YP7, YP8 (pruning), YP9–YP15, YP16 (no delta layer), YP17 (one update encoding), YP18 (no undo manager), YP19 (only what edytor calls), YP20 (hold records), YP21 (claim items), YP22 (move records), YP23 (a block set's trim), YP24 (a string table per update), YP25 (paired marks written in parts), YP26 (a client table per update), YP27 (the parent kind in the info byte), YP28 (large integers in content values), YP29 (a collected mark or claim keeps its class), YP30 (a transaction's `beforeCommit`), YP31 (a hold inside a nested node holds nothing), YP32 (a delete that proposes), YP33 (one transaction log) and YP34 (an update read at an earlier state)
37
37
  global.d.ts upstream global.d.ts, plus patches YP1, YP8 and YP19
38
38
  dts/ generated TypeScript declarations (not upstream source — see below)
39
39
  LICENSE upstream MIT license, verbatim
@@ -1022,7 +1022,8 @@ Patch (new code delimited by `// YP20 begin` / `// YP20 end`, edits marked `// Y
1022
1022
  text a room lost is stored and acknowledged at once. A GC struct takes
1023
1023
  nothing. An item that starts or stops being held (and is not removed)
1024
1024
  moves its parent's length, drops its search markers, joins
1025
- `transaction.changed` and is logged in `transaction.holds`
1025
+ `transaction.changed` and is logged in `transaction.holds` (since YP33:
1026
+ `transaction.log`, kind 1)
1026
1027
  (`client, clock, ±len`, in order: the index's edits).
1027
1028
  - **`YNode#delete(index, length, into, fresh)`**: with `into` (the records
1028
1029
  list) the live units are held by one record appended there instead of
@@ -1038,7 +1039,8 @@ Edytor side: a text delete passes the records list (`crdt/text/model.ts`
1038
1039
  copies the record (never the text), an undo of typing holds the typed text,
1039
1040
  a dropped step removes what this writer's records of it hold
1040
1041
  (`hist.delete-marks.release`); the purge removes what a record older than
1041
- the horizon holds; the index folds `transaction.holds`. Removed:
1042
+ the horizon holds; the index folds `transaction.holds` (since YP33, the one
1043
+ `transaction.log`). Removed:
1042
1044
  `bindCrdt(Y).doc.keepCopies`, `PurgeReport.records`, the restoration
1043
1045
  records root, the engine object's `ContentAny`.
1044
1046
 
@@ -1172,7 +1174,8 @@ Patch (new code delimited by `// YP22 begin` / `// YP22 end`, edits marked `// Y
1172
1174
  pending holds only. A
1173
1175
  unit that starts or stops being moved (and is not gone) moves
1174
1176
  its home sequence's length and drops its search markers; one whose mover
1175
- changed is logged in `transaction.moves` (`client, clock, len`, in
1177
+ changed is logged in `transaction.moves` (since YP33: `transaction.log`,
1178
+ kind 2; `client, clock, len`, in
1176
1179
  order), and its home and its new mover's sequences are marked changed.
1177
1180
  - **`doc._clock`** (was YP13's `_markClock`): the greatest Lamport
1178
1181
  timestamp of a mark or a move this document integrated; a local mark or
@@ -1537,7 +1540,7 @@ Patch (new code delimited by `// YP31 begin` / `// YP31 end`, edits marked `// Y
1537
1540
  records list, `textdel`). `ContentHold#integrate` and `ContentHold#delete`
1538
1541
  hold and release only then; inside a nested node's sequence the record is
1539
1542
  a list of ids: it moves no length, waits in no `pendingHolds`, logs
1540
- nothing in `transaction.holds`, and its delete (an undo, a reviewer's
1543
+ nothing in `transaction.holds` (YP33: `transaction.log`), and its delete (an undo, a reviewer's
1541
1544
  withdrawal, its node's delete) releases nothing. `YNode#hold(ids)` on a
1542
1545
  nested node appends one such list entry.
1543
1546
 
@@ -1598,6 +1601,99 @@ schedules with the index's self-checks on), `suggesting-input.test.tsx`
1598
1601
  (typing over a selection, a paste, Backspace and Delete over struck text),
1599
1602
  the YP20 rows, the upstream suite unchanged.
1600
1603
 
1604
+ ### YP33 — one log of what a transaction did to items (`src/utils/Transaction.js`, `src/structs/Item.js`)
1605
+
1606
+ Reason: edytor's index folds each transaction into its derived facts (rows,
1607
+ streams, slots, placements) once at commit and, for read-your-writes, over
1608
+ the part of an open transaction a read has not folded. The engine logged
1609
+ the holds (YP20, `transaction.holds`) and the mover changes (YP22,
1610
+ `transaction.moves`) in order, but the inserts and removals only as id sets,
1611
+ so the fold reconstructed them: it kept, per open transaction, copies of the
1612
+ insert and delete sets and diffed them at every read, walked both sets'
1613
+ structs again for the texts and again for the sequences, and decided by set
1614
+ membership which edits cancelled within a fold (about 1.6 KB minified in
1615
+ every app, `makeEditIndex` and `syncPending` in `crdt/text/index/build.ts`).
1616
+ The engine now logs the edits in the same log as the holds and the moves,
1617
+ and the fold reads one log from a cursor. In memory only: nothing encoded,
1618
+ no schema change.
1619
+
1620
+ Patch (new code delimited by `// YP33 begin` / `// YP33 end`, edits marked `// YP33`):
1621
+
1622
+ - **`transaction.log`** (replaces YP20's `holds` and YP22's `moves`): per
1623
+ run of one writer's items `kind, client, clock, len`, in the order the
1624
+ transaction did them; an item that continues the last entry (its kind,
1625
+ its client, the next clock, its sign) extends it, so a bulk insert or
1626
+ delete is a few entries, as the id sets' ranges were. Kind 0, an edit:
1627
+ `Item#integrate` logs `+length` before its content integrates (a node's
1628
+ children follow it; nothing for an item integrated removed, a
1629
+ `ContentDeleted`), `Item#delete` `-length`; kind 1,
1630
+ a hold record's change of visibility (YP20's entries, unchanged: a held
1631
+ item removed logs its give-back, so an item's kinds 0 and 1 sum to its
1632
+ visibility change); kind 2, a mover change (YP22's entries, unchanged).
1633
+ - **`logItem(tr, kind, item, len, mark)`** (`Item.js`): the one writer (it
1634
+ extends a run's entry), which also marks the parent changed (it replaces
1635
+ `logHold` and `Item#delete`'s `addChangedTypeToTransaction`; `logMove`
1636
+ calls it). `Item#integrate` logs with `mark` false and keeps its own
1637
+ mark, after its content, so `transaction.changed` keeps its order.
1638
+
1639
+ The insert and delete sets, `transaction.changed` and every upstream path
1640
+ are untouched: the log is a list the engine appends to and never reads.
1641
+
1642
+ Edytor side: the fold keeps one number per open transaction (the length of
1643
+ its log a read folded and that entry's last length then, so the units the
1644
+ engine added to it since are folded too), reads each entry's structs once
1645
+ (`walkRange`, the walk `walkIdSet` now runs per range) and replays the units each item showed
1646
+ or hid in order, so an edit made and undone within one fold moves the row
1647
+ by nothing; the sequences read kind 0 as their slots and records written and
1648
+ removed, kind 2 as their units whose mover changed. Removed: the copied and
1649
+ diffed id sets, the watermark, the second and third walks, the membership
1650
+ tests, the incomplete-walk fallback.
1651
+
1652
+ Measured (`scripts/bundle-size.mjs`, own minified): minimal 425,374 →
1653
+ 424,034 (−1,340 B; gzip −290), every plugin 675,230 → 673,890 (−1,340),
1654
+ `edytor/export` unchanged; the engine's share +106 B (one log writer, which
1655
+ extends a run's entry). A first version logged one entry per item: the
1656
+ fold walked them one by one where the id sets gave it ranges, and a 5,000
1657
+ block construction and a 40-block range delete got slower (`bench/scale.js`);
1658
+ the runs bring both back.
1659
+
1660
+ Oracle: `src/tests/crdt/yp33-log.test.ts` (edits, holds and a removed held
1661
+ item's give-back, a move, a remote update with an item integrated removed),
1662
+ the YP20, YP22 and YP31 rows reading their entries of the log, the index's
1663
+ self-checks after every fold (they rebuild from the replicated state and
1664
+ never read the log), the fold's counters (`foldedStructs`, the scale rows),
1665
+ the random corpus, the upstream suite unchanged.
1666
+
1667
+ ### YP34 — an update read at an earlier state (`src/utils/encoding.js`, `src/index.js`)
1668
+
1669
+ Reason: a re-seed's rebase (`room.reseed.rebase`, `crdt/reseed.ts`) gets an
1670
+ offline client's whole old document and must split it into the part the
1671
+ room held at the cut (the client's base) and the rest (its unsaved edits).
1672
+ `diffUpdate(update, sv)` gives the rest; nothing gave the base, and edytor
1673
+ has no access to the lazy struct reader and writer it would need (they are
1674
+ not exported). The engine now gives the counterpart. Encoding unchanged, no
1675
+ schema change.
1676
+
1677
+ Patch (new code delimited by `// YP34 begin` / `// YP34 end`, edits marked `// YP34`):
1678
+
1679
+ - **`clipUpdate(update, sv, ds)`**: the structs of `update` under the state
1680
+ vector `sv` (a struct that crosses it is cut there, through the content's
1681
+ `offsetEnd`; a skip is written only where a later struct of its client
1682
+ follows it), and the deletes of `update` that the id set `ds` also holds
1683
+ (`intersectSets`). Exported from `src/index.js`, not from edytor's engine
1684
+ object (`crdt/engine.js`): only the room calls it, so apps keep none of it.
1685
+
1686
+ Deleted content a document collected cannot come back (a collecting
1687
+ document writes it as deleted); edytor's text deletes are hold records
1688
+ (YP20), which keep it.
1689
+
1690
+ Measured: vendored execution lines +30; the app bundle unchanged (the
1691
+ engine object does not hold it).
1692
+
1693
+ Oracle: `src/tests/crdt/yp34-clip.test.ts` (an update read at an earlier
1694
+ state, its later deletes left out; a run cut at the state vector), the
1695
+ re-seed rows (`src/tests/crdt/reseed/`), the upstream suite unchanged.
1696
+
1601
1697
  ## Generated declarations (`dts/`)
1602
1698
 
1603
1699
  `svelte-package` copies JS verbatim but emits no `.d.ts` for JS inputs, so
@@ -11,7 +11,7 @@ export { GC } from "./structs/GC.js";
11
11
  export { Skip } from "./structs/Skip.js";
12
12
  export type ContentIds = import("./utils/ids.js").ContentIds;
13
13
  export { IdSet, createDeleteSetFromStructStore, diffIdSet, createIdSet, mergeIdSets, insertIntoIdSet, iterateStructsByIdSet, readIdSet, writeIdSet, intersectSets } from "./utils/ids.js";
14
- export { applyUpdate, encodeStateAsUpdate, encodeStateVector, decodeStateVector, diffUpdate, mergeUpdates } from "./utils/encoding.js";
14
+ export { applyUpdate, encodeStateAsUpdate, encodeStateVector, decodeStateVector, diffUpdate, clipUpdate, mergeUpdates } from "./utils/encoding.js";
15
15
  export { ID, createID, compareIDs, findRootTypeKey } from "./utils/ID.js";
16
16
  export { idAtIndex, indexOfId } from "./utils/positions.js";
17
17
  export { findIndexSS, getItemCleanStart, getItemCleanEnd, isKeptReplaced } from "./utils/transaction-helpers.js";
@@ -31,18 +31,18 @@ export class Transaction {
31
31
  */
32
32
  insertSet: import("./ids.js").IdSet;
33
33
  /**
34
- * YP20: the visibility changes hold records made, in order: per item,
35
- * `client, clock, len` (`len > 0`: shown, `< 0`: hidden; an item removed
36
- * while held is logged shown, as the delete set counts it hidden).
34
+ * What the transaction did to items, in order: per run of one writer's
35
+ * items `kind, client, clock, len` (an item that continues the last entry
36
+ * extends it). Kind 0, an edit: integrated (`len > 0`, before its content
37
+ * integrates; an item integrated removed logs none) or removed (`len < 0`);
38
+ * kind 1, a hold record's
39
+ * (YP20): shown (`len > 0`) or hidden (`< 0`; an item removed while held
40
+ * is logged shown, so an item's kinds 0 and 1 sum to its visibility
41
+ * change); kind 2, its mover changed (YP22: a move record integrated or
42
+ * deleted, a moved unit removed).
37
43
  * @type {Array<number>}
38
44
  */
39
- holds: Array<number>;
40
- /**
41
- * YP22: the units whose mover changed (a move record integrated or
42
- * deleted, a moved unit removed), in order: per item `client, clock, len`.
43
- * @type {Array<number>}
44
- */
45
- moves: Array<number>;
45
+ log: Array<number>;
46
46
  /**
47
47
  * All types that were directly modified (property added or child
48
48
  * inserted/deleted). New types are not included in this Set.
@@ -5,6 +5,7 @@ export function writeStateAsUpdate(encoder: UpdateEncoder, doc: Doc, targetState
5
5
  export function encodeStateAsUpdate(doc: Doc, encodedTargetStateVector?: Uint8Array): Uint8Array<ArrayBuffer>;
6
6
  export function mergeUpdates(updates: Array<Uint8Array<ArrayBuffer>>): Uint8Array<ArrayBuffer>;
7
7
  export function diffUpdate(update: Uint8Array, sv: Uint8Array): Uint8Array<ArrayBuffer>;
8
+ export function clipUpdate(update: Uint8Array, sv: Uint8Array, ds: import("./ids.js").IdSet): Uint8Array<ArrayBuffer>;
8
9
  export function decodeStateVector(decodedState: Uint8Array): Map<number, number>;
9
10
  export function encodeStateVector(doc: Doc | Map<number, number>): Uint8Array<ArrayBuffer>;
10
11
  import { UpdateEncoder } from './UpdateEncoder.js';
@@ -7,7 +7,7 @@ export { IdSet, createDeleteSetFromStructStore, diffIdSet, createIdSet, mergeIdS
7
7
  export { Doc } from './utils/Doc.js'
8
8
  export { UpdateDecoder } from './utils/UpdateDecoder.js'
9
9
  export { UpdateEncoder } from './utils/UpdateEncoder.js'
10
- export { applyUpdate, encodeStateAsUpdate, encodeStateVector, decodeStateVector, diffUpdate, mergeUpdates } from './utils/encoding.js'
10
+ export { applyUpdate, encodeStateAsUpdate, encodeStateVector, decodeStateVector, diffUpdate, clipUpdate, mergeUpdates } from './utils/encoding.js' // YP34: clipUpdate
11
11
  export { ID, createID, compareIDs, findRootTypeKey } from './utils/ID.js'
12
12
  export { isParentOf } from './utils/isParentOf.js'
13
13
  export { idAtIndex, indexOfId } from './utils/positions.js' // YP19
@@ -308,6 +308,8 @@ export class Item extends AbstractStruct {
308
308
  }
309
309
  addStructToIdSet(transaction.insertSet, this)
310
310
  transaction.doc.store.add(this)
311
+ // YP33: log the units it shows before its content integrates (a node's children follow it)
312
+ logItem(transaction, 0, this, this.content.constructor === ContentDeleted ? 0 : this.length, false)
311
313
  this.content.integrate(transaction, this)
312
314
  // add parent to transaction.changed
313
315
  addChangedTypeToTransaction(transaction, /** @type {YNode} */ (this.parent), this.parentSub)
@@ -393,7 +395,7 @@ export class Item extends AbstractStruct {
393
395
  // adjust the length of parent
394
396
  if (this.holds > 0) {
395
397
  // YP20: already hidden — the delete set counts it again, the log gives it back
396
- logHold(transaction, this, this.length)
398
+ logItem(transaction, 1, this, this.length) // YP33
397
399
  } else if (this.movers !== null) {
398
400
  // YP22: hidden at home already; its mover shows it no more (`gone`)
399
401
  logMove(transaction, this, this.mover)
@@ -411,7 +413,7 @@ export class Item extends AbstractStruct {
411
413
  }
412
414
  this.markDeleted()
413
415
  transaction.deleteSet.add(this.id.client, this.id.clock, this.length)
414
- addChangedTypeToTransaction(transaction, parent, this.parentSub)
416
+ logItem(transaction, 0, this, -this.length) // YP33
415
417
  // YP14: a node a concurrent value replaced keeps its subtree (the document asked)
416
418
  if (!isKeptReplaced(this)) this.content.delete(transaction, this) // YP22: a move record drops itself from its units' movers
417
419
  }
@@ -929,26 +931,36 @@ export class ContentDeleted {
929
931
  }
930
932
 
931
933
  // YP20 begin (edytor fork: hold records — see UPSTREAM.md YP20)
934
+ // YP33 begin (edytor fork: one log of what a transaction did to items — see UPSTREAM.md YP33)
932
935
  /**
933
- * Log that a hold changed `item`'s visibility (`len > 0`: shown, `< 0`:
934
- * hidden) in `tr.holds`, in order, and its parent as changed.
936
+ * Log `len` units of `item` as `kind` (0 an edit, 1 a hold, 2 a move) in
937
+ * `tr.log`, in order, and (unless `mark` is false) its parent as changed.
938
+ * An entry the last one continues (its kind, its client, the next clock, its
939
+ * sign) extends it, so a run of one writer's items is one entry; `len` 0
940
+ * logs none.
935
941
  *
936
942
  * @param {Transaction} tr
943
+ * @param {number} kind
937
944
  * @param {Item} item
938
945
  * @param {number} len
946
+ * @param {boolean} [mark]
939
947
  */
940
- const logHold = (tr, item, len) => {
941
- tr.holds.push(item.id.client, item.id.clock, len)
942
- addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
948
+ const logItem = (tr, kind, item, len, mark = true) => {
949
+ const log = tr.log
950
+ const n = log.length
951
+ const { client, clock } = item.id
952
+ if (log[n - 4] === kind && log[n - 3] === client && log[n - 2] + math.abs(log[n - 1]) === clock && log[n - 1] * len > 0) log[n - 1] += len
953
+ else if (len !== 0) log.push(kind, client, clock, len)
954
+ if (mark) addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
943
955
  }
956
+ // YP33 end
944
957
 
945
958
  /**
946
959
  * Add `delta` (1, or -1: release) of a hold record (`record` null: `holds`)
947
960
  * or of the move record `record` (its `movers`, YP22; a record never moves a
948
961
  * record) to `item`. An item that is not removed and starts or stops being
949
962
  * hidden moves its parent's length and drops its search markers; a hold that
950
- * does is logged (`tr.holds`), and so is a unit whose mover changed
951
- * (`tr.moves`).
963
+ * does is logged (`tr.log`, YP33), and so is a unit whose mover changed.
952
964
  *
953
965
  * @param {Transaction} tr
954
966
  * @param {Item} item
@@ -976,7 +988,7 @@ const holdItem = (tr, item, delta, record) => {
976
988
  if (item.countable && item.parentSub === null) parent._length += len
977
989
  // the indices the search markers counted moved (a held delete moves them itself)
978
990
  if (parent._searchMarker) parent._searchMarker.length = 0
979
- if (record === null) logHold(tr, item, len)
991
+ if (record === null) logItem(tr, 1, item, len) // YP33
980
992
  }
981
993
  const after = item.mover
982
994
  if (before !== after) {
@@ -1223,15 +1235,14 @@ const stronger = (a, b) =>
1223
1235
 
1224
1236
  /**
1225
1237
  * Log that `item`'s mover changed (or it left its mover: removed) in
1226
- * `tr.moves`, in order, and its home and its new mover's sequences as changed.
1238
+ * `tr.log` (YP33), and its home and its new mover's sequences as changed.
1227
1239
  *
1228
1240
  * @param {Transaction} tr
1229
1241
  * @param {Item} item
1230
1242
  * @param {Item|null} mover
1231
1243
  */
1232
1244
  const logMove = (tr, item, mover) => {
1233
- tr.moves.push(item.id.client, item.id.clock, item.length)
1234
- addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
1245
+ logItem(tr, 2, item, item.length) // YP33
1235
1246
  if (mover !== null) addChangedTypeToTransaction(tr, /** @type {YNode} */ (mover.parent), null)
1236
1247
  }
1237
1248
 
@@ -40,19 +40,21 @@ export class Transaction {
40
40
  * Describes the set of inserted items by ids
41
41
  */
42
42
  this.insertSet = createIdSet()
43
+ // YP33 begin (edytor fork: one log of what the transaction did to items — see UPSTREAM.md YP33)
43
44
  /**
44
- * YP20: the visibility changes hold records made, in order: per item,
45
- * `client, clock, len` (`len > 0`: shown, `< 0`: hidden; an item removed
46
- * while held is logged shown, as the delete set counts it hidden).
45
+ * What the transaction did to items, in order: per run of one writer's
46
+ * items `kind, client, clock, len` (an item that continues the last entry
47
+ * extends it). Kind 0, an edit: integrated (`len > 0`, before its content
48
+ * integrates; an item integrated removed logs none) or removed (`len < 0`);
49
+ * kind 1, a hold record's
50
+ * (YP20): shown (`len > 0`) or hidden (`< 0`; an item removed while held
51
+ * is logged shown, so an item's kinds 0 and 1 sum to its visibility
52
+ * change); kind 2, its mover changed (YP22: a move record integrated or
53
+ * deleted, a moved unit removed).
47
54
  * @type {Array<number>}
48
55
  */
49
- this.holds = []
50
- /**
51
- * YP22: the units whose mover changed (a move record integrated or
52
- * deleted, a moved unit removed), in order: per item `client, clock, len`.
53
- * @type {Array<number>}
54
- */
55
- this.moves = []
56
+ this.log = []
57
+ // YP33 end
56
58
  /**
57
59
  * All types that were directly modified (property added or child
58
60
  * inserted/deleted). New types are not included in this Set.