edytor 3.0.0 → 4.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 +2 -2
- package/dist/cloudflare/DocumentRoom.d.ts +13 -9
- package/dist/cloudflare/DocumentRoom.js +10 -6
- package/dist/cloudflare/room/context.d.ts +27 -26
- package/dist/cloudflare/room/context.js +2 -2
- package/dist/cloudflare/room/purge.d.ts +6 -3
- package/dist/cloudflare/room/purge.js +44 -19
- package/dist/cloudflare/room/updates.d.ts +3 -4
- package/dist/cloudflare/room/updates.js +6 -8
- package/dist/crdt/attribution/attribution.d.ts +31 -12
- package/dist/crdt/attribution/attribution.js +56 -18
- package/dist/crdt/attribution/block.d.ts +3 -3
- package/dist/crdt/attribution/block.js +7 -8
- package/dist/crdt/attribution/index.d.ts +1 -1
- package/dist/crdt/attribution/index.js +1 -1
- package/dist/crdt/data.d.ts +62 -31
- package/dist/crdt/data.js +211 -103
- package/dist/crdt/doc/delete.d.ts +1 -1
- package/dist/crdt/doc/delete.js +13 -30
- package/dist/crdt/doc/funnel.js +29 -24
- package/dist/crdt/doc/gate.d.ts +3 -3
- package/dist/crdt/doc/history.d.ts +25 -9
- package/dist/crdt/doc/history.js +150 -40
- package/dist/crdt/doc/layout.js +11 -22
- package/dist/crdt/doc/meta.js +12 -17
- package/dist/crdt/doc/moves.d.ts +1 -1
- package/dist/crdt/doc/moves.js +52 -32
- package/dist/crdt/doc/reads.js +2 -2
- package/dist/crdt/doc/seed.js +2 -2
- package/dist/crdt/doc/split-merge.js +12 -15
- package/dist/crdt/doc/steps.d.ts +25 -13
- package/dist/crdt/doc/steps.js +52 -122
- package/dist/crdt/doc/types.d.ts +21 -9
- package/dist/crdt/document.d.ts +16 -5
- package/dist/crdt/document.js +21 -4
- package/dist/crdt/edytor-doc.d.ts +62 -64
- package/dist/crdt/edytor-doc.js +18 -13
- package/dist/crdt/engine-api.d.ts +33 -1
- package/dist/crdt/engine.d.ts +0 -1
- package/dist/crdt/engine.js +1 -4
- package/dist/crdt/flow.d.ts +1 -1
- package/dist/crdt/flow.js +25 -27
- package/dist/crdt/index.d.ts +26 -25
- package/dist/crdt/index.js +1 -1
- package/dist/crdt/placement/display.d.ts +5 -30
- package/dist/crdt/placement/display.js +39 -144
- package/dist/crdt/placement/keys.d.ts +58 -0
- package/dist/crdt/placement/keys.js +90 -0
- package/dist/crdt/placement/model.d.ts +119 -118
- package/dist/crdt/placement/model.js +242 -142
- package/dist/crdt/placement/resolve.d.ts +123 -35
- package/dist/crdt/placement/resolve.js +187 -153
- package/dist/crdt/placement/slots.d.ts +134 -0
- package/dist/crdt/placement/slots.js +152 -0
- package/dist/crdt/providers/room.d.ts +16 -2
- package/dist/crdt/providers/room.js +64 -17
- package/dist/crdt/providers/websocket.js +5 -3
- package/dist/crdt/purge.d.ts +17 -8
- package/dist/crdt/purge.js +236 -126
- package/dist/crdt/restore.d.ts +2 -2
- package/dist/crdt/restore.js +18 -34
- package/dist/crdt/schema.d.ts +22 -21
- package/dist/crdt/schema.js +29 -23
- package/dist/crdt/storage.d.ts +1 -1
- package/dist/crdt/storage.js +1 -1
- package/dist/crdt/structs.d.ts +15 -15
- package/dist/crdt/structs.js +8 -22
- package/dist/crdt/text/index/cache.js +4 -0
- package/dist/crdt/text/index/claims.js +12 -0
- package/dist/crdt/text/index/fold.d.ts +2 -3
- package/dist/crdt/text/index/fold.js +194 -44
- package/dist/crdt/text/index/layout.js +5 -8
- package/dist/crdt/text/index/placement.d.ts +26 -18
- package/dist/crdt/text/index/placement.js +404 -138
- package/dist/crdt/text/index/records.d.ts +3 -3
- package/dist/crdt/text/index/records.js +24 -31
- package/dist/crdt/text/index/shared.d.ts +11 -3
- package/dist/crdt/text/index/shared.js +12 -3
- package/dist/crdt/text/index/state.d.ts +21 -17
- package/dist/crdt/text/index/state.js +15 -17
- package/dist/crdt/text/index/streams.d.ts +10 -5
- package/dist/crdt/text/index/streams.js +78 -27
- package/dist/crdt/text/items.d.ts +10 -3
- package/dist/crdt/text/items.js +7 -0
- package/dist/crdt/text/model.d.ts +77 -62
- package/dist/crdt/text/model.js +84 -169
- package/dist/crdt/text/ranges.d.ts +1 -1
- package/dist/crdt/text/ranges.js +3 -3
- package/dist/crdt/text/rows.d.ts +13 -7
- package/dist/crdt/text/rows.js +24 -7
- package/dist/crdt/text/runs.d.ts +17 -12
- package/dist/crdt/text/runs.js +24 -7
- package/dist/crdt/text/streams.d.ts +26 -13
- package/dist/crdt/text/streams.js +41 -14
- package/dist/crdt/vendor/yjs/UPSTREAM.md +568 -21
- package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +194 -10
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
- package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +43 -7
- package/dist/crdt/vendor/yjs/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
- package/dist/crdt/vendor/yjs/src/structs/Item.js +610 -51
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
- package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
- package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
- package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
- package/dist/crdt/vendor/yjs/src/ynode.js +177 -12
- package/dist/edytor.svelte.js +4 -1
- package/dist/events/beforeInputCommands.js +2 -2
- package/dist/events/onPageHide.d.ts +3 -0
- package/dist/events/onPageHide.js +16 -0
- package/dist/export/forms.d.ts +25 -25
- package/dist/surface/observer.svelte.js +2 -2
- package/dist/surface/projector.svelte.d.ts +15 -3
- package/dist/surface/projector.svelte.js +59 -8
- package/package.json +2 -2
- package/dist/crdt/placement/rank.d.ts +0 -87
- package/dist/crdt/placement/rank.js +0 -332
- package/dist/crdt/placement/source.d.ts +0 -43
- package/dist/crdt/placement/source.js +0 -66
- package/dist/crdt/text/delete-index.d.ts +0 -129
- package/dist/crdt/text/delete-index.js +0 -365
- package/dist/crdt/text/deletes.d.ts +0 -76
- package/dist/crdt/text/deletes.js +0 -362
- package/dist/crdt/text/index/anchored.d.ts +0 -17
- package/dist/crdt/text/index/anchored.js +0 -197
|
@@ -1,38 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Placement
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Placement reads over the sequences (fork patch YP22): which block a home
|
|
3
|
+
* slot belongs to, each block's display edges (the sequences that can show
|
|
4
|
+
* its slot: its live move records', strongest first, then its parent's), the
|
|
5
|
+
* places of every sequence, and the cycle walk. Which edge a block takes is
|
|
6
|
+
* the index's acceptance (`text/index/placement.ts` `settle`). Pure over
|
|
7
|
+
* replicated state.
|
|
6
8
|
*/
|
|
7
|
-
import type { EngineNode } from '../engine-api.js';
|
|
9
|
+
import type { EngineDoc, EngineNode } from '../engine-api.js';
|
|
10
|
+
import { isRecord, type Entry, type SeqItem, type Stamp, type Unit } from './slots.js';
|
|
8
11
|
import { type Owner } from '../text/model.js';
|
|
9
|
-
import type { BlockId
|
|
10
|
-
/**
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* block
|
|
37
|
-
|
|
38
|
-
|
|
12
|
+
import type { BlockId } from './model.js';
|
|
13
|
+
/** A block's home slot: the owner of the sequence holding it (`null`: the root) and its unit. */
|
|
14
|
+
export type SlotRef = Unit & {
|
|
15
|
+
parent: BlockId | null;
|
|
16
|
+
};
|
|
17
|
+
/** The stamp of a block with no slot: below every edge (it shows at the root's tail). */
|
|
18
|
+
export declare const NO_SLOT: Stamp;
|
|
19
|
+
/**
|
|
20
|
+
* A block's display edge: the owner of the sequence that shows its slot
|
|
21
|
+
* (`null`: the root; `undefined`: a node that is no block of the document),
|
|
22
|
+
* the record it shows through (`null`: at home), the edge's stamp, and the
|
|
23
|
+
* item of its displaying unit (the slot's, or the record).
|
|
24
|
+
*/
|
|
25
|
+
export type Edge = {
|
|
26
|
+
parent: BlockId | null | undefined;
|
|
27
|
+
via: SeqItem | null;
|
|
28
|
+
stamp: Stamp;
|
|
29
|
+
item: SeqItem;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The block a home slot `{ b: id }` written by `client` belongs to: `id`
|
|
33
|
+
* when its registry value is `client`'s, else the losing incarnation
|
|
34
|
+
* `client` wrote (`id.same.concurrent`: each creation wrote its own slot),
|
|
35
|
+
* else `id` (a lone slot).
|
|
36
|
+
*/
|
|
37
|
+
export declare const slotOwner: (registry: EngineNode, kept: (item: unknown) => boolean, id: BlockId, client: number) => BlockId;
|
|
38
|
+
/**
|
|
39
|
+
* The block whose sequence `node` is (`null`: the registry root), or
|
|
40
|
+
* `undefined` for a node that is no block of the document (an atom, a data
|
|
41
|
+
* root, a seed's losing incarnation).
|
|
42
|
+
*/
|
|
43
|
+
export declare const ownerOfNode: (registry: EngineNode, kept: (item: unknown) => boolean, node: EngineNode) => BlockId | null | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The owner of the sequence block `home`'s slot shows in as the engine
|
|
46
|
+
* stands — its mover's, else its home's (`undefined`: gone, not arrived, or
|
|
47
|
+
* a node that is no block): the strongest edge's parent without building the
|
|
48
|
+
* edges (`candidatesOf`).
|
|
49
|
+
*/
|
|
50
|
+
export declare const edgeParentOf: (doc: EngineDoc, registry: EngineNode, kept: (item: unknown) => boolean, home: SlotRef) => BlockId | null | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* The display edges block `home`'s slot can take, the strongest first: one per
|
|
53
|
+
* live move record naming it, by descending `(priority, client, clock)` (the
|
|
54
|
+
* engine keeps a unit's movers in that order; the first is its mover), then
|
|
55
|
+
* its home slot. A block shows at the
|
|
56
|
+
* first one that closes no cycle (the later move wins); when that is not the
|
|
57
|
+
* first, it is back where it stood before the move a cycle dropped: the
|
|
58
|
+
* record before it, else home. `[]` when the slot is gone or has not arrived.
|
|
59
|
+
* `at`: the slot's item when the caller read it already (a rebuild's
|
|
60
|
+
* collection: no store lookup).
|
|
61
|
+
*/
|
|
62
|
+
export declare const candidatesOf: (doc: EngineDoc, registry: EngineNode, kept: (item: unknown) => boolean, home: SlotRef, at?: SeqItem) => Edge[];
|
|
63
|
+
/**
|
|
64
|
+
* The block slots that can show in one sequence, in its order, as the index
|
|
65
|
+
* reads it: a slot item's units (moved away or not: a block a cycle sends back
|
|
66
|
+
* home shows there), a live record's units in the order it names them (a
|
|
67
|
+
* block a cycle sends back to an older record shows at it). Which block shows
|
|
68
|
+
* at a position is the index's acceptance; these are the places.
|
|
69
|
+
*/
|
|
70
|
+
export declare const positionsOfItem: (doc: EngineDoc, it: SeqItem) => Entry[];
|
|
71
|
+
/**
|
|
72
|
+
* The places before unit `home` shown at `edge`, nearest first: the units its
|
|
73
|
+
* record names before it (or its slot item's earlier units), then each item to
|
|
74
|
+
* the left, last place first. The index's order keys walk them to the nearest
|
|
75
|
+
* block that shows where it was found.
|
|
76
|
+
*/
|
|
77
|
+
export declare function slotsBefore(doc: EngineDoc, edge: Edge, home: Unit): Generator<Entry>;
|
|
78
|
+
/** Where a cycle check walks: the display parent of `at` through the accepted edges. */
|
|
79
|
+
type Accepted = ReadonlyMap<BlockId, {
|
|
80
|
+
parent: BlockId | null;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* The blocks on the cycle a display edge `block → parent` would close
|
|
84
|
+
* through `accepted` edges and the claim owners (`ownerOf`: a live parent's
|
|
85
|
+
* edge is its owner's, a deleted one is walked itself), or `null` when it
|
|
86
|
+
* closes none. The members are `block` and every block whose edge the walk
|
|
87
|
+
* followed.
|
|
88
|
+
*/
|
|
89
|
+
export declare const cycleThrough: (accepted: Accepted, ownerOf: (b: BlockId) => Owner, block: BlockId, parent: BlockId | null) => BlockId[] | null;
|
|
90
|
+
/** The order of the blocks at the root's tail (rejected or slotless): by their edge's stamp, then id. */
|
|
91
|
+
export declare const byTail: (a: {
|
|
92
|
+
id: BlockId;
|
|
93
|
+
stamp: Stamp;
|
|
94
|
+
}, b: {
|
|
95
|
+
id: BlockId;
|
|
96
|
+
stamp: Stamp;
|
|
97
|
+
}) => number;
|
|
98
|
+
/**
|
|
99
|
+
* The home slot of block `id` among the slots naming its registry id
|
|
100
|
+
* (`slots`: every `{ b }` slot by `b`): the one its writer wrote
|
|
101
|
+
* (`slotOwner`) — the lowest `(client, clock)` when several do (a redo's
|
|
102
|
+
* copy beside the original) — or `null`.
|
|
103
|
+
*/
|
|
104
|
+
export declare const homeOf: (registry: EngineNode, kept: (item: unknown) => boolean, slots: ReadonlyMap<BlockId, readonly SlotRef[]>, id: BlockId) => SlotRef | null;
|
|
105
|
+
/** The block slots of every sequence: by the id they name (with each one's item), and each sequence's places in order. */
|
|
106
|
+
export type Slots = {
|
|
107
|
+
slots: Map<BlockId, SlotRef[]>;
|
|
108
|
+
items: Map<SlotRef, SeqItem>;
|
|
109
|
+
places: Map<BlockId | null, Entry[]>;
|
|
110
|
+
};
|
|
111
|
+
/** Every registry node and its kept incarnations, with the block id each stands for (the sequences' owners). */
|
|
112
|
+
export declare const registryOwners: (registry: EngineNode, kept: (item: unknown) => boolean) => {
|
|
113
|
+
id: BlockId;
|
|
114
|
+
node: EngineNode;
|
|
115
|
+
}[];
|
|
116
|
+
/**
|
|
117
|
+
* Every block slot of every sequence (the root's, each block's), each item
|
|
118
|
+
* read once: the slots by the id they name (a slot item's units, moved away
|
|
119
|
+
* or not, none gone), and each sequence's places in order
|
|
120
|
+
* (`positionsOfItem`).
|
|
121
|
+
*/
|
|
122
|
+
export declare const collectSlots: (doc: EngineDoc, owners: Iterable<{
|
|
123
|
+
id: BlockId;
|
|
124
|
+
node: EngineNode;
|
|
125
|
+
}>) => Slots;
|
|
126
|
+
export { isRecord, type Stamp };
|
|
@@ -1,169 +1,203 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { isNodeLike, REGISTRY_KEY } from '../schema.js';
|
|
2
|
+
import { baseIdOf, incarnationId, incarnationsOf, LIVE_WRITERS } from '../incarnations.js';
|
|
3
|
+
import { attrItems, firstItem, itemOf } from '../structs.js';
|
|
4
|
+
import { cmpStamp, isBlockSlot, isRecord, itemAt, sameUnit, stampOf, unitsOf } from './slots.js';
|
|
5
|
+
import { DEAD } from '../text/model.js';
|
|
6
|
+
/** The stamp of a home slot's edge: below every record's. */
|
|
7
|
+
const HOME = 0;
|
|
8
|
+
/** The stamp of a block with no slot: below every edge (it shows at the root's tail). */
|
|
9
|
+
export const NO_SLOT = [-1, 0, 0];
|
|
4
10
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* carried by the `v` digit (each successive rehome mints below the last).
|
|
11
|
+
* The block a home slot `{ b: id }` written by `client` belongs to: `id`
|
|
12
|
+
* when its registry value is `client`'s, else the losing incarnation
|
|
13
|
+
* `client` wrote (`id.same.concurrent`: each creation wrote its own slot),
|
|
14
|
+
* else `id` (a lone slot).
|
|
10
15
|
*/
|
|
11
|
-
const
|
|
16
|
+
export const slotOwner = (registry, kept, id, client) => {
|
|
17
|
+
const current = attrItems(registry).get(id);
|
|
18
|
+
if (current === undefined || current.id.client === client)
|
|
19
|
+
return id;
|
|
20
|
+
for (let it = current.left; it !== null && it.parentSub === id; it = it.left)
|
|
21
|
+
if (it.id.client === client && it.id.client >= LIVE_WRITERS && kept(it))
|
|
22
|
+
return incarnationId(id, it);
|
|
23
|
+
return id;
|
|
24
|
+
};
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* undefined, that)` hits the `rSeg.v <= RANK_VMIN` guard and throws
|
|
17
|
-
* `RankSpaceExhausted` — reachable with ZERO boundary inserts (corpus
|
|
18
|
-
* seed 96). Minting `min[0].v - 1` keeps the documented "orphans rehome
|
|
19
|
-
* to the FRONT of the root" behavior while leaving digit headroom for
|
|
20
|
-
* legal inserts above the rehomed block.
|
|
21
|
-
*
|
|
22
|
-
* `min` = the current minimum rank among placements displaying at the
|
|
23
|
-
* root, `undefined` when the root list is empty (mint `{v:0}` — the same
|
|
24
|
-
* canonical first key `rankBetween(undefined, undefined)` emits). At the
|
|
25
|
-
* absolute floor (`min[0].v` already `RANK_VMIN` — unreachable through
|
|
26
|
-
* any legal write; nothing sorts below it) join `min`'s tie instead of
|
|
27
|
-
* minting an unencodable digit.
|
|
26
|
+
* The block whose sequence `node` is (`null`: the registry root), or
|
|
27
|
+
* `undefined` for a node that is no block of the document (an atom, a data
|
|
28
|
+
* root, a seed's losing incarnation).
|
|
28
29
|
*/
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
35
|
-
|
|
30
|
+
export const ownerOfNode = (registry, kept, node) => {
|
|
31
|
+
if (node === registry)
|
|
32
|
+
return null;
|
|
33
|
+
const it = itemOf(node);
|
|
34
|
+
if (it === null || it.parent !== registry || typeof it.parentSub !== 'string')
|
|
35
|
+
return undefined;
|
|
36
|
+
if (!it.deleted)
|
|
37
|
+
return it.parentSub;
|
|
38
|
+
if (!kept(it) || it.id.client < LIVE_WRITERS)
|
|
39
|
+
return undefined;
|
|
40
|
+
return incarnationId(it.parentSub, it);
|
|
36
41
|
};
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The owner of the sequence block `home`'s slot shows in as the engine
|
|
44
|
+
* stands — its mover's, else its home's (`undefined`: gone, not arrived, or
|
|
45
|
+
* a node that is no block): the strongest edge's parent without building the
|
|
46
|
+
* edges (`candidatesOf`).
|
|
47
|
+
*/
|
|
48
|
+
export const edgeParentOf = (doc, registry, kept, home) => {
|
|
49
|
+
const item = itemAt(doc, home);
|
|
50
|
+
if (item === null || item.gone === true)
|
|
51
|
+
return undefined;
|
|
52
|
+
const r = item.mover ?? null;
|
|
53
|
+
return r === null || r.deleted
|
|
54
|
+
? home.parent
|
|
55
|
+
: ownerOfNode(registry, kept, r.parent);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The display edges block `home`'s slot can take, the strongest first: one per
|
|
59
|
+
* live move record naming it, by descending `(priority, client, clock)` (the
|
|
60
|
+
* engine keeps a unit's movers in that order; the first is its mover), then
|
|
61
|
+
* its home slot. A block shows at the
|
|
62
|
+
* first one that closes no cycle (the later move wins); when that is not the
|
|
63
|
+
* first, it is back where it stood before the move a cycle dropped: the
|
|
64
|
+
* record before it, else home. `[]` when the slot is gone or has not arrived.
|
|
65
|
+
* `at`: the slot's item when the caller read it already (a rebuild's
|
|
66
|
+
* collection: no store lookup).
|
|
67
|
+
*/
|
|
68
|
+
export const candidatesOf = (doc, registry, kept, home, at) => {
|
|
69
|
+
const item = at ?? itemAt(doc, home);
|
|
70
|
+
if (item === null || item.gone === true)
|
|
71
|
+
return [];
|
|
72
|
+
const out = item.movers
|
|
73
|
+
?.filter((r) => !r.deleted)
|
|
74
|
+
.map((r) => ({
|
|
75
|
+
parent: ownerOfNode(registry, kept, r.parent),
|
|
76
|
+
via: r,
|
|
77
|
+
stamp: stampOf(r),
|
|
78
|
+
item: r
|
|
79
|
+
})) ?? [];
|
|
80
|
+
out.push({ parent: home.parent, via: null, stamp: [HOME, home.c, home.k], item });
|
|
81
|
+
return out;
|
|
60
82
|
};
|
|
61
83
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* replicated state, writes nothing.
|
|
68
|
-
*
|
|
69
|
-
* The relation kept acyclic is the COMPOSED display-parent relation
|
|
70
|
-
* `b ↦ owner(parent(b))` (see `displayParentOf`): a placement parent that
|
|
71
|
-
* was merged away resolves to its claim owner, so the raw `pl.parent`
|
|
72
|
-
* graph being acyclic is NOT sufficient — a merge claim can redirect a
|
|
73
|
-
* display edge back into the block's own subtree (e.g. `A` merged into
|
|
74
|
-
* `B` while `B` sits under `A`, or `del` resurrection re-arming a claim).
|
|
75
|
-
* Because the claim-owner map is computed independently of placements
|
|
76
|
-
* (claims live on `claims` lists), the acceptance test composes the
|
|
77
|
-
* two: the candidate's tentative display edge is `ownerOf(p)` — a live
|
|
78
|
-
* self-owned block or `null` (root). A deleted parent is no sink: its
|
|
79
|
-
* unmarked children display in its slot (read-time promotion,
|
|
80
|
-
* {@link displaySlotOf}), so the walk passes through it along its own
|
|
81
|
-
* accepted placement.
|
|
82
|
-
*
|
|
83
|
-
* `ownerOf` is injectable so callers can share an ownership context they
|
|
84
|
-
* already computed; standalone callers get the map derived from the
|
|
85
|
-
* block records (pure over replicated state — claims are `claims` items).
|
|
84
|
+
* The block slots that can show in one sequence, in its order, as the index
|
|
85
|
+
* reads it: a slot item's units (moved away or not: a block a cycle sends back
|
|
86
|
+
* home shows there), a live record's units in the order it names them (a
|
|
87
|
+
* block a cycle sends back to an older record shows at it). Which block shows
|
|
88
|
+
* at a position is the index's acceptance; these are the places.
|
|
86
89
|
*/
|
|
87
|
-
export const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
export const positionsOfItem = (doc, it) => unitsOf(doc, it, (_, value) => isBlockSlot(value));
|
|
91
|
+
/**
|
|
92
|
+
* The places before unit `home` shown at `edge`, nearest first: the units its
|
|
93
|
+
* record names before it (or its slot item's earlier units), then each item to
|
|
94
|
+
* the left, last place first. The index's order keys walk them to the nearest
|
|
95
|
+
* block that shows where it was found.
|
|
96
|
+
*/
|
|
97
|
+
export function* slotsBefore(doc, edge, home) {
|
|
98
|
+
const own = positionsOfItem(doc, edge.item);
|
|
99
|
+
let at = own.findIndex((e) => sameUnit(e.unit, home));
|
|
100
|
+
if (at < 0)
|
|
101
|
+
at = edge.via === null ? 0 : own.length;
|
|
102
|
+
for (let j = at - 1; j >= 0; j--)
|
|
103
|
+
yield own[j];
|
|
104
|
+
for (let p = edge.item.left; p !== null; p = p.left) {
|
|
105
|
+
const list = positionsOfItem(doc, p);
|
|
106
|
+
for (let j = list.length - 1; j >= 0; j--)
|
|
107
|
+
yield list[j];
|
|
92
108
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The blocks on the cycle a display edge `block → parent` would close
|
|
112
|
+
* through `accepted` edges and the claim owners (`ownerOf`: a live parent's
|
|
113
|
+
* edge is its owner's, a deleted one is walked itself), or `null` when it
|
|
114
|
+
* closes none. The members are `block` and every block whose edge the walk
|
|
115
|
+
* followed.
|
|
116
|
+
*/
|
|
117
|
+
export const cycleThrough = (accepted, ownerOf, block, parent) => {
|
|
118
|
+
const members = [block];
|
|
119
|
+
for (let cur = parent, steps = 0; cur !== null; steps++) {
|
|
120
|
+
const o = ownerOf(cur);
|
|
121
|
+
const at = o === DEAD ? cur : o;
|
|
122
|
+
if (at === block)
|
|
123
|
+
return members;
|
|
124
|
+
const next = accepted.get(at);
|
|
125
|
+
if (next === undefined || steps > accepted.size)
|
|
126
|
+
return null;
|
|
127
|
+
members.push(at);
|
|
128
|
+
cur = next.parent;
|
|
97
129
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
/** The order of the blocks at the root's tail (rejected or slotless): by their edge's stamp, then id. */
|
|
133
|
+
export const byTail = (a, b) => cmpStamp(a.stamp, b.stamp) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
134
|
+
/**
|
|
135
|
+
* The home slot of block `id` among the slots naming its registry id
|
|
136
|
+
* (`slots`: every `{ b }` slot by `b`): the one its writer wrote
|
|
137
|
+
* (`slotOwner`) — the lowest `(client, clock)` when several do (a redo's
|
|
138
|
+
* copy beside the original) — or `null`.
|
|
139
|
+
*/
|
|
140
|
+
export const homeOf = (registry, kept, slots, id) => {
|
|
141
|
+
const base = baseIdOf(id);
|
|
142
|
+
let best = null;
|
|
143
|
+
for (const ref of slots.get(base) ?? []) {
|
|
144
|
+
if (slotOwner(registry, kept, base, ref.c) !== id)
|
|
102
145
|
continue;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
p = null; // parent never integrated → root
|
|
106
|
-
if (p !== null) {
|
|
107
|
-
// Reject the candidate iff the block already is a display
|
|
108
|
-
// ancestor of `p` through accepted edges: a live parent's edge
|
|
109
|
-
// is its owner's, a deleted one is walked itself (its children
|
|
110
|
-
// take its slot). Accepted edges are acyclic by induction, so
|
|
111
|
-
// the walk always terminates.
|
|
112
|
-
let cyclic = false;
|
|
113
|
-
for (let cur = p; cur !== null;) {
|
|
114
|
-
const o = owner(cur);
|
|
115
|
-
const at = o === DEAD ? cur : o;
|
|
116
|
-
if (at === cand.blockId) {
|
|
117
|
-
cyclic = true;
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
const cp = accepted.get(at);
|
|
121
|
-
if (cp === undefined)
|
|
122
|
-
break; // edge not yet accepted → cannot cycle
|
|
123
|
-
cur = cp.parent;
|
|
124
|
-
}
|
|
125
|
-
if (cyclic)
|
|
126
|
-
continue; // try this block's next candidate
|
|
127
|
-
}
|
|
128
|
-
accepted.set(cand.blockId, { parent: p, rank: cand.r });
|
|
146
|
+
if (best === null || ref.c < best.c || (ref.c === best.c && ref.k < best.k))
|
|
147
|
+
best = ref;
|
|
129
148
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
if (accepted.has(id))
|
|
149
|
+
return best;
|
|
150
|
+
};
|
|
151
|
+
/** Every registry node and its kept incarnations, with the block id each stands for (the sequences' owners). */
|
|
152
|
+
export const registryOwners = (registry, kept) => {
|
|
153
|
+
const owners = [];
|
|
154
|
+
// The registry's live entries, read from its items (one pass, no attr lookups).
|
|
155
|
+
for (const [id, item] of attrItems(registry)) {
|
|
156
|
+
if (item.deleted)
|
|
139
157
|
continue;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
rehomed.push(rec);
|
|
145
|
-
}
|
|
158
|
+
const node = item.content.type;
|
|
159
|
+
if (isNodeLike(node))
|
|
160
|
+
owners.push({ id, node });
|
|
161
|
+
owners.push(...incarnationsOf(registry, id, kept));
|
|
146
162
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
return owners;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Every block slot of every sequence (the root's, each block's), each item
|
|
167
|
+
* read once: the slots by the id they name (a slot item's units, moved away
|
|
168
|
+
* or not, none gone), and each sequence's places in order
|
|
169
|
+
* (`positionsOfItem`).
|
|
170
|
+
*/
|
|
171
|
+
export const collectSlots = (doc, owners) => {
|
|
172
|
+
const slots = new Map();
|
|
173
|
+
const items = new Map();
|
|
174
|
+
const places = new Map();
|
|
175
|
+
const read = (owner, node) => {
|
|
176
|
+
let list;
|
|
177
|
+
for (let it = firstItem(node); it !== null; it = it.right) {
|
|
178
|
+
const here = positionsOfItem(doc, it);
|
|
179
|
+
if (here.length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
if (list === undefined)
|
|
182
|
+
places.set(owner, (list = []));
|
|
183
|
+
for (const e of here) {
|
|
184
|
+
list.push(e);
|
|
185
|
+
if (e.via !== null)
|
|
186
|
+
continue;
|
|
187
|
+
const ref = { parent: owner, c: e.unit.c, k: e.unit.k };
|
|
188
|
+
const b = e.value.b;
|
|
189
|
+
const named = slots.get(b);
|
|
190
|
+
if (named === undefined)
|
|
191
|
+
slots.set(b, [ref]);
|
|
192
|
+
else
|
|
193
|
+
named.push(ref);
|
|
194
|
+
items.set(ref, it);
|
|
195
|
+
}
|
|
166
196
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
197
|
+
};
|
|
198
|
+
read(null, doc.get(REGISTRY_KEY));
|
|
199
|
+
for (const { id, node } of owners)
|
|
200
|
+
read(id, node);
|
|
201
|
+
return { slots, items, places };
|
|
169
202
|
};
|
|
203
|
+
export { isRecord };
|