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.
- package/README.md +3 -3
- package/dist/cloudflare/DocumentRoom.d.ts +77 -2
- package/dist/cloudflare/DocumentRoom.js +37 -1
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/room/access.d.ts +6 -1
- package/dist/cloudflare/room/access.js +30 -4
- package/dist/cloudflare/room/admission.d.ts +11 -0
- package/dist/cloudflare/room/admission.js +43 -10
- package/dist/cloudflare/room/context.d.ts +11 -2
- package/dist/cloudflare/room/context.js +17 -6
- package/dist/cloudflare/room/forged.d.ts +1 -1
- package/dist/cloudflare/room/forged.js +11 -9
- package/dist/cloudflare/room/frames.d.ts +2 -0
- package/dist/cloudflare/room/frames.js +2 -0
- package/dist/cloudflare/room/presence.js +1 -4
- package/dist/cloudflare/room/reseed.d.ts +93 -0
- package/dist/cloudflare/room/reseed.js +500 -0
- package/dist/cloudflare/room/scheduler.d.ts +1 -1
- package/dist/cloudflare/room/scheduler.js +9 -1
- package/dist/cloudflare/room/shared.d.ts +10 -0
- package/dist/cloudflare/room/shared.js +10 -0
- package/dist/cloudflare/room/storage.d.ts +7 -0
- package/dist/cloudflare/room/storage.js +33 -1
- package/dist/components/Edytor.svelte +18 -3
- package/dist/components/Edytor.svelte.d.ts +10 -1
- package/dist/components/EdytorRoot.svelte +17 -0
- package/dist/components/EdytorRoot.svelte.d.ts +4 -0
- package/dist/components/Suggestion.svelte +1 -1
- package/dist/crdt/attribution/block.d.ts +3 -0
- package/dist/crdt/attribution/block.js +3 -2
- package/dist/crdt/doc/suggest.d.ts +6 -2
- package/dist/crdt/doc/suggest.js +6 -5
- package/dist/crdt/edytor-doc.d.ts +1 -1
- package/dist/crdt/edytor-doc.js +1 -1
- package/dist/crdt/engine-room.d.ts +4 -0
- package/dist/crdt/engine-room.js +6 -0
- package/dist/crdt/index.d.ts +2 -1
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/protocols/auth.d.ts +8 -1
- package/dist/crdt/protocols/auth.js +14 -1
- package/dist/crdt/providers/index.d.ts +1 -0
- package/dist/crdt/providers/index.js +22 -3
- package/dist/crdt/providers/room.d.ts +15 -0
- package/dist/crdt/providers/room.js +15 -0
- package/dist/crdt/providers/websocket.d.ts +4 -2
- package/dist/crdt/providers/websocket.js +36 -9
- package/dist/crdt/reseed.d.ts +127 -0
- package/dist/crdt/reseed.js +533 -0
- package/dist/crdt/restore.d.ts +13 -1
- package/dist/crdt/restore.js +8 -3
- package/dist/crdt/schema.d.ts +6 -0
- package/dist/crdt/schema.js +6 -0
- package/dist/crdt/structs.d.ts +8 -11
- package/dist/crdt/structs.js +25 -44
- package/dist/crdt/text/index/build.d.ts +3 -9
- package/dist/crdt/text/index/build.js +87 -181
- package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
- package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/selection/selection.svelte.d.ts +1 -1
- package/dist/selection/selection.svelte.js +87 -106
- package/dist/session/commands.js +49 -51
- package/dist/surface/attributes.d.ts +3 -3
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/observer.svelte.d.ts +7 -7
- package/dist/surface/observer.svelte.js +213 -309
- package/package.json +1 -1
package/dist/crdt/restore.d.ts
CHANGED
|
@@ -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;
|
package/dist/crdt/restore.js
CHANGED
|
@@ -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;
|
package/dist/crdt/schema.d.ts
CHANGED
|
@@ -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;
|
package/dist/crdt/schema.js
CHANGED
|
@@ -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
|
package/dist/crdt/structs.d.ts
CHANGED
|
@@ -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
|
|
68
|
-
* from, to)` gets the struct and the clock range `[from,
|
|
69
|
-
* covers (a struct can reach past
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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`
|
package/dist/crdt/structs.js
CHANGED
|
@@ -50,51 +50,32 @@ export const structAt = (Y, structs, clock) => {
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Walk every struct
|
|
54
|
-
* from, to)` gets the struct and the clock range `[from,
|
|
55
|
-
* covers (a struct can reach past
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
77
|
-
|
|
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,
|
|
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
|
-
*
|
|
1804
|
-
*
|
|
1805
|
-
* (
|
|
1806
|
-
* `
|
|
1807
|
-
*
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
* removed, `putSlot`) and lists the ids
|
|
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 = (
|
|
1814
|
-
|
|
1810
|
+
const makeEditIndex = (log, [from, seen], pairs) => {
|
|
1811
|
+
const texts = new Map();
|
|
1812
|
+
const seqs = new Map();
|
|
1815
1813
|
const rehome = new Set();
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
|
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
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
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
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
-
|
|
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
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
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(
|
|
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
|