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
@@ -35,6 +35,8 @@ export type Unit = {
35
35
  };
36
36
  /** `items` one unit each (a character with its marks, or an atom), keyed for comparison. */
37
37
  export declare const unitsOf: (items: readonly ContentItem[]) => Unit[];
38
+ /** Units back to runs: consecutive characters with the same marks joined. */
39
+ export declare const runsOf: (units: readonly Unit[]) => ContentItem[];
38
40
  /** The one changed stretch from `x` to `y`: `x[p, e)` became `mid`. */
39
41
  export declare const hunk: (x: readonly Unit[], y: readonly Unit[]) => {
40
42
  p: number;
@@ -76,9 +78,19 @@ export type RestoreFacade<Plan> = Partial<SuggestFacade> & {
76
78
  patchData(target: null, ops: DataPatch[]): Plan;
77
79
  };
78
80
  };
81
+ /** How a restore writes content, and whether it rejects the pending suggestions. */
82
+ export type RestoreOptions<Plan> = {
83
+ /**
84
+ * The content rewrite of a block that differs (default `rewriteContent`).
85
+ * Another one edits through the display, so the block keeps its claims.
86
+ */
87
+ rewrite?: (facade: RestoreFacade<Plan>, id: BlockId, have: readonly Unit[], want: readonly Unit[]) => boolean;
88
+ /** `keep`: the pending suggestions stay (a re-seed's rebase writes as one more writer). */
89
+ suggestions?: 'reject' | 'keep';
90
+ };
79
91
  /**
80
92
  * Make the visible document `doc` (through `facade`, the facade over it)
81
93
  * equal `json`. Runs inside the caller's transaction; the writer is
82
94
  * `doc.clientID`. See the module comment for what it writes.
83
95
  */
84
- export declare const restoreDocument: <Plan>(doc: EngineDoc, facade: RestoreFacade<Plan>, json: JSONDoc) => RestoreReport;
96
+ export declare const restoreDocument: <Plan>(doc: EngineDoc, facade: RestoreFacade<Plan>, json: JSONDoc, { rewrite, suggestions }?: RestoreOptions<Plan>) => RestoreReport;
@@ -24,7 +24,7 @@ export const unitsOf = (items) => {
24
24
  return out;
25
25
  };
26
26
  /** Units back to runs: consecutive characters with the same marks joined. */
27
- const runsOf = (units) => {
27
+ export const runsOf = (units) => {
28
28
  const out = [];
29
29
  let tag = null;
30
30
  for (const { key, item } of units) {
@@ -98,7 +98,7 @@ const walk = (specs, parent, into) => {
98
98
  * equal `json`. Runs inside the caller's transaction; the writer is
99
99
  * `doc.clientID`. See the module comment for what it writes.
100
100
  */
101
- export const restoreDocument = (doc, facade, json) => {
101
+ export const restoreDocument = (doc, facade, json, { rewrite, suggestions = 'reject' } = {}) => {
102
102
  const report = { revived: 0, moved: 0, rewritten: 0, created: 0, deleted: 0 };
103
103
  const specs = json.children.map((block) => toBlockSpec(block));
104
104
  const all = walk(specs, null, new Map());
@@ -170,6 +170,11 @@ export const restoreDocument = (doc, facade, json) => {
170
170
  if (have.length === want.length && have.every((u, i) => u.key === want[i].key))
171
171
  continue;
172
172
  const claims = modelView(doc).blocks.get(id)?.claims ?? [];
173
+ if (rewrite) {
174
+ rewrite(facade, id, have, want);
175
+ report.rewritten++;
176
+ continue;
177
+ }
173
178
  if (claims.some((c) => c.stamp.c >= 0)) {
174
179
  deleteClaims(doc, claims);
175
180
  have = unitsOf(facade.contentItems(id));
@@ -202,7 +207,7 @@ export const restoreDocument = (doc, facade, json) => {
202
207
  // their proposals — is rejected, in the restore's step (its undo brings
203
208
  // them back).
204
209
  // (Built over the facade, its shared suggestions: `doc/suggest.ts`.)
205
- const suggest = facade.part && suggestionsOf(facade);
210
+ const suggest = suggestions === 'reject' && facade.part && suggestionsOf(facade);
206
211
  if (suggest)
207
212
  suggest.run(suggest.decide(suggest.changes(), false));
208
213
  return report;
@@ -98,6 +98,12 @@ export declare const hasWithdrawMark: (node: {
98
98
  /** The per-block `lastChangedBy` LWW attr (`SCHEMA.blockAttrs.lastChanged`). */
99
99
  export declare const LAST_CHANGED_ATTR = "l";
100
100
  export declare const CONTENT = "content";
101
+ /**
102
+ * The epoch a re-seed wrote (`room.reseed.protocol`): the root `epoch`, its
103
+ * attr `n` (none before the first re-seed). Apart from `meta`, whose keys
104
+ * are the schema stamp's alone.
105
+ */
106
+ export declare const EPOCH_ROOT = "epoch";
101
107
  /** The schema generation stamped on the `meta` root (`v`) and its manifest name (`schema`). */
102
108
  export declare const SCHEMA: {
103
109
  readonly version: 11;
@@ -87,6 +87,12 @@ export const hasWithdrawMark = (node) => [...node.attrKeys()].some((key) => key.
87
87
  /** The per-block `lastChangedBy` LWW attr (`SCHEMA.blockAttrs.lastChanged`). */
88
88
  export const LAST_CHANGED_ATTR = 'l';
89
89
  export const CONTENT = 'content';
90
+ /**
91
+ * The epoch a re-seed wrote (`room.reseed.protocol`): the root `epoch`, its
92
+ * attr `n` (none before the first re-seed). Apart from `meta`, whose keys
93
+ * are the schema stamp's alone.
94
+ */
95
+ export const EPOCH_ROOT = 'epoch';
90
96
  /** The schema generation stamped on the `meta` root (`v`) and its manifest name (`schema`). */
91
97
  export const SCHEMA = {
92
98
  // 7: a text delete is a hold record (fork patch YP20) on the wire and in
@@ -64,17 +64,14 @@ export declare const clientsOf: (doc: EngineDoc) => Map<number, StoreStruct[]>;
64
64
  */
65
65
  export declare const structAt: (Y: EngineApi, structs: readonly StoreStruct[], clock: number) => StoreStruct | null;
66
66
  /**
67
- * Walk every struct an IdSet covers, in (client, clock) order: `fn(struct,
68
- * from, to)` gets the struct and the clock range `[from, to)` of it the set
69
- * covers (a struct can reach past a range a diff of two id sets cut). The
70
- * walk never splits a struct; `fn` returning `false` halts it.
71
- *
72
- * Returns `false` when coverage was INCOMPLETE — a client's struct list
73
- * was missing or a range start could not be located — so the caller can
74
- * fall back to an all-consumers/opaque answer instead of trusting a
75
- * partial walk. `clientsOf` throws first when the store itself is absent.
76
- */
77
- export declare const walkIdSet: (Y: EngineApi, doc: EngineDoc, idSet: IdSetLike, fn: (struct: StoreStruct, from: number, to: number) => boolean | void) => boolean;
67
+ * Walk every struct the range `client:clock`, `len` units, covers, in clock
68
+ * order: `fn(struct, from, to)` gets the struct and the clock range `[from,
69
+ * to)` of it the range covers (a struct can reach past it). The walk never
70
+ * splits a struct; a client or a clock the store lacks walks nothing.
71
+ */
72
+ export declare const walkRange: (Y: EngineApi, doc: EngineDoc, client: number, clock: number, len: number, fn: (struct: StoreStruct, from: number, to: number) => void) => void;
73
+ /** Walk every struct an IdSet covers, in (client, clock) order (`walkRange` over each range). */
74
+ export declare const walkIdSet: (Y: EngineApi, doc: EngineDoc, idSet: IdSetLike, fn: (struct: StoreStruct, from: number, to: number) => void) => void;
78
75
  /**
79
76
  * The id the local `redone` chain leads `id` to: the copy an undo or redo
80
77
  * on THIS replica re-created of a deleted item (redo copies chain), or `id`
@@ -50,51 +50,32 @@ export const structAt = (Y, structs, clock) => {
50
50
  }
51
51
  };
52
52
  /**
53
- * Walk every struct an IdSet covers, in (client, clock) order: `fn(struct,
54
- * from, to)` gets the struct and the clock range `[from, to)` of it the set
55
- * covers (a struct can reach past a range a diff of two id sets cut). The
56
- * walk never splits a struct; `fn` returning `false` halts it.
57
- *
58
- * Returns `false` when coverage was INCOMPLETE — a client's struct list
59
- * was missing or a range start could not be located — so the caller can
60
- * fall back to an all-consumers/opaque answer instead of trusting a
61
- * partial walk. `clientsOf` throws first when the store itself is absent.
62
- */
63
- export const walkIdSet = (Y, doc, idSet, fn) => {
64
- const clients = clientsOf(doc);
65
- let complete = true;
66
- let stopped = false;
67
- idSet.clients.forEach((ranges, client) => {
68
- if (stopped)
69
- return;
70
- const structs = clients.get(client);
71
- if (structs === undefined) {
72
- complete = false;
73
- return;
74
- }
75
- for (const r of ranges.getIds()) {
76
- const end = r.clock + r.len;
77
- let i;
78
- try {
79
- i = Y.findIndexSS(structs, r.clock);
80
- }
81
- catch {
82
- complete = false;
83
- continue;
84
- }
85
- for (; i < structs.length; i++) {
86
- const s = structs[i];
87
- if (s.id.clock >= end)
88
- break;
89
- if (fn(s, Math.max(r.clock, s.id.clock), Math.min(end, s.id.clock + s.length)) === false) {
90
- stopped = true;
91
- return;
92
- }
93
- }
94
- }
95
- });
96
- return complete;
53
+ * Walk every struct the range `client:clock`, `len` units, covers, in clock
54
+ * order: `fn(struct, from, to)` gets the struct and the clock range `[from,
55
+ * to)` of it the range covers (a struct can reach past it). The walk never
56
+ * splits a struct; a client or a clock the store lacks walks nothing.
57
+ */
58
+ export const walkRange = (Y, doc, client, clock, len, fn) => {
59
+ const structs = clientsOf(doc).get(client) ?? [];
60
+ const end = clock + len;
61
+ let i = structs.length;
62
+ try {
63
+ if (clock < end)
64
+ i = Y.findIndexSS(structs, clock);
65
+ }
66
+ catch {
67
+ // past the client's clock: nothing to walk
68
+ }
69
+ for (; i < structs.length && structs[i].id.clock < end; i++) {
70
+ const s = structs[i];
71
+ fn(s, Math.max(clock, s.id.clock), Math.min(end, s.id.clock + s.length));
72
+ }
97
73
  };
74
+ /** Walk every struct an IdSet covers, in (client, clock) order (`walkRange` over each range). */
75
+ export const walkIdSet = (Y, doc, idSet, fn) => idSet.clients.forEach((ranges, client) => {
76
+ for (const r of ranges.getIds())
77
+ walkRange(Y, doc, client, r.clock, r.len, fn);
78
+ });
98
79
  /**
99
80
  * The id the local `redone` chain leads `id` to: the copy an undo or redo
100
81
  * on THIS replica re-created of a deleted item (redo copies chain), or `id`
@@ -10,7 +10,6 @@
10
10
  */
11
11
  import type { EngineDoc, EngineNode, EngineTransaction } from '../../engine-api.js';
12
12
  import { type BlockId, type BlockRec, type ChildSlot, type DisplayOwnership, type ResolvedPlacement } from '../../placement/model.js';
13
- import { type IdSetLike } from '../../structs.js';
14
13
  import { type LiveRow, type RowItem } from '../rows.js';
15
14
  import type { ContentRun, RunView } from '../runs.js';
16
15
  import { type Cached } from './shared.js';
@@ -18,8 +17,7 @@ import { type Claim, type Owner, type Stream } from '../model.js';
18
17
  import { type Edge, type SlotRef } from '../../placement/resolve.js';
19
18
  export type TextEdit = {
20
19
  item: RowItem;
21
- len: number;
22
- del: boolean;
20
+ n: number;
23
21
  };
24
22
  export type TextEdits = {
25
23
  edits: TextEdit[];
@@ -70,13 +68,9 @@ export type Published = {
70
68
  };
71
69
  /** A transaction as the fold reads it. */
72
70
  export type Tx = {
73
- insertSet?: IdSetLike;
74
- deleteSet?: IdSetLike;
75
71
  changed?: EngineTransaction['changed'];
76
- /** The visibility changes hold records made, in order (fork patch YP20). */
77
- holds?: number[];
78
- /** The units whose mover changed, in order (fork patch YP22). */
79
- moves?: number[];
72
+ /** What the transaction did to items, in order: per item `kind, client, clock, len` (fork patch YP33). */
73
+ log?: number[];
80
74
  };
81
75
  /** The index's self-checks (`indexChecks`), run after each fold and each report. */
82
76
  export type SelfChecks = {
@@ -1,6 +1,6 @@
1
1
  import { bySlot, childrenIndex, displaySlotOf, documentOrder, REGISTRY_KEY } from '../../placement/model.js';
2
2
  import { CONTENT, CONTENT_NODE, DOC_DATA_ROOT, hasDeleteMark, hasWithdrawMark, isNodeLike, NONCE } from '../../schema.js';
3
- import { changedUnder, clientsOf, engineOps, firstItem, itemOf, queuedTransactions, sequenceLength, structAt, walkIdSet } from '../../structs.js';
3
+ import { changedUnder, engineOps, firstItem, itemOf, queuedTransactions, sequenceLength, walkRange } from '../../structs.js';
4
4
  import { gapOfItem, headOf, placeRow, rowLength, rowOf, segmentAt, segmentClaims, segmentOfCut, segmentOfGap, shiftGap, streamOfSegment } from '../rows.js';
5
5
  import { addTo, CONTENT_ATTR, DATA_SEQ_FACET, dataOf, dropFrom, EMPTY_IDS, EMPTY_RUNS, ENTRY_FACET, facetOf, indexChecks, keyOf, runEquals, sameShape, SEQ_FACET, typeAttr } from './shared.js';
6
6
  import { cloneJsonSafe, sameIds } from '../../../utils/json.js';
@@ -1782,7 +1782,6 @@ const buildIndex = (doc) => {
1782
1782
  // THE fold: one pass over a transaction's changed `(type, parentSub)` pairs
1783
1783
  // into the index, once at commit and, for read-your-writes, over the
1784
1784
  // pending part of every transaction whose commit fold has not run.
1785
- const ops = engineOps(Y);
1786
1785
  const NO_EDITS = Object.freeze({
1787
1786
  edits: [],
1788
1787
  scan: false,
@@ -1797,26 +1796,28 @@ const buildIndex = (doc) => {
1797
1796
  return true;
1798
1797
  return false;
1799
1798
  };
1800
- /** The struct holding id `client:clock` (unsplit), `null` when the store holds none. */
1801
- const structOf = (client, clock) => structAt(Y, clientsOf(doc).get(client) ?? [], clock);
1802
1799
  /**
1803
- * The edits the id sets `ins`/`del`, the hold log `log` and the move log
1804
- * `moves` (what the fold has not folded yet) hold: per text, its edits
1805
- * (a log entry is a unit a hold record hid or showed, fork patch YP20:
1806
- * `client, clock, ±len`, in order); per sequence, what changed in it
1807
- * (`seq`): the slots written and removed, the records written and
1808
- * removed and the units whose mover changed name the blocks to re-place.
1809
- * Reading the sequences writes the slot map (the slots written and
1810
- * removed, `putSlot`) and lists the ids whose home to read again
1811
- * (`rehome`).
1800
+ * What a transaction's log past a read's cursor did (fork patch YP33:
1801
+ * per run of items `kind, client, clock, len`, in order), in one pass: per text
1802
+ * (`texts`), its edits (the units each item showed or hid, in order); per
1803
+ * sequence (`seqs`), what changed in it: the slots written and removed, the
1804
+ * records written and removed and the units whose mover changed name
1805
+ * the blocks to re-place; with `pairs` (a read's fold), the `(type, key)`
1806
+ * pairs it changed, and the structs it named (`structs`). Reading the sequences writes the
1807
+ * slot map (the slots written and removed, `putSlot`) and lists the ids
1808
+ * whose home to read again (`rehome`).
1812
1809
  */
1813
- const makeEditIndex = (ins, del, log = [], moves = []) => {
1814
- let seqs;
1810
+ const makeEditIndex = (log, [from, seen], pairs) => {
1811
+ const texts = new Map();
1812
+ const seqs = new Map();
1815
1813
  const rehome = new Set();
1816
- const seqOf = (node) => {
1817
- return getOr(seqs, node, () => ({ blocks: new Set(), data: false }));
1814
+ let structs = 0;
1815
+ const seqOf = (node) => getOr(seqs, node, () => ({ blocks: new Set(), data: false }));
1816
+ const note = (s) => {
1817
+ if (pairs && isNodeLike(s.parent))
1818
+ getOr(pairs, s.parent, () => new Set()).add(s.parentSub);
1818
1819
  };
1819
- /** Note unit `c:k` (`value`) of the sequence of `node`: a block slot names its block, a data slot its data. */
1820
+ /** Note unit `c:k` (`value`) of a sequence: a block slot names its block, a data slot its data. */
1820
1821
  const noteUnit = (e, owner, value, c, k, inserted) => {
1821
1822
  if (isBlockSlot(value)) {
1822
1823
  e.blocks.add(slotOwner(registry, kept, value.b, c));
@@ -1828,102 +1829,58 @@ const buildIndex = (doc) => {
1828
1829
  else if (isDataSlot(value))
1829
1830
  e.data = true;
1830
1831
  };
1831
- const visitSeq = (s, from, to, inserted) => {
1832
- const parent = s.parent;
1833
- if (s.parentSub !== null || !isNodeLike(parent) || parent.name === CONTENT_NODE)
1834
- return;
1835
- const e = seqOf(parent);
1836
- const owner = ownerOfNode(registry, kept, parent);
1837
- const it = s;
1838
- if (it.content === undefined)
1839
- return;
1840
- if (isRecord(it)) {
1841
- const ids = it.content.ids;
1842
- for (let i = 0; i < ids.length; i += 3)
1843
- for (let j = 0; j < ids[i + 2]; j++) {
1844
- const u = unitOf(doc, { c: ids[i], k: ids[i + 1] + j });
1845
- if (u !== null)
1846
- noteUnit(e, owner, u.value, ids[i], ids[i + 1] + j, null);
1847
- }
1848
- }
1849
- else if (it.content.arr !== undefined)
1850
- for (let k = from; k < to; k++)
1851
- noteUnit(e, owner, it.content.arr[k - s.id.clock], s.id.client, k, inserted);
1852
- };
1853
- const buildSeqs = () => {
1854
- if (seqs !== undefined)
1855
- return seqs;
1856
- seqs = new Map();
1857
- if (ins === undefined || del === undefined)
1858
- return seqs;
1859
- walkIdSet(Y, doc, ins, (s, from, to) => visitSeq(s, from, to, true));
1860
- walkIdSet(Y, doc, del, (s, from, to) => visitSeq(s, from, to, false));
1861
- for (let i = 0; i < moves.length; i += 3) {
1862
- const s = structOf(moves[i], moves[i + 1]);
1863
- if (s === null)
1864
- continue;
1865
- visitSeq(s, moves[i + 1], moves[i + 1] + moves[i + 2], null);
1866
- // Where the unit shows now: that sequence changed too.
1832
+ /** Walk `n` units of entry `i` from clock `k` (`n`'s sign is the entry's). */
1833
+ const run = (i, k, n) => {
1834
+ const kind = log[i];
1835
+ walkRange(Y, doc, log[i + 1], k, Math.abs(n), (s, a, b) => {
1836
+ structs++;
1837
+ note(s);
1838
+ // A unit a record moved: where it shows now changed too.
1867
1839
  const mover = s.mover;
1868
- if (mover)
1840
+ if (mover) {
1841
+ note(mover);
1869
1842
  seqOf(mover.parent);
1870
- }
1871
- return seqs;
1872
- };
1873
- let built;
1874
- const build = () => {
1875
- if (built !== undefined)
1876
- return built;
1877
- if (ins === undefined || del === undefined)
1878
- return (built = null);
1879
- const out = new Map();
1880
- const edit = (s, from, to, deleted) => {
1843
+ }
1881
1844
  const parent = s.parent;
1882
- if (s.parentSub !== null || !isNodeLike(parent))
1845
+ const it = s;
1846
+ if (s.parentSub !== null || !isNodeLike(parent) || it.content === undefined)
1847
+ return;
1848
+ if (parent.name === CONTENT_NODE) {
1849
+ if (kind > 1)
1850
+ return;
1851
+ const e = getOr(texts, parent, () => ({ edits: [], scan: false, opaque: false }));
1852
+ // A format marker moves no unit; its effect reaches past the edit.
1853
+ if (s.countable !== true)
1854
+ e.opaque = true;
1855
+ else if (holdsBoundary(s, a, b))
1856
+ e.scan = true;
1857
+ else
1858
+ e.edits.push({ item: s, n: n < 0 ? a - b : b - a });
1883
1859
  return;
1884
- const e = getOr(out, parent, () => ({ edits: [], scan: false, opaque: false }));
1885
- // A format marker moves no unit; its effect reaches past the edit.
1886
- if (s.countable !== true)
1887
- return void (e.opaque = true);
1888
- if (holdsBoundary(s, from, to))
1889
- return void (e.scan = true);
1890
- e.edits.push({ item: s, len: to - from, del: deleted });
1891
- };
1892
- const visit = (idSet, deleted) => walkIdSet(Y, doc, idSet, (s, from, to) => {
1893
- // Written and removed (or held) within the fold: nothing moved.
1894
- if (!(deleted ? ins.has(s.id.client, from) : s.deleted))
1895
- edit(s, from, to, deleted);
1896
- });
1897
- // In order, after the inserts and before the deletes (each gap then
1898
- // holds what an edit takes); a unit written within the fold shows or
1899
- // not as its insert says.
1900
- const held = () => {
1901
- for (let i = 0; i < log.length; i += 3) {
1902
- const s = structOf(log[i], log[i + 1]);
1903
- if (s === null)
1904
- return false;
1905
- const n = log[i + 2];
1906
- if (!ins.has(log[i], log[i + 1]))
1907
- edit(s, log[i + 1], log[i + 1] + Math.abs(n), n < 0);
1908
1860
  }
1909
- return true;
1910
- };
1911
- built = visit(ins, false) && held() && visit(del, true) ? out : null;
1912
- return built;
1913
- };
1914
- return {
1915
- /** `node`'s edits (`null`: unknown — rescan, every consumer recomputes). */
1916
- text: (node) => {
1917
- const map = build();
1918
- return map === null ? null : (map.get(node) ?? NO_EDITS);
1919
- },
1920
- /** What changed in `node`'s sequence (`undefined`: nothing). */
1921
- seq: (node) => buildSeqs().get(node),
1922
- /** The sequences the fold changed. */
1923
- seqNodes: () => buildSeqs().keys(),
1924
- /** The ids whose home to read again (their slots changed). */
1925
- rehome
1861
+ const e = seqOf(parent);
1862
+ const owner = ownerOfNode(registry, kept, parent);
1863
+ const inserted = kind > 1 ? null : n > 0;
1864
+ if (isRecord(it)) {
1865
+ const ids = it.content.ids;
1866
+ for (let i = 0; i < ids.length; i += 3)
1867
+ for (let j = 0; j < ids[i + 2]; j++) {
1868
+ const u = unitOf(doc, { c: ids[i], k: ids[i + 1] + j });
1869
+ if (u !== null)
1870
+ noteUnit(e, owner, u.value, ids[i], ids[i + 1] + j, null);
1871
+ }
1872
+ }
1873
+ else if (it.content.arr !== undefined)
1874
+ for (let k = a; k < b; k++)
1875
+ noteUnit(e, owner, it.content.arr[k - s.id.clock], s.id.client, k, inserted);
1876
+ });
1926
1877
  };
1878
+ // The units the engine added to the entry a read folded last (`seen`: its length then), then the rest.
1879
+ if (from > 0 && log[from - 1] !== seen)
1880
+ run(from - 4, log[from - 2] + Math.abs(seen), log[from - 1] - seen);
1881
+ for (let i = from; i < log.length; i += 4)
1882
+ run(i, log[i + 2], log[i + 3]);
1883
+ return { texts, seqs, structs, rehome };
1927
1884
  };
1928
1885
  const runs = (b) => {
1929
1886
  // Read-your-writes: fold the open transaction's pending writes first.
@@ -1986,7 +1943,7 @@ const buildIndex = (doc) => {
1986
1943
  ensureRec(id);
1987
1944
  const rec = blocks.get(id);
1988
1945
  // A data slot in the block's sequence: its arrays' items changed (the seq pass seeded the blocks).
1989
- if (kinds.has('seq') && rec && editsOf.seq(rec.node)?.data)
1946
+ if (kinds.has('seq') && rec && editsOf.seqs.get(rec.node)?.data)
1990
1947
  kinds.add('meta');
1991
1948
  if (kinds.has('meta') && rec) {
1992
1949
  const was = rec.type;
@@ -2040,7 +1997,7 @@ const buildIndex = (doc) => {
2040
1997
  if (!fresh) {
2041
1998
  for (const e of t.edits) {
2042
1999
  const gap = gapOfItem(row, e.item);
2043
- if (gap < 0 || !shiftGap(row, gap, e.del ? -e.len : e.len)) {
2000
+ if (gap < 0 || !shiftGap(row, gap, e.n)) {
2044
2001
  fresh = true;
2045
2002
  break;
2046
2003
  }
@@ -2219,7 +2176,7 @@ const buildIndex = (doc) => {
2219
2176
  continue;
2220
2177
  // A sequence edit of the block's own text, or a change inside one of its atoms.
2221
2178
  const list = getOr(edits, id, () => []);
2222
- list.push(seq ? editsOf.text(type) : null);
2179
+ list.push(seq ? (editsOf.texts.get(type) ?? NO_EDITS) : null);
2223
2180
  }
2224
2181
  }
2225
2182
  const ctx = {
@@ -2230,8 +2187,7 @@ const buildIndex = (doc) => {
2230
2187
  parents: new Set()
2231
2188
  };
2232
2189
  // The sequences (the root's, the blocks'): their slots index the blocks' homes, their changes seed the placements.
2233
- for (const node of editsOf.seqNodes()) {
2234
- const ch = editsOf.seq(node);
2190
+ for (const [node, ch] of editsOf.seqs) {
2235
2191
  seqEpoch++;
2236
2192
  for (const b of ch.blocks)
2237
2193
  placementSeeds.add(b);
@@ -2289,71 +2245,31 @@ const buildIndex = (doc) => {
2289
2245
  }
2290
2246
  return touched;
2291
2247
  };
2292
- /** Each transaction's folded part: copies of its id sets, the hold and move logs' folded lengths, and the watermark. */
2248
+ /** Each transaction's folded part (`Cursor`). */
2293
2249
  const cursors = new WeakMap();
2250
+ const NONE = [0];
2251
+ /** Whether `log` holds entries or units past `cursor`. */
2252
+ const past = (log, [from, seen]) => log.length !== from || log[from - 1] !== seen;
2294
2253
  /** The transactions whose commit fold ran. */
2295
2254
  const committed = new WeakSet();
2296
- const lengthOf = (set) => {
2297
- let n = 0;
2298
- set.clients.forEach((ranges) => {
2299
- for (const r of ranges.getIds())
2300
- n += r.len;
2301
- });
2302
- return n;
2303
- };
2304
- /** How much a transaction wrote so far: its id sets' lengths and its hold and move logs'. */
2305
- const watermark = (tr) => lengthOf(tr.insertSet) +
2306
- lengthOf(tr.deleteSet) +
2307
- (tr.holds?.length ?? 0) +
2308
- (tr.moves?.length ?? 0);
2309
2255
  /**
2310
- * Fold the pending part of `tr` — the structs its insert/delete sets
2311
- * gained since the last fold — before a read inside it (read-your-
2312
- * writes; `transaction.changed` does not grow on a second edit to an
2256
+ * Fold the pending part of `tr` — the entries its log gained since the
2257
+ * last fold — before a read inside it (read-your-writes;
2258
+ * `transaction.changed` does not grow on a second edit to an
2313
2259
  * already-changed type).
2314
2260
  */
2315
2261
  const syncPending = (tr) => {
2316
- if (tr?.insertSet === undefined || tr.deleteSet === undefined)
2317
- return false;
2318
- let cursor = cursors.get(tr);
2319
- if (cursor === undefined)
2320
- cursors.set(tr, (cursor = { ins: ops.idSet(), del: ops.idSet(), holds: 0, moves: 0, mark: 0 }));
2321
- const mark = watermark(tr);
2322
- if (mark === cursor.mark)
2262
+ const log = tr?.log;
2263
+ const cursor = cursors.get(tr) ?? NONE;
2264
+ if (log === undefined || !past(log, cursor))
2323
2265
  return false;
2324
- const ins = ops.diff(tr.insertSet, cursor.ins);
2325
- const del = ops.diff(tr.deleteSet, cursor.del);
2326
- const log = (tr.holds ?? []).slice(cursor.holds);
2327
- const moves = (tr.moves ?? []).slice(cursor.moves);
2328
- ops.insertInto(cursor.ins, ins);
2329
- ops.insertInto(cursor.del, del);
2330
- cursor.holds += log.length;
2331
- cursor.moves += moves.length;
2332
- cursor.mark = mark;
2266
+ cursors.set(tr, [log.length, log[log.length - 1]]);
2333
2267
  for (const f of frames)
2334
2268
  f.wrote = true;
2335
- const changed = new Map();
2336
- const note = (s) => {
2337
- foldStats.structs++;
2338
- if (!isNodeLike(s.parent))
2339
- return;
2340
- const subs = getOr(changed, s.parent, () => new Set());
2341
- subs.add(s.parentSub);
2342
- };
2343
- walkIdSet(Y, doc, ins, note);
2344
- walkIdSet(Y, doc, del, note);
2345
- for (const l of [log, moves])
2346
- for (let i = 0; i < l.length; i += 3) {
2347
- const s = structOf(l[i], l[i + 1]);
2348
- if (s === null)
2349
- continue;
2350
- note(s);
2351
- // A unit a record moved: where it shows now changed too (a held unit has no mover).
2352
- const mover = s.mover;
2353
- if (mover)
2354
- note(mover);
2355
- }
2356
- fold(changed, makeEditIndex(ins, del, log, moves));
2269
+ const pairs = new Map();
2270
+ const edits = makeEditIndex(log, cursor, pairs);
2271
+ foldStats.structs += edits.structs;
2272
+ fold(pairs, edits);
2357
2273
  return true;
2358
2274
  };
2359
2275
  const openTx = () => doc._transaction;
@@ -2371,10 +2287,7 @@ const buildIndex = (doc) => {
2371
2287
  syncPending(tr);
2372
2288
  };
2373
2289
  /** Some queued transaction holds writes no fold has seen. */
2374
- const unfolded = () => queuedTransactions(doc).some((tr) => !committed.has(tr) &&
2375
- tr.insertSet !== undefined &&
2376
- tr.deleteSet !== undefined &&
2377
- watermark(tr) !== (cursors.get(tr)?.mark ?? 0));
2290
+ const unfolded = () => queuedTransactions(doc).some((tr) => !committed.has(tr) && tr.log !== undefined && past(tr.log, cursors.get(tr) ?? NONE));
2378
2291
  /**
2379
2292
  * The commit of a transaction that changed the registry or a node under
2380
2293
  * it: fold `transaction.changed` once (the report publishes from `update`).
@@ -2382,19 +2295,12 @@ const buildIndex = (doc) => {
2382
2295
  const onCommit = (tr) => {
2383
2296
  if (!changedUnder(tr, [registry]))
2384
2297
  return;
2385
- let [ins, del] = [tr.insertSet, tr.deleteSet];
2386
2298
  // The writes a read folded already moved the rows: only the rest
2387
2299
  // are edits now (the facets fold again, idempotent).
2388
- const cursor = cursors.get(tr);
2389
- if (cursor !== undefined && ins !== undefined && del !== undefined) {
2390
- ins = ops.diff(ins, cursor.ins);
2391
- del = ops.diff(del, cursor.del);
2392
- }
2393
- const log = (tr.holds ?? []).slice(cursor?.holds ?? 0);
2394
- const moves = (tr.moves ?? []).slice(cursor?.moves ?? 0);
2300
+ const cursor = cursors.get(tr) ?? NONE;
2395
2301
  cursors.delete(tr);
2396
2302
  committed.add(tr);
2397
- fold(tr.changed ?? new Map(), makeEditIndex(ins, del, log, moves));
2303
+ fold(tr.changed ?? new Map(), makeEditIndex(tr.log ?? [], cursor));
2398
2304
  };
2399
2305
  // ══ The change report ═══════════════════════════════════════════
2400
2306
  // The change report: the fold against the last published index, one per