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,332 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rank keys for the placement model — a Logoot-style dense ordering.
|
|
3
|
-
*
|
|
4
|
-
* A rank is a non-empty sequence of *segments* `(v, t)`:
|
|
5
|
-
*
|
|
6
|
-
* - `v` — a signed digit in `[VMIN, VMAX]` (±2^40). There is always room
|
|
7
|
-
* between two distinct digits (`rv - lv > 1`); when there is not, the
|
|
8
|
-
* generator descends one level instead of renumbering.
|
|
9
|
-
* - `t` — the allocator's `doc.clientID` (uint53), the *tiebreak* that makes
|
|
10
|
-
* concurrent allocations of the same digit path distinct, or none
|
|
11
|
-
* (`Infinity`: sorts above every client) on a segment that only leads to
|
|
12
|
-
* deeper ones. The last segment always carries one (schema
|
|
13
|
-
* generation 5): a rank's own client sits on its last segment, and a
|
|
14
|
-
* copied segment keeps its tie only where the order needs it (between two
|
|
15
|
-
* keys that differ only by their ties).
|
|
16
|
-
*
|
|
17
|
-
* Encoding is variable-length and order-preserving, so the persisted rank
|
|
18
|
-
* is a plain string and the comparator is `a < b` (CRDT study 2026-10;
|
|
19
|
-
* up to generation 4 every segment was 16 characters, 9 of them a tie):
|
|
20
|
-
*
|
|
21
|
-
* - `v` → a length character (`'H'` for zero, `'H' + k` for a positive
|
|
22
|
-
* digit of `k` base-64 places, `'H' - k` for a negative one, its places
|
|
23
|
-
* complemented) and its places — small digits are short;
|
|
24
|
-
* - a tie → `'!'`, then `t` as a digit (`'!'` sorts below every length
|
|
25
|
-
* character, so a tied segment sorts before the same digit leading on);
|
|
26
|
-
* - rank = segments concatenated; a prefix sorts before its extension, as
|
|
27
|
-
* string `<` does.
|
|
28
|
-
*
|
|
29
|
-
* Allocation (`rankBetween(left, right, clientId)`):
|
|
30
|
-
*
|
|
31
|
-
* - gap at level `i` (`rv - lv > 1`): emit `lv + 1 + floor(rand·(rv-lv-1))`,
|
|
32
|
-
* own tie — random-in-gap keeps sequential inserts shallow and concurrent
|
|
33
|
-
* same-gap inserts deterministic-distinct.
|
|
34
|
-
* - adjacent or equal digits (`rv - lv ≤ 1`): the left digit or the right
|
|
35
|
-
* one with the allocator's own tie when that tie sorts between theirs;
|
|
36
|
-
* else copy the left segment and descend — untied when that already sorts
|
|
37
|
-
* below the right bound (the result is then above left and below right,
|
|
38
|
-
* and the next level is free), tied otherwise.
|
|
39
|
-
* - left exhausted/open: emit `rSeg.v - 1` (boundary-minus). Sequential
|
|
40
|
-
* prepends stay at depth 1; the bound can only underflow after ~2^40
|
|
41
|
-
* consecutive boundary pushes — documented unreachable, guarded by a
|
|
42
|
-
* descriptive throw.
|
|
43
|
-
*
|
|
44
|
-
* No floating point, no global renumbering, no wall clocks.
|
|
45
|
-
*/
|
|
46
|
-
/**
|
|
47
|
-
* Order-preserving alphabet: charCode order equals numeric order.
|
|
48
|
-
* `-`(45) < `0-9`(48-57) < `A-Z`(65-90) < `_`(95) < `a-z`(97-122) — 64 chars,
|
|
49
|
-
* strictly ascending charCodes, all safe inside a plain JSON string.
|
|
50
|
-
*/
|
|
51
|
-
const ALPHABET = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
|
|
52
|
-
const BASE = 64;
|
|
53
|
-
/** Digit range. VMIN = -2^40, VMAX = 2^40 - 1. */
|
|
54
|
-
export const RANK_VMIN = -(2 ** 40);
|
|
55
|
-
export const RANK_VMAX = 2 ** 40 - 1;
|
|
56
|
-
/** The length character of zero; a digit of `k` places is `ZERO ± k`. */
|
|
57
|
-
const ZERO = 'H'.charCodeAt(0);
|
|
58
|
-
/** Most places a digit or a tie takes (uint53 → 9 base-64 places). */
|
|
59
|
-
const MAX_PLACES = 9;
|
|
60
|
-
/** Marks a segment's tie: below every length character. */
|
|
61
|
-
const TIE = '!';
|
|
62
|
-
/** Thrown when rank generation would need a digit below VMIN (unreachable). */
|
|
63
|
-
export class RankSpaceExhausted extends Error {
|
|
64
|
-
constructor() {
|
|
65
|
-
super('rank space exhausted at digit minimum — requires ~2^40 sequential boundary inserts; unreachable in practice');
|
|
66
|
-
this.name = 'RankSpaceExhausted';
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/** A rank this module cannot encode, a malformed rank, or bounds out of order (a caller's error). */
|
|
70
|
-
const bad = (what) => {
|
|
71
|
-
throw new Error(`rank: ${what}`);
|
|
72
|
-
};
|
|
73
|
-
/** `n` (≥ 0) as exactly `width` base-64 places. */
|
|
74
|
-
const places = (n, width) => {
|
|
75
|
-
let out = '';
|
|
76
|
-
for (let i = 0; i < width; i++) {
|
|
77
|
-
out = ALPHABET[n % BASE] + out;
|
|
78
|
-
n = Math.floor(n / BASE);
|
|
79
|
-
}
|
|
80
|
-
if (n > 0)
|
|
81
|
-
bad('overflow');
|
|
82
|
-
return out;
|
|
83
|
-
};
|
|
84
|
-
/** How many base-64 places `m` (≥ 1) takes. */
|
|
85
|
-
const width = (m) => {
|
|
86
|
-
let k = 1;
|
|
87
|
-
for (let x = Math.floor(m / BASE); x > 0; x = Math.floor(x / BASE))
|
|
88
|
-
k++;
|
|
89
|
-
return k;
|
|
90
|
-
};
|
|
91
|
-
/** A signed integer as a self-delimiting, order-preserving string. */
|
|
92
|
-
const encInt = (v) => {
|
|
93
|
-
if (!Number.isSafeInteger(v))
|
|
94
|
-
bad('digit');
|
|
95
|
-
if (v === 0)
|
|
96
|
-
return 'H';
|
|
97
|
-
const m = Math.abs(v);
|
|
98
|
-
const k = width(m);
|
|
99
|
-
if (k > MAX_PLACES)
|
|
100
|
-
bad('overflow');
|
|
101
|
-
if (v > 0)
|
|
102
|
-
return String.fromCharCode(ZERO + k) + places(m, k);
|
|
103
|
-
// Complemented: a greater magnitude sorts first.
|
|
104
|
-
return String.fromCharCode(ZERO - k) + places(BASE ** k - 1 - m, k);
|
|
105
|
-
};
|
|
106
|
-
/** The integer at `s[i]`, and where it ends. */
|
|
107
|
-
const decInt = (s, i) => {
|
|
108
|
-
const k = s.charCodeAt(i) - ZERO;
|
|
109
|
-
const n = Math.abs(k);
|
|
110
|
-
if (Number.isNaN(k) || n > MAX_PLACES || i + 1 + n > s.length)
|
|
111
|
-
bad('malformed');
|
|
112
|
-
let x = 0;
|
|
113
|
-
for (let j = i + 1; j <= i + n; j++) {
|
|
114
|
-
const d = ALPHABET.indexOf(s[j]);
|
|
115
|
-
if (d < 0)
|
|
116
|
-
bad('malformed');
|
|
117
|
-
x = x * BASE + d;
|
|
118
|
-
}
|
|
119
|
-
const v = k === 0 ? 0 : k > 0 ? x : -(BASE ** n - 1 - x);
|
|
120
|
-
return [v, i + 1 + n];
|
|
121
|
-
};
|
|
122
|
-
/** Decode a rank string into its segment array (`t: Infinity` where a segment has no tie). */
|
|
123
|
-
export const decodeRank = (rank) => {
|
|
124
|
-
if (rank.length === 0)
|
|
125
|
-
bad('malformed');
|
|
126
|
-
const segs = [];
|
|
127
|
-
for (let i = 0; i < rank.length;) {
|
|
128
|
-
const [v, j] = decInt(rank, i);
|
|
129
|
-
let t = Infinity;
|
|
130
|
-
i = j;
|
|
131
|
-
if (rank[i] === TIE)
|
|
132
|
-
[t, i] = decInt(rank, i + 1);
|
|
133
|
-
segs.push({ v, t });
|
|
134
|
-
}
|
|
135
|
-
if (segs[segs.length - 1].t === Infinity)
|
|
136
|
-
bad('malformed');
|
|
137
|
-
return segs;
|
|
138
|
-
};
|
|
139
|
-
/** Encode segments back to the persisted string form. */
|
|
140
|
-
export const encodeRank = (segs) => {
|
|
141
|
-
let out = '';
|
|
142
|
-
for (const { v, t } of segs) {
|
|
143
|
-
if (v < RANK_VMIN || v > RANK_VMAX)
|
|
144
|
-
bad('overflow');
|
|
145
|
-
out += encInt(v);
|
|
146
|
-
if (t !== Infinity)
|
|
147
|
-
out += TIE + encInt(t);
|
|
148
|
-
}
|
|
149
|
-
return out;
|
|
150
|
-
};
|
|
151
|
-
const tieCmp = (a, b) => (a === b ? 0 : a < b ? -1 : 1);
|
|
152
|
-
const segCmp = (a, b) => a.v - b.v || tieCmp(a.t, b.t);
|
|
153
|
-
/**
|
|
154
|
-
* Window for open-end (append-at-tail) allocation: emit at most `lv + APPEND_WINDOW`
|
|
155
|
-
* rather than anywhere in `(lv, VMAX]`. Sequential appends advance the tail
|
|
156
|
-
* digit by ≤2^16, so the digit space only saturates after ~2^24 tail-appends;
|
|
157
|
-
* a uniform-in-`(lv, VMAX]` pick would instead halve the headroom every step
|
|
158
|
-
* and cascade into ever-deeper levels after ~40 appends. Concurrent tail
|
|
159
|
-
* inserts may collide on `v` — the `t` tiebreak orders them.
|
|
160
|
-
*/
|
|
161
|
-
const APPEND_WINDOW = 2 ** 16;
|
|
162
|
-
/**
|
|
163
|
-
* Allocate a rank strictly between `left` and `right` (either may be
|
|
164
|
-
* `undefined` for the open end of a sibling list). `clientId` is the
|
|
165
|
-
* allocating doc's clientID; `rand` supplies the in-gap pick (defaults to
|
|
166
|
-
* `Math.random`, tests inject a seeded stream).
|
|
167
|
-
*
|
|
168
|
-
* Guaranteed: `left < result < right` for any `left < right`, including
|
|
169
|
-
* equal-prefix/concurrent-collision inputs — the tiebreak dimension makes a
|
|
170
|
-
* strictly-between key always exist (modulo the documented VMIN bound).
|
|
171
|
-
*/
|
|
172
|
-
export const rankBetween = (left, right, clientId, rand = Math.random, compact = true) => {
|
|
173
|
-
const L = left === undefined ? [] : decodeRank(left);
|
|
174
|
-
const R = right === undefined ? [] : decodeRank(right);
|
|
175
|
-
if (left !== undefined && right !== undefined && left >= right)
|
|
176
|
-
bad('left >= right');
|
|
177
|
-
const rightOpen = right === undefined;
|
|
178
|
-
const path = [];
|
|
179
|
-
// `locked`: the emitted prefix already sorts below the right bound.
|
|
180
|
-
let locked = false;
|
|
181
|
-
// `above`: the emitted prefix already sorts above the left bound.
|
|
182
|
-
let above = false;
|
|
183
|
-
// `floor` = right segments at the digit minimum were copied past the left
|
|
184
|
-
// bound's end (a promoted slot's separator): the result equals right so far.
|
|
185
|
-
let floor = false;
|
|
186
|
-
const own = (v) => encodeRank([...path, { v, t: clientId }]);
|
|
187
|
-
for (let i = 0;; i++) {
|
|
188
|
-
const lSeg = above ? undefined : L[i];
|
|
189
|
-
// Locked: the right bound no longer constrains any deeper level
|
|
190
|
-
// (descending past it grew ranks linearly under concentrated edits).
|
|
191
|
-
const rSeg = rightOpen || locked ? undefined : R[i];
|
|
192
|
-
if (lSeg === undefined) {
|
|
193
|
-
// Left bound exhausted (or passed): extending the prefix keeps the
|
|
194
|
-
// result above it. Only the right bound constrains the digit.
|
|
195
|
-
if (rSeg === undefined) {
|
|
196
|
-
if (floor)
|
|
197
|
-
throw new RankSpaceExhausted();
|
|
198
|
-
return own(0);
|
|
199
|
-
}
|
|
200
|
-
if (rSeg.v <= RANK_VMIN) {
|
|
201
|
-
// No digit below: copy it and descend — below a promoted block
|
|
202
|
-
// (`slot + separator + rank`) there is room under its own rank.
|
|
203
|
-
floor = true;
|
|
204
|
-
path.push(rSeg);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
return own(rSeg.v - 1);
|
|
208
|
-
}
|
|
209
|
-
if (rSeg === undefined) {
|
|
210
|
-
// Right bound ended while left continues: valid only when `locked`
|
|
211
|
-
// already holds (an earlier copied segment was < the right's). If not,
|
|
212
|
-
// right is a proper prefix of left — i.e. right < left, caller error.
|
|
213
|
-
if (!rightOpen && !locked)
|
|
214
|
-
bad('left >= right');
|
|
215
|
-
// No right bound at this level (open, or `locked` below it): emit
|
|
216
|
-
// within the bounded append window — dense growth, and one more
|
|
217
|
-
// level per exhausted gap, not one per insert (SW12-crdt-2).
|
|
218
|
-
const span = Math.min(APPEND_WINDOW, RANK_VMAX - lSeg.v);
|
|
219
|
-
if (span <= 0) {
|
|
220
|
-
path.push(lSeg); // digit space exhausted at this level — descend
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
return own(lSeg.v + 1 + Math.floor(rand() * span));
|
|
224
|
-
}
|
|
225
|
-
if (rSeg.v - lSeg.v > 1) {
|
|
226
|
-
// Gap at this level: any v in (lv, rv) is > left and < right.
|
|
227
|
-
return own(lSeg.v + 1 + Math.floor(rand() * (rSeg.v - lSeg.v - 1)));
|
|
228
|
-
}
|
|
229
|
-
// rv - lv ≤ 1. Compact: the left digit with our tie, or the right
|
|
230
|
-
// one, when it sorts between; else the left digit untied when that
|
|
231
|
-
// already sorts below the right bound. A run's member (`rankAfter`) is
|
|
232
|
-
// never compact: a peer's rank of the same digit and another tie would
|
|
233
|
-
// sort between two of its members.
|
|
234
|
-
const mineL = { v: lSeg.v, t: clientId };
|
|
235
|
-
if (compact && segCmp(lSeg, mineL) < 0 && segCmp(mineL, rSeg) < 0)
|
|
236
|
-
return own(lSeg.v);
|
|
237
|
-
const mineR = { v: rSeg.v, t: clientId };
|
|
238
|
-
if (compact && rSeg.v > lSeg.v && segCmp(mineR, rSeg) < 0)
|
|
239
|
-
return own(rSeg.v);
|
|
240
|
-
// Copy the left digit and descend: untied when compact and that
|
|
241
|
-
// already sorts below the right bound (then above the left one too,
|
|
242
|
-
// unless its segment was untied as well), else with its tie.
|
|
243
|
-
const bare = { v: lSeg.v, t: Infinity };
|
|
244
|
-
if (compact && segCmp(bare, rSeg) < 0) {
|
|
245
|
-
path.push(bare);
|
|
246
|
-
locked = true;
|
|
247
|
-
if (lSeg.t !== Infinity)
|
|
248
|
-
above = true;
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const c = segCmp(lSeg, rSeg);
|
|
252
|
-
if (c < 0)
|
|
253
|
-
locked = true;
|
|
254
|
-
else if (c > 0 && !locked) {
|
|
255
|
-
// Never locked and left's segment exceeds right's at the first
|
|
256
|
-
// differing level → left > right (invalid bounds — caller error).
|
|
257
|
-
bad('left >= right');
|
|
258
|
-
}
|
|
259
|
-
path.push(lSeg);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
/** The base of the run band: a run member's digit lies just above it. */
|
|
263
|
-
export const RANK_RUN = -(2 ** 30);
|
|
264
|
-
/**
|
|
265
|
-
* A run member's digit lies in `(RANK_RUN, RANK_RUN + RUN_BAND)`: below every
|
|
266
|
-
* digit a plain rank extends a prefix with (`0`, an append's), far above the
|
|
267
|
-
* digit minimum. A run opens at {@link RUN_START}, the middle of the band, so
|
|
268
|
-
* its members can be inserted before and after one another.
|
|
269
|
-
*/
|
|
270
|
-
const RUN_BAND = 2 ** 29;
|
|
271
|
-
const RUN_START = RANK_RUN + 2 ** 28;
|
|
272
|
-
const inRun = (v) => v > RANK_RUN && v < RANK_RUN + RUN_BAND;
|
|
273
|
-
/**
|
|
274
|
-
* A rank in `(left, right)` for an insert right after `left` — one rule for
|
|
275
|
-
* array items (`crdt/data.ts`) and inserted blocks (`order.insert.run`).
|
|
276
|
-
* When `client` made `left`, the rank is in the client's run after it: `left`
|
|
277
|
-
* itself as the prefix, then one segment of the run band — or, when `left`
|
|
278
|
-
* is already a member of the client's run, that run's prefix and a member
|
|
279
|
-
* between `left` and the next one. So what one client inserts after its own
|
|
280
|
-
* items stays together whatever a peer inserts in that gap meanwhile: a
|
|
281
|
-
* peer's rank there is either no extension of the run's prefix (it sorts
|
|
282
|
-
* after the whole run) or one whose next digit is above the band (YATA's
|
|
283
|
-
* origin rule, as `Y.Array` keeps an insert after its origin). Elsewhere, or
|
|
284
|
-
* when the run rank would not sort below `right`, a plain {@link rankBetween}.
|
|
285
|
-
*/
|
|
286
|
-
export const rankAfter = (left, right, client, rand = Math.random) => {
|
|
287
|
-
const plain = () => rankBetween(left, right, client, rand);
|
|
288
|
-
if (left === undefined)
|
|
289
|
-
return plain();
|
|
290
|
-
const segs = decodeRank(left);
|
|
291
|
-
const last = segs.at(-1);
|
|
292
|
-
const bottom = encodeRank([{ v: RANK_RUN, t: 0 }]);
|
|
293
|
-
const top = encodeRank([{ v: RANK_RUN + RUN_BAND, t: 0 }]);
|
|
294
|
-
/**
|
|
295
|
-
* `prefix` and a rank after `after` (`undefined`: the run opens) in the
|
|
296
|
-
* run, inside the band. Opening, it takes the band's middle, or a digit
|
|
297
|
-
* below the run's first member; at the run's end it appends as an open
|
|
298
|
-
* end does (a window, not half the band); else between the two.
|
|
299
|
-
*/
|
|
300
|
-
const own = (prefix, after) => {
|
|
301
|
-
const tail = (r) => r && r.length > prefix.length && r.startsWith(prefix) ? r.slice(prefix.length) : undefined;
|
|
302
|
-
let next = tail(right);
|
|
303
|
-
if (next !== undefined && next > top)
|
|
304
|
-
next = undefined;
|
|
305
|
-
let mine;
|
|
306
|
-
if (after === undefined) {
|
|
307
|
-
if (next === undefined)
|
|
308
|
-
mine = encodeRank([{ v: RUN_START, t: client }]);
|
|
309
|
-
else if (next > bottom)
|
|
310
|
-
mine = rankBetween(bottom, next, client, rand, false);
|
|
311
|
-
else
|
|
312
|
-
return plain();
|
|
313
|
-
}
|
|
314
|
-
else if (next === undefined) {
|
|
315
|
-
const append = rankBetween(after, undefined, client, rand, false);
|
|
316
|
-
mine = append < top ? append : rankBetween(after, top, client, rand, false);
|
|
317
|
-
}
|
|
318
|
-
else
|
|
319
|
-
mine = rankBetween(after, next, client, rand, false);
|
|
320
|
-
const rank = prefix + mine;
|
|
321
|
-
return right === undefined || rank < right ? rank : plain();
|
|
322
|
-
};
|
|
323
|
-
// A member of the client's run: the next member, inside the band.
|
|
324
|
-
if (last.t === client && inRun(last.v))
|
|
325
|
-
return own(encodeRank(segs.slice(0, -1)), encodeRank([last]));
|
|
326
|
-
// A rank the client made: its run opens right after it. The prefix keeps
|
|
327
|
-
// `left`'s tie: a peer's rank of the same digit and a greater tie
|
|
328
|
-
// then sorts after the whole run, never between `left` and it.
|
|
329
|
-
if (last.t === client)
|
|
330
|
-
return own(left, undefined);
|
|
331
|
-
return plain();
|
|
332
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rank of a block promoted into the slot ranked `slot` (read-time
|
|
3
|
-
* promotion, and a promote-delete's planned moves): a valid rank, so an
|
|
4
|
-
* insert beside a promoted block ranks against it like any other.
|
|
5
|
-
*/
|
|
6
|
-
export declare const promotedRank: (slot: string, rank: string) => string;
|
|
7
|
-
/**
|
|
8
|
-
* Which side of a gap `(X, Y)` a block ranked by its source comes from
|
|
9
|
-
* ({@link sourceRank}), in the order every serial run puts them: the
|
|
10
|
-
* pieces a split of `X` creates, then the blocks leaving `X` for the gap
|
|
11
|
-
* right after it (its children, a list's last items), then the blocks
|
|
12
|
-
* leaving `Y` for the gap right before it (a list's first items).
|
|
13
|
-
*/
|
|
14
|
-
export declare const SOURCE_SIDE: {
|
|
15
|
-
readonly pieces: 0;
|
|
16
|
-
readonly after: 1;
|
|
17
|
-
readonly before: 2;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* The rank of a block ranked by where it came from for the gap
|
|
21
|
-
* `(left, right)` right beside its source, not by who moved it.
|
|
22
|
-
* Two peers splitting or lifting out of the same block at once rank their
|
|
23
|
-
* blocks in the same gap; ranks drawn at random there (and tied by client
|
|
24
|
-
* id) sorted one peer's blocks before the other's whatever the text order.
|
|
25
|
-
* Here the rank is a base every replica that saw the same gap computes
|
|
26
|
-
* alike (the gap's midpoint, a fixed tie), then the `side` it comes from
|
|
27
|
-
* ({@link SOURCE_SIDE}: blocks from different sources share the gap, and
|
|
28
|
-
* their paths are not comparable), then the ranks down the
|
|
29
|
-
* block's `path` in its source (the block it stands at), each closed by
|
|
30
|
-
* the lowest segment (a shorter rank then sorts first, as a prefix does),
|
|
31
|
-
* then its `part` there (the caller's order among what stands at one
|
|
32
|
-
* block), its first segment tied by `clientId`: two peers' blocks at one
|
|
33
|
-
* part never share a rank (a later insert between two equal ranks could
|
|
34
|
-
* not land between them), and each peer's blocks there stay together. A
|
|
35
|
-
* segment tied by `clock`, the client's own next clock, follows that first
|
|
36
|
-
* one: one client never mints one rank twice in a gap, even when the block
|
|
37
|
-
* it first minted it for was deleted there and a peer's undo brings it
|
|
38
|
-
* back, and what one gesture minted (the lines of one paste)
|
|
39
|
-
* sorts before what its later gestures mint there, never among it.
|
|
40
|
-
* `null` on a degenerate gap (`left >= right`): the caller ranks it as any
|
|
41
|
-
* insert.
|
|
42
|
-
*/
|
|
43
|
-
export declare const sourceRank: (left: string | undefined, right: string | undefined, side: number, path: readonly string[], part: readonly number[], clientId: number, clock: number) => string | null;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ranks minted from where a block comes from: the rank of a block promoted
|
|
3
|
-
* into a slot (`promotedRank`), and the source rank of a block placed beside
|
|
4
|
-
* its source (`sourceRank`), the same on every replica that saw the gap.
|
|
5
|
-
*/
|
|
6
|
-
import { decodeRank, encodeRank, rankBetween, RANK_VMIN } from './rank.js';
|
|
7
|
-
/**
|
|
8
|
-
* The rank separator of a promoted slot: the lowest segment a rank can hold
|
|
9
|
-
* (`rankBetween` only copies it from a promoted bound), so `slot + PROMOTED + rank` sorts after
|
|
10
|
-
* `slot` and before every rank the slot's list minted after it — also one
|
|
11
|
-
* that extends `slot`, as an insert between two adjacent digits does.
|
|
12
|
-
*/
|
|
13
|
-
const PROMOTED = encodeRank([{ v: RANK_VMIN, t: 0 }]);
|
|
14
|
-
/**
|
|
15
|
-
* The rank of a block promoted into the slot ranked `slot` (read-time
|
|
16
|
-
* promotion, and a promote-delete's planned moves): a valid rank, so an
|
|
17
|
-
* insert beside a promoted block ranks against it like any other.
|
|
18
|
-
*/
|
|
19
|
-
export const promotedRank = (slot, rank) => slot + PROMOTED + rank;
|
|
20
|
-
/**
|
|
21
|
-
* Which side of a gap `(X, Y)` a block ranked by its source comes from
|
|
22
|
-
* ({@link sourceRank}), in the order every serial run puts them: the
|
|
23
|
-
* pieces a split of `X` creates, then the blocks leaving `X` for the gap
|
|
24
|
-
* right after it (its children, a list's last items), then the blocks
|
|
25
|
-
* leaving `Y` for the gap right before it (a list's first items).
|
|
26
|
-
*/
|
|
27
|
-
export const SOURCE_SIDE = { pieces: 0, after: 1, before: 2 };
|
|
28
|
-
/**
|
|
29
|
-
* The rank of a block ranked by where it came from for the gap
|
|
30
|
-
* `(left, right)` right beside its source, not by who moved it.
|
|
31
|
-
* Two peers splitting or lifting out of the same block at once rank their
|
|
32
|
-
* blocks in the same gap; ranks drawn at random there (and tied by client
|
|
33
|
-
* id) sorted one peer's blocks before the other's whatever the text order.
|
|
34
|
-
* Here the rank is a base every replica that saw the same gap computes
|
|
35
|
-
* alike (the gap's midpoint, a fixed tie), then the `side` it comes from
|
|
36
|
-
* ({@link SOURCE_SIDE}: blocks from different sources share the gap, and
|
|
37
|
-
* their paths are not comparable), then the ranks down the
|
|
38
|
-
* block's `path` in its source (the block it stands at), each closed by
|
|
39
|
-
* the lowest segment (a shorter rank then sorts first, as a prefix does),
|
|
40
|
-
* then its `part` there (the caller's order among what stands at one
|
|
41
|
-
* block), its first segment tied by `clientId`: two peers' blocks at one
|
|
42
|
-
* part never share a rank (a later insert between two equal ranks could
|
|
43
|
-
* not land between them), and each peer's blocks there stay together. A
|
|
44
|
-
* segment tied by `clock`, the client's own next clock, follows that first
|
|
45
|
-
* one: one client never mints one rank twice in a gap, even when the block
|
|
46
|
-
* it first minted it for was deleted there and a peer's undo brings it
|
|
47
|
-
* back, and what one gesture minted (the lines of one paste)
|
|
48
|
-
* sorts before what its later gestures mint there, never among it.
|
|
49
|
-
* `null` on a degenerate gap (`left >= right`): the caller ranks it as any
|
|
50
|
-
* insert.
|
|
51
|
-
*/
|
|
52
|
-
export const sourceRank = (left, right, side, path, part, clientId, clock) => {
|
|
53
|
-
if (left !== undefined && right !== undefined && left >= right)
|
|
54
|
-
return null;
|
|
55
|
-
const closed = path.flatMap((r) => [...decodeRank(r), { v: RANK_VMIN, t: SOURCE_TIE }]);
|
|
56
|
-
const [first, ...rest] = part;
|
|
57
|
-
const parts = [
|
|
58
|
-
{ v: first, t: clientId },
|
|
59
|
-
{ v: 0, t: clock },
|
|
60
|
-
...rest.map((v) => ({ v, t: SOURCE_TIE }))
|
|
61
|
-
];
|
|
62
|
-
const own = [{ v: side, t: SOURCE_TIE }, ...closed, ...parts];
|
|
63
|
-
return rankBetween(left, right, SOURCE_TIE, () => 0.5) + encodeRank(own);
|
|
64
|
-
};
|
|
65
|
-
/** The tie of a source rank's own segments: the same on every replica. */
|
|
66
|
-
const SOURCE_TIE = 0;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { EngineApi, EngineDoc, EngineNode, YUndoManager } from '../engine-api.js';
|
|
2
|
-
import { type IdSetLike, type IdSetOf, type StoreStruct } from '../structs.js';
|
|
3
|
-
import type { Span } from './deletes.js';
|
|
4
|
-
/** Copy `c:k…` of member `oc:ok…`, whose root characters are `rc:rk…` (`n` of them). */
|
|
5
|
-
export type Copy = Span & {
|
|
6
|
-
rc: number;
|
|
7
|
-
rk: number;
|
|
8
|
-
oc: number;
|
|
9
|
-
ok: number;
|
|
10
|
-
};
|
|
11
|
-
/** A piece of one member (root or copy) of some characters: ids `c:k…`, root characters from `rk`. */
|
|
12
|
-
export type Member = Span & {
|
|
13
|
-
rk: number;
|
|
14
|
-
};
|
|
15
|
-
/** A record (one element of a records list), by engine id. */
|
|
16
|
-
export type Rec = {
|
|
17
|
-
client: number;
|
|
18
|
-
clock: number;
|
|
19
|
-
};
|
|
20
|
-
export type Unit = StoreStruct & {
|
|
21
|
-
parent: EngineNode & {
|
|
22
|
-
name?: string;
|
|
23
|
-
};
|
|
24
|
-
content: {
|
|
25
|
-
getContent(): unknown[];
|
|
26
|
-
str?: string;
|
|
27
|
-
type?: {
|
|
28
|
-
name?: string;
|
|
29
|
-
_map?: Map<string, Unit>;
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
delete(tr: unknown): void;
|
|
33
|
-
};
|
|
34
|
-
export type IdSet = IdSetOf;
|
|
35
|
-
export type Tr = {
|
|
36
|
-
local: boolean;
|
|
37
|
-
insertSet: IdSet;
|
|
38
|
-
deleteSet: IdSet;
|
|
39
|
-
changed: Map<unknown, unknown>;
|
|
40
|
-
};
|
|
41
|
-
export type Step = {
|
|
42
|
-
inserts: IdSet;
|
|
43
|
-
deletes: IdSet;
|
|
44
|
-
};
|
|
45
|
-
export type Policy = {
|
|
46
|
-
um: () => YUndoManager;
|
|
47
|
-
/**
|
|
48
|
-
* Root spans this session answers for: characters an undo skipped (held,
|
|
49
|
-
* or shown by another copy) and copies it removed as duplicates. Each is
|
|
50
|
-
* restored whenever nothing shows it and no mark holds it.
|
|
51
|
-
*/
|
|
52
|
-
watch: Span[];
|
|
53
|
-
/** The text units of the step being popped (its transaction). */
|
|
54
|
-
tr: unknown;
|
|
55
|
-
units: Span[];
|
|
56
|
-
};
|
|
57
|
-
export type State = {
|
|
58
|
-
doc: EngineDoc & {
|
|
59
|
-
clientID: number;
|
|
60
|
-
_transaction: Tr | null;
|
|
61
|
-
};
|
|
62
|
-
marks: EngineNode;
|
|
63
|
-
restored: EngineNode;
|
|
64
|
-
/** The purge horizon's root: a transaction that changes it is a purge. */
|
|
65
|
-
horizon: EngineNode;
|
|
66
|
-
/** Marks by root client: the root spans each holds (and by mark key). */
|
|
67
|
-
held: Map<number, Map<string, {
|
|
68
|
-
k: number;
|
|
69
|
-
n: number;
|
|
70
|
-
mark: Rec;
|
|
71
|
-
}[]>>;
|
|
72
|
-
holds: Map<string, Span[]>;
|
|
73
|
-
/** Copies by copy client, root client and origin client. */
|
|
74
|
-
copies: Map<number, Copy[]>;
|
|
75
|
-
byRoot: Map<number, Copy[]>;
|
|
76
|
-
byOrigin: Map<number, Copy[]>;
|
|
77
|
-
copyKeys: Set<string>;
|
|
78
|
-
policies: Set<Policy>;
|
|
79
|
-
/** This replica's last delete record (its id), the one a delete may fold into. */
|
|
80
|
-
lastMark: {
|
|
81
|
-
c: number;
|
|
82
|
-
k: number;
|
|
83
|
-
} | null;
|
|
84
|
-
};
|
|
85
|
-
export declare const overlaps: (a: {
|
|
86
|
-
k: number;
|
|
87
|
-
n: number;
|
|
88
|
-
}, b: {
|
|
89
|
-
k: number;
|
|
90
|
-
n: number;
|
|
91
|
-
}) => boolean;
|
|
92
|
-
export declare const push: <T>(map: Map<number, T[]>, key: number, v: T) => void;
|
|
93
|
-
export declare const keyOf: (r: Rec) => string;
|
|
94
|
-
/** The most spans a folded delete record holds (a backspace run merges into one). */
|
|
95
|
-
export declare const FOLD_SPANS = 8;
|
|
96
|
-
/** `spans` sorted, with touching and overlapping spans of one client merged. */
|
|
97
|
-
export declare const mergeSpans: (spans: readonly Span[]) => Span[];
|
|
98
|
-
/** `m` narrowed to root characters `[lo, hi)`. */
|
|
99
|
-
export declare const clip: (m: Member, lo: number, hi: number) => Member;
|
|
100
|
-
export declare const encode: (rows: number[][]) => Uint8Array;
|
|
101
|
-
export declare const decode: (bytes: Uint8Array, width: number) => number[][];
|
|
102
|
-
/** The index's reads and writes over a document's `State`. */
|
|
103
|
-
export declare const deleteIndex: (Y: EngineApi) => {
|
|
104
|
-
idSet: (spans: readonly Span[]) => IdSet;
|
|
105
|
-
each: (tr: Tr, spans: readonly Span[], fn: (it: Unit) => void) => void;
|
|
106
|
-
rootSpans: (s: State, sp: Span) => Span[];
|
|
107
|
-
members: (s: State, r: Span) => Member[];
|
|
108
|
-
liveRoots: (s: State, m: Member) => Span[];
|
|
109
|
-
shown: (s: State, roots: readonly Span[]) => Span[];
|
|
110
|
-
indexOf: (s: State, c: number, k: number) => number;
|
|
111
|
-
leaves: (s: State, r: Span) => Member[];
|
|
112
|
-
holders: (s: State, r: Span, skip?: (mark: Rec) => boolean) => Rec[];
|
|
113
|
-
minus: (r: Span, done: readonly Span[]) => Span[];
|
|
114
|
-
parts: (s: State, r: Span) => Span[];
|
|
115
|
-
addCopies: (s: State, copies: readonly Copy[]) => Copy[];
|
|
116
|
-
indexRecord: (s: State, rec: Rec, bytes: Uint8Array) => Copy[];
|
|
117
|
-
indexMark: (s: State, mark: Rec, bytes: Uint8Array) => boolean;
|
|
118
|
-
dropMarks: (s: State, marks: readonly Rec[]) => boolean;
|
|
119
|
-
recordsIn: (st: StoreStruct, ids: IdSetLike | null) => [Rec, Uint8Array][];
|
|
120
|
-
sync: (s: State, tr: Tr) => {
|
|
121
|
-
copies: Copy[];
|
|
122
|
-
marks: Rec[];
|
|
123
|
-
released: boolean;
|
|
124
|
-
};
|
|
125
|
-
copiesOf: (r: Rec, bytes: Uint8Array) => Copy[];
|
|
126
|
-
forget: (s: State, copies: readonly Copy[]) => void;
|
|
127
|
-
collect: (s: State, tr: unknown, copies: readonly Copy[]) => void;
|
|
128
|
-
};
|
|
129
|
-
export type DeleteIndex = ReturnType<typeof deleteIndex>;
|