@tldraw/utils 5.2.0-next.79b13319d317 → 5.2.0-next.7b677d60c152
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 +4 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/fractionalIndexing.js +227 -0
- package/dist-cjs/lib/fractionalIndexing.js.map +7 -0
- package/dist-cjs/lib/reordering.js +3 -6
- package/dist-cjs/lib/reordering.js.map +2 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/fractionalIndexing.mjs +207 -0
- package/dist-esm/lib/fractionalIndexing.mjs.map +7 -0
- package/dist-esm/lib/reordering.mjs +7 -6
- package/dist-esm/lib/reordering.mjs.map +2 -2
- package/package.json +4 -2
- package/src/lib/fractionalIndexing.test.ts +138 -0
- package/src/lib/fractionalIndexing.ts +275 -0
- package/src/lib/reordering.ts +7 -7
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ Utility functions used by tldraw.
|
|
|
4
4
|
|
|
5
5
|
## Documentation
|
|
6
6
|
|
|
7
|
+
Documentation for the most recent release can be found on [tldraw.dev/docs](https://tldraw.dev/docs), including [reference docs](https://tldraw.dev/reference/editor/Editor). Our release notes can be found [here](https://tldraw.dev/releases).
|
|
8
|
+
|
|
9
|
+
For more agent-friendly docs, see our [LLMs.txt](https://tldraw.dev/llms.txt).
|
|
10
|
+
|
|
7
11
|
A `DOCS.md` file is included alongside this README in the published package, with detailed API documentation and usage examples.
|
|
8
12
|
|
|
9
13
|
## Contribution
|
package/dist-cjs/index.js
CHANGED
|
@@ -171,7 +171,7 @@ var import_version2 = require("./lib/version");
|
|
|
171
171
|
var import_warn = require("./lib/warn");
|
|
172
172
|
(0, import_version.registerTldrawLibraryVersion)(
|
|
173
173
|
"@tldraw/utils",
|
|
174
|
-
"5.2.0-next.
|
|
174
|
+
"5.2.0-next.7b677d60c152",
|
|
175
175
|
"cjs"
|
|
176
176
|
);
|
|
177
177
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var fractionalIndexing_exports = {};
|
|
20
|
+
__export(fractionalIndexing_exports, {
|
|
21
|
+
generateNJitteredKeysBetween: () => generateNJitteredKeysBetween,
|
|
22
|
+
generateNKeysBetween: () => generateNKeysBetween,
|
|
23
|
+
validateOrderKey: () => validateOrderKey
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(fractionalIndexing_exports);
|
|
26
|
+
const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
27
|
+
const ZERO = DIGITS[0];
|
|
28
|
+
const LARGEST_DIGIT = DIGITS[DIGITS.length - 1];
|
|
29
|
+
const SMALLEST_INTEGER = "A" + ZERO.repeat(26);
|
|
30
|
+
const JITTER_BITS = 16;
|
|
31
|
+
function getIntegerLength(head) {
|
|
32
|
+
if (head >= "a" && head <= "z") {
|
|
33
|
+
return head.charCodeAt(0) - "a".charCodeAt(0) + 2;
|
|
34
|
+
} else if (head >= "A" && head <= "Z") {
|
|
35
|
+
return "Z".charCodeAt(0) - head.charCodeAt(0) + 2;
|
|
36
|
+
}
|
|
37
|
+
throw new Error("invalid order key head: " + head);
|
|
38
|
+
}
|
|
39
|
+
function getIntegerPart(key) {
|
|
40
|
+
const integerPartLength = getIntegerLength(key[0]);
|
|
41
|
+
if (integerPartLength > key.length) {
|
|
42
|
+
throw new Error("invalid order key: " + key);
|
|
43
|
+
}
|
|
44
|
+
return key.slice(0, integerPartLength);
|
|
45
|
+
}
|
|
46
|
+
function digitIndex(char) {
|
|
47
|
+
const code = char.charCodeAt(0);
|
|
48
|
+
if (code <= 57) return code - 48;
|
|
49
|
+
if (code <= 90) return code - 55;
|
|
50
|
+
return code - 61;
|
|
51
|
+
}
|
|
52
|
+
function validateOrderKey(key) {
|
|
53
|
+
if (key === SMALLEST_INTEGER) {
|
|
54
|
+
throw new Error("invalid order key: " + key);
|
|
55
|
+
}
|
|
56
|
+
const i = getIntegerPart(key);
|
|
57
|
+
if (key.length > i.length && key[key.length - 1] === ZERO) {
|
|
58
|
+
throw new Error("invalid order key: " + key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function midpoint(a, b) {
|
|
62
|
+
if (b != null && a >= b) {
|
|
63
|
+
throw new Error(a + " >= " + b);
|
|
64
|
+
}
|
|
65
|
+
if (a.slice(-1) === ZERO || b && b.slice(-1) === ZERO) {
|
|
66
|
+
throw new Error("trailing zero");
|
|
67
|
+
}
|
|
68
|
+
if (b) {
|
|
69
|
+
let n = 0;
|
|
70
|
+
while ((a[n] || ZERO) === b[n]) {
|
|
71
|
+
n++;
|
|
72
|
+
}
|
|
73
|
+
if (n > 0) {
|
|
74
|
+
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const digitA = a ? digitIndex(a[0]) : 0;
|
|
78
|
+
const digitB = b != null ? digitIndex(b[0]) : DIGITS.length;
|
|
79
|
+
if (digitB - digitA > 1) {
|
|
80
|
+
const midDigit = Math.round(0.5 * (digitA + digitB));
|
|
81
|
+
return DIGITS[midDigit];
|
|
82
|
+
}
|
|
83
|
+
if (b && b.length > 1) {
|
|
84
|
+
return b.slice(0, 1);
|
|
85
|
+
}
|
|
86
|
+
return DIGITS[digitA] + midpoint(a.slice(1), null);
|
|
87
|
+
}
|
|
88
|
+
function incrementInteger(x) {
|
|
89
|
+
const [head, ...digs] = x.split("");
|
|
90
|
+
let carry = true;
|
|
91
|
+
for (let i = digs.length - 1; carry && i >= 0; i--) {
|
|
92
|
+
const d = digitIndex(digs[i]) + 1;
|
|
93
|
+
if (d === DIGITS.length) {
|
|
94
|
+
digs[i] = ZERO;
|
|
95
|
+
} else {
|
|
96
|
+
digs[i] = DIGITS[d];
|
|
97
|
+
carry = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (carry) {
|
|
101
|
+
if (head === "Z") return "a" + ZERO;
|
|
102
|
+
if (head === "z") return null;
|
|
103
|
+
const h = String.fromCharCode(head.charCodeAt(0) + 1);
|
|
104
|
+
if (h > "a") {
|
|
105
|
+
digs.push(ZERO);
|
|
106
|
+
} else {
|
|
107
|
+
digs.pop();
|
|
108
|
+
}
|
|
109
|
+
return h + digs.join("");
|
|
110
|
+
}
|
|
111
|
+
return head + digs.join("");
|
|
112
|
+
}
|
|
113
|
+
function decrementInteger(x) {
|
|
114
|
+
const [head, ...digs] = x.split("");
|
|
115
|
+
let borrow = true;
|
|
116
|
+
for (let i = digs.length - 1; borrow && i >= 0; i--) {
|
|
117
|
+
const d = digitIndex(digs[i]) - 1;
|
|
118
|
+
if (d === -1) {
|
|
119
|
+
digs[i] = LARGEST_DIGIT;
|
|
120
|
+
} else {
|
|
121
|
+
digs[i] = DIGITS[d];
|
|
122
|
+
borrow = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (borrow) {
|
|
126
|
+
if (head === "a") return "Z" + LARGEST_DIGIT;
|
|
127
|
+
if (head === "A") return null;
|
|
128
|
+
const h = String.fromCharCode(head.charCodeAt(0) - 1);
|
|
129
|
+
if (h < "Z") {
|
|
130
|
+
digs.push(LARGEST_DIGIT);
|
|
131
|
+
} else {
|
|
132
|
+
digs.pop();
|
|
133
|
+
}
|
|
134
|
+
return h + digs.join("");
|
|
135
|
+
}
|
|
136
|
+
return head + digs.join("");
|
|
137
|
+
}
|
|
138
|
+
function keyBetweenUnchecked(a, b) {
|
|
139
|
+
if (a != null && b != null && a >= b) {
|
|
140
|
+
throw new Error(a + " >= " + b);
|
|
141
|
+
}
|
|
142
|
+
if (a == null) {
|
|
143
|
+
if (b == null) return "a" + ZERO;
|
|
144
|
+
const ib2 = getIntegerPart(b);
|
|
145
|
+
const fb2 = b.slice(ib2.length);
|
|
146
|
+
if (ib2 === SMALLEST_INTEGER) {
|
|
147
|
+
return ib2 + midpoint("", fb2);
|
|
148
|
+
}
|
|
149
|
+
if (ib2 < b) return ib2;
|
|
150
|
+
const res = decrementInteger(ib2);
|
|
151
|
+
if (res == null) throw new Error("cannot decrement any more");
|
|
152
|
+
return res;
|
|
153
|
+
}
|
|
154
|
+
if (b == null) {
|
|
155
|
+
const ia2 = getIntegerPart(a);
|
|
156
|
+
const fa2 = a.slice(ia2.length);
|
|
157
|
+
const i2 = incrementInteger(ia2);
|
|
158
|
+
return i2 == null ? ia2 + midpoint(fa2, null) : i2;
|
|
159
|
+
}
|
|
160
|
+
const ia = getIntegerPart(a);
|
|
161
|
+
const fa = a.slice(ia.length);
|
|
162
|
+
const ib = getIntegerPart(b);
|
|
163
|
+
const fb = b.slice(ib.length);
|
|
164
|
+
if (ia === ib) {
|
|
165
|
+
return ia + midpoint(fa, fb);
|
|
166
|
+
}
|
|
167
|
+
const i = incrementInteger(ia);
|
|
168
|
+
if (i == null) throw new Error("cannot increment any more");
|
|
169
|
+
if (i < b) return i;
|
|
170
|
+
return ia + midpoint(fa, null);
|
|
171
|
+
}
|
|
172
|
+
function nKeysBetweenUnchecked(a, b, n) {
|
|
173
|
+
if (n === 0) return [];
|
|
174
|
+
if (n === 1) return [keyBetweenUnchecked(a, b)];
|
|
175
|
+
if (b == null) {
|
|
176
|
+
let c2 = keyBetweenUnchecked(a, b);
|
|
177
|
+
const result = [c2];
|
|
178
|
+
for (let i = 0; i < n - 1; i++) {
|
|
179
|
+
c2 = keyBetweenUnchecked(c2, b);
|
|
180
|
+
result.push(c2);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
if (a == null) {
|
|
185
|
+
let c2 = keyBetweenUnchecked(a, b);
|
|
186
|
+
const result = [c2];
|
|
187
|
+
for (let i = 0; i < n - 1; i++) {
|
|
188
|
+
c2 = keyBetweenUnchecked(a, c2);
|
|
189
|
+
result.push(c2);
|
|
190
|
+
}
|
|
191
|
+
result.reverse();
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
const mid = Math.floor(n / 2);
|
|
195
|
+
const c = keyBetweenUnchecked(a, b);
|
|
196
|
+
return [...nKeysBetweenUnchecked(a, c, mid), c, ...nKeysBetweenUnchecked(c, b, n - mid - 1)];
|
|
197
|
+
}
|
|
198
|
+
function jitterBetween(low, high) {
|
|
199
|
+
let mid = keyBetweenUnchecked(low, high);
|
|
200
|
+
for (let i = 0; i < JITTER_BITS; i++) {
|
|
201
|
+
if (Math.random() < 0.5) {
|
|
202
|
+
low = mid;
|
|
203
|
+
} else {
|
|
204
|
+
high = mid;
|
|
205
|
+
}
|
|
206
|
+
mid = keyBetweenUnchecked(low, high);
|
|
207
|
+
}
|
|
208
|
+
return mid;
|
|
209
|
+
}
|
|
210
|
+
function generateNJitteredKeysBetween(a, b, n) {
|
|
211
|
+
if (n === 0) return [];
|
|
212
|
+
if (a != null) validateOrderKey(a);
|
|
213
|
+
if (b != null) validateOrderKey(b);
|
|
214
|
+
const keys = nKeysBetweenUnchecked(a, b, n + 1);
|
|
215
|
+
const result = new Array(n);
|
|
216
|
+
for (let i = 0; i < n; i++) {
|
|
217
|
+
result[i] = jitterBetween(keys[i], keys[i + 1]);
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
function generateNKeysBetween(a, b, n) {
|
|
222
|
+
if (n === 0) return [];
|
|
223
|
+
if (a != null) validateOrderKey(a);
|
|
224
|
+
if (b != null) validateOrderKey(b);
|
|
225
|
+
return nKeysBetweenUnchecked(a, b, n);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=fractionalIndexing.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/fractionalIndexing.ts"],
|
|
4
|
+
"sourcesContent": ["// Fractional indexing, specialized to tldraw's needs.\n//\n// This is a vendored and trimmed version of the `fractional-indexing` (by\n// arv@rocicorp.dev) and `jittered-fractional-indexing` (by\n// github@nathanhleung.com) packages, both released under CC0-1.0 (public\n// domain). See https://observablehq.com/@dgreensp/implementing-fractional-indexing\n// for the original algorithm.\n//\n// We only ever use the default base-62 alphabet, so the `digits` parameter has\n// been specialized away. This lets us hoist the per-call allocations that the\n// upstream `validateOrderKey` made (notably `digits[0].repeat(26)`) into module\n// constants, and skip redundant re-validation inside the jitter loop, which are\n// both on hot paths (every index generation and every IndexKey validation).\n\n// Digits must be in ascending character-code order.\nconst DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst ZERO = DIGITS[0] // '0'\nconst LARGEST_DIGIT = DIGITS[DIGITS.length - 1] // 'z'\n// The reserved \"smallest integer\" key, which has no valid predecessor.\nconst SMALLEST_INTEGER = 'A' + ZERO.repeat(26)\n// Number of random bisections used to spread concurrently-generated keys apart,\n// reducing the chance of collisions when multiple clients insert into the same\n// gap at once. Each bit costs one extra key generation and ~0.17 characters of\n// key length, so this trades collision resistance against compute and key size.\n// 16 keeps collisions below ~0.1% even for ~10 simultaneous same-position\n// inserts, which is ample headroom for real multiplayer use.\nconst JITTER_BITS = 16\n\nfunction getIntegerLength(head: string): number {\n\tif (head >= 'a' && head <= 'z') {\n\t\treturn head.charCodeAt(0) - 'a'.charCodeAt(0) + 2\n\t} else if (head >= 'A' && head <= 'Z') {\n\t\treturn 'Z'.charCodeAt(0) - head.charCodeAt(0) + 2\n\t}\n\tthrow new Error('invalid order key head: ' + head)\n}\n\nfunction getIntegerPart(key: string): string {\n\tconst integerPartLength = getIntegerLength(key[0])\n\tif (integerPartLength > key.length) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n\treturn key.slice(0, integerPartLength)\n}\n\n// Map a base-62 digit to its value. Faster than DIGITS.indexOf() (which scans\n// the alphabet) on the hot key-generation path. DIGITS is in char-code order:\n// '0'-'9' -> 0-9, 'A'-'Z' -> 10-35, 'a'-'z' -> 36-61.\nfunction digitIndex(char: string): number {\n\tconst code = char.charCodeAt(0)\n\tif (code <= 57) return code - 48\n\tif (code <= 90) return code - 55\n\treturn code - 61\n}\n\n/**\n * Throws if `key` is not a canonical order key. Allocation-free in the common\n * case: no `repeat`/`slice` per call.\n */\nexport function validateOrderKey(key: string): void {\n\tif (key === SMALLEST_INTEGER) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n\t// getIntegerPart throws if the first character is bad or the key is too short.\n\tconst i = getIntegerPart(key)\n\t// The fractional part (everything after the integer part) must not end in the\n\t// smallest digit, as a trailing zero is a non-canonical representation.\n\tif (key.length > i.length && key[key.length - 1] === ZERO) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n}\n\n// `a` may be empty string, `b` is null or non-empty string.\n// `a < b` lexicographically if `b` is non-null. No trailing zeros allowed.\nfunction midpoint(a: string, b: string | null): string {\n\tif (b != null && a >= b) {\n\t\tthrow new Error(a + ' >= ' + b)\n\t}\n\tif (a.slice(-1) === ZERO || (b && b.slice(-1) === ZERO)) {\n\t\tthrow new Error('trailing zero')\n\t}\n\tif (b) {\n\t\t// Remove the longest common prefix, padding `a` with zeros as we go. We\n\t\t// don't need to pad `b`, because it can't end before `a` while traversing\n\t\t// the common prefix.\n\t\tlet n = 0\n\t\twhile ((a[n] || ZERO) === b[n]) {\n\t\t\tn++\n\t\t}\n\t\tif (n > 0) {\n\t\t\treturn b.slice(0, n) + midpoint(a.slice(n), b.slice(n))\n\t\t}\n\t}\n\t// First digits (or lack of digit) are different.\n\tconst digitA = a ? digitIndex(a[0]) : 0\n\tconst digitB = b != null ? digitIndex(b[0]) : DIGITS.length\n\tif (digitB - digitA > 1) {\n\t\tconst midDigit = Math.round(0.5 * (digitA + digitB))\n\t\treturn DIGITS[midDigit]\n\t}\n\t// First digits are consecutive.\n\tif (b && b.length > 1) {\n\t\treturn b.slice(0, 1)\n\t}\n\t// `b` is null or has length 1 (a single digit). The first digit of `a` is the\n\t// previous digit to `b`, or the largest digit if `b` is null.\n\treturn DIGITS[digitA] + midpoint(a.slice(1), null)\n}\n\n// May return null, as there is a largest integer.\nfunction incrementInteger(x: string): string | null {\n\tconst [head, ...digs] = x.split('')\n\tlet carry = true\n\tfor (let i = digs.length - 1; carry && i >= 0; i--) {\n\t\tconst d = digitIndex(digs[i]) + 1\n\t\tif (d === DIGITS.length) {\n\t\t\tdigs[i] = ZERO\n\t\t} else {\n\t\t\tdigs[i] = DIGITS[d]\n\t\t\tcarry = false\n\t\t}\n\t}\n\tif (carry) {\n\t\tif (head === 'Z') return 'a' + ZERO\n\t\tif (head === 'z') return null\n\t\tconst h = String.fromCharCode(head.charCodeAt(0) + 1)\n\t\tif (h > 'a') {\n\t\t\tdigs.push(ZERO)\n\t\t} else {\n\t\t\tdigs.pop()\n\t\t}\n\t\treturn h + digs.join('')\n\t}\n\treturn head + digs.join('')\n}\n\n// May return null, as there is a smallest integer.\nfunction decrementInteger(x: string): string | null {\n\tconst [head, ...digs] = x.split('')\n\tlet borrow = true\n\tfor (let i = digs.length - 1; borrow && i >= 0; i--) {\n\t\tconst d = digitIndex(digs[i]) - 1\n\t\tif (d === -1) {\n\t\t\tdigs[i] = LARGEST_DIGIT\n\t\t} else {\n\t\t\tdigs[i] = DIGITS[d]\n\t\t\tborrow = false\n\t\t}\n\t}\n\tif (borrow) {\n\t\tif (head === 'a') return 'Z' + LARGEST_DIGIT\n\t\tif (head === 'A') return null\n\t\tconst h = String.fromCharCode(head.charCodeAt(0) - 1)\n\t\tif (h < 'Z') {\n\t\t\tdigs.push(LARGEST_DIGIT)\n\t\t} else {\n\t\t\tdigs.pop()\n\t\t}\n\t\treturn h + digs.join('')\n\t}\n\treturn head + digs.join('')\n}\n\n// Generate a key strictly between `a` and `b`, without validating the inputs.\n// `a`/`b` are order keys or null (START/END). `a < b` if both are non-null.\nfunction keyBetweenUnchecked(a: string | null, b: string | null): string {\n\tif (a != null && b != null && a >= b) {\n\t\tthrow new Error(a + ' >= ' + b)\n\t}\n\tif (a == null) {\n\t\tif (b == null) return 'a' + ZERO\n\t\tconst ib = getIntegerPart(b)\n\t\tconst fb = b.slice(ib.length)\n\t\tif (ib === SMALLEST_INTEGER) {\n\t\t\treturn ib + midpoint('', fb)\n\t\t}\n\t\tif (ib < b) return ib\n\t\tconst res = decrementInteger(ib)\n\t\tif (res == null) throw new Error('cannot decrement any more')\n\t\treturn res\n\t}\n\tif (b == null) {\n\t\tconst ia = getIntegerPart(a)\n\t\tconst fa = a.slice(ia.length)\n\t\tconst i = incrementInteger(ia)\n\t\treturn i == null ? ia + midpoint(fa, null) : i\n\t}\n\tconst ia = getIntegerPart(a)\n\tconst fa = a.slice(ia.length)\n\tconst ib = getIntegerPart(b)\n\tconst fb = b.slice(ib.length)\n\tif (ia === ib) {\n\t\treturn ia + midpoint(fa, fb)\n\t}\n\tconst i = incrementInteger(ia)\n\tif (i == null) throw new Error('cannot increment any more')\n\tif (i < b) return i\n\treturn ia + midpoint(fa, null)\n}\n\n// Generate `n` keys between `a` and `b`, without validating the inputs.\n// Intermediate keys are valid by construction, so they're never re-validated.\nfunction nKeysBetweenUnchecked(a: string | null, b: string | null, n: number): string[] {\n\tif (n === 0) return []\n\tif (n === 1) return [keyBetweenUnchecked(a, b)]\n\tif (b == null) {\n\t\tlet c = keyBetweenUnchecked(a, b)\n\t\tconst result = [c]\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tc = keyBetweenUnchecked(c, b)\n\t\t\tresult.push(c)\n\t\t}\n\t\treturn result\n\t}\n\tif (a == null) {\n\t\tlet c = keyBetweenUnchecked(a, b)\n\t\tconst result = [c]\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tc = keyBetweenUnchecked(a, c)\n\t\t\tresult.push(c)\n\t\t}\n\t\tresult.reverse()\n\t\treturn result\n\t}\n\tconst mid = Math.floor(n / 2)\n\tconst c = keyBetweenUnchecked(a, b)\n\treturn [...nKeysBetweenUnchecked(a, c, mid), c, ...nKeysBetweenUnchecked(c, b, n - mid - 1)]\n}\n\n// Bisect `[low, high]` JITTER_BITS times, following a random walk, so that keys\n// generated concurrently land in different sub-ranges. `low`/`high` must already\n// be valid (or null); the intermediate midpoints are valid by construction.\nfunction jitterBetween(low: string | null, high: string | null): string {\n\tlet mid = keyBetweenUnchecked(low, high)\n\tfor (let i = 0; i < JITTER_BITS; i++) {\n\t\tif (Math.random() < 0.5) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t\tmid = keyBetweenUnchecked(low, high)\n\t}\n\treturn mid\n}\n\n/**\n * Generate `n` jittered keys evenly spaced between `a` and `b`. Inputs are\n * validated once; everything downstream is generated, hence trusted.\n */\nexport function generateNJitteredKeysBetween(\n\ta: string | null,\n\tb: string | null,\n\tn: number\n): string[] {\n\tif (n === 0) return []\n\tif (a != null) validateOrderKey(a)\n\tif (b != null) validateOrderKey(b)\n\tconst keys = nKeysBetweenUnchecked(a, b, n + 1)\n\tconst result = new Array<string>(n)\n\tfor (let i = 0; i < n; i++) {\n\t\tresult[i] = jitterBetween(keys[i], keys[i + 1])\n\t}\n\treturn result\n}\n\n/**\n * Generate `n` keys evenly spaced between `a` and `b`, without jitter. Used in\n * tests for deterministic output.\n */\nexport function generateNKeysBetween(a: string | null, b: string | null, n: number): string[] {\n\tif (n === 0) return []\n\tif (a != null) validateOrderKey(a)\n\tif (b != null) validateOrderKey(b)\n\treturn nKeysBetweenUnchecked(a, b, n)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["ib", "fb", "ia", "fa", "i", "c"]
|
|
7
|
+
}
|
|
@@ -31,15 +31,12 @@ __export(reordering_exports, {
|
|
|
31
31
|
validateIndexKey: () => validateIndexKey
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(reordering_exports);
|
|
34
|
-
var
|
|
35
|
-
const
|
|
36
|
-
return (0, import_jittered_fractional_indexing.generateNKeysBetween)(a, b, n, { jitterBits: 0 });
|
|
37
|
-
};
|
|
38
|
-
const generateKeysFn = process.env.NODE_ENV === "test" ? generateNKeysBetweenWithNoJitter : import_jittered_fractional_indexing.generateNKeysBetween;
|
|
34
|
+
var import_fractionalIndexing = require("./fractionalIndexing");
|
|
35
|
+
const generateKeysFn = process.env.NODE_ENV === "test" ? import_fractionalIndexing.generateNKeysBetween : import_fractionalIndexing.generateNJitteredKeysBetween;
|
|
39
36
|
const ZERO_INDEX_KEY = "a0";
|
|
40
37
|
function validateIndexKey(index) {
|
|
41
38
|
try {
|
|
42
|
-
(0,
|
|
39
|
+
(0, import_fractionalIndexing.validateOrderKey)(index);
|
|
43
40
|
} catch {
|
|
44
41
|
throw new Error("invalid index: " + index);
|
|
45
42
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/reordering.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tgenerateNJitteredKeysBetween,\n\tgenerateNKeysBetween,\n\tvalidateOrderKey,\n} from './fractionalIndexing'\n\nconst generateKeysFn =\n\tprocess.env.NODE_ENV === 'test' ? generateNKeysBetween : generateNJitteredKeysBetween\n\n/**\n * A string made up of an integer part followed by a fraction part. The fraction point consists of\n * zero or more digits with no trailing zeros. Based on\n * {@link https://observablehq.com/@dgreensp/implementing-fractional-indexing}.\n *\n * @public\n */\nexport type IndexKey = string & { __brand: 'indexKey' }\n\n/**\n * The index key for the first index - 'a0'.\n * @public\n */\nexport const ZERO_INDEX_KEY = 'a0' as IndexKey\n\n/**\n * Validates that a string is a valid IndexKey.\n * @param index - The string to validate.\n * @throws Error if the index is invalid.\n * @internal\n */\nexport function validateIndexKey(index: string): asserts index is IndexKey {\n\ttry {\n\t\tvalidateOrderKey(index)\n\t} catch {\n\t\tthrow new Error('invalid index: ' + index)\n\t}\n}\n\n/**\n * Get a number of indices between two indices.\n * @param below - The index below.\n * @param above - The index above.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values between below and above.\n * @example\n * ```ts\n * const indices = getIndicesBetween('a0' as IndexKey, 'a2' as IndexKey, 2)\n * console.log(indices) // ['a0V', 'a1']\n * ```\n * @public\n */\nexport function getIndicesBetween(\n\tbelow: IndexKey | null | undefined,\n\tabove: IndexKey | null | undefined,\n\tn: number\n) {\n\treturn generateKeysFn(below ?? null, above ?? null, n) as IndexKey[]\n}\n\n/**\n * Get a number of indices above an index.\n * @param below - The index below.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values above the given index.\n * @example\n * ```ts\n * const indices = getIndicesAbove('a0' as IndexKey, 3)\n * console.log(indices) // ['a1', 'a2', 'a3']\n * ```\n * @public\n */\nexport function getIndicesAbove(below: IndexKey | null | undefined, n: number) {\n\treturn generateKeysFn(below ?? null, null, n) as IndexKey[]\n}\n\n/**\n * Get a number of indices below an index.\n * @param above - The index above.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values below the given index.\n * @example\n * ```ts\n * const indices = getIndicesBelow('a2' as IndexKey, 2)\n * console.log(indices) // ['a1', 'a0V']\n * ```\n * @public\n */\nexport function getIndicesBelow(above: IndexKey | null | undefined, n: number) {\n\treturn generateKeysFn(null, above ?? null, n) as IndexKey[]\n}\n\n/**\n * Get the index between two indices.\n * @param below - The index below.\n * @param above - The index above.\n * @returns A single IndexKey value between below and above.\n * @example\n * ```ts\n * const index = getIndexBetween('a0' as IndexKey, 'a2' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexBetween(\n\tbelow: IndexKey | null | undefined,\n\tabove: IndexKey | null | undefined\n) {\n\treturn generateKeysFn(below ?? null, above ?? null, 1)[0] as IndexKey\n}\n\n/**\n * Get the index above a given index.\n * @param below - The index below.\n * @returns An IndexKey value above the given index.\n * @example\n * ```ts\n * const index = getIndexAbove('a0' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexAbove(below: IndexKey | null | undefined = null) {\n\treturn generateKeysFn(below, null, 1)[0] as IndexKey\n}\n\n/**\n * Get the index below a given index.\n * @param above - The index above.\n * @returns An IndexKey value below the given index.\n * @example\n * ```ts\n * const index = getIndexBelow('a2' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexBelow(above: IndexKey | null | undefined = null) {\n\treturn generateKeysFn(null, above, 1)[0] as IndexKey\n}\n\n/**\n * Get n number of indices, starting at an index.\n * @param n - The number of indices to get.\n * @param start - The index to start at.\n * @returns An array containing the start index plus n additional IndexKey values.\n * @example\n * ```ts\n * const indices = getIndices(3, 'a1' as IndexKey)\n * console.log(indices) // ['a1', 'a2', 'a3', 'a4']\n * ```\n * @public\n */\nexport function getIndices(n: number, start = 'a1' as IndexKey) {\n\treturn [start, ...generateKeysFn(start, null, n)] as IndexKey[]\n}\n\n/**\n * Sort by index.\n * @param a - An object with an index property.\n * @param b - An object with an index property.\n * @returns A number indicating sort order (-1, 0, or 1).\n * @example\n * ```ts\n * const shapes = [\n * { id: 'b', index: 'a2' as IndexKey },\n * { id: 'a', index: 'a1' as IndexKey }\n * ]\n * const sorted = shapes.sort(sortByIndex)\n * console.log(sorted) // [{ id: 'a', index: 'a1' }, { id: 'b', index: 'a2' }]\n * ```\n * @public\n */\nexport function sortByIndex<T extends { index: IndexKey }>(a: T, b: T) {\n\tif (a.index < b.index) {\n\t\treturn -1\n\t} else if (a.index > b.index) {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n/**\n * Sort by index, or null.\n * @param a - An object with an index property.\n * @param b - An object with an index property.\n * @public\n */\nexport function sortByMaybeIndex<T extends { index?: IndexKey | null }>(a: T, b: T) {\n\tif (a.index && b.index) {\n\t\treturn a.index < b.index ? -1 : 1\n\t}\n\tif (a.index && b.index == null) {\n\t\treturn -1\n\t}\n\tif (a.index == null && b.index == null) {\n\t\treturn 0\n\t}\n\treturn 1\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAIO;AAEP,MAAM,iBACL,QAAQ,IAAI,aAAa,SAAS,iDAAuB;AAenD,MAAM,iBAAiB;AAQvB,SAAS,iBAAiB,OAA0C;AAC1E,MAAI;AACH,oDAAiB,KAAK;AAAA,EACvB,QAAQ;AACP,UAAM,IAAI,MAAM,oBAAoB,KAAK;AAAA,EAC1C;AACD;AAeO,SAAS,kBACf,OACA,OACA,GACC;AACD,SAAO,eAAe,SAAS,MAAM,SAAS,MAAM,CAAC;AACtD;AAcO,SAAS,gBAAgB,OAAoC,GAAW;AAC9E,SAAO,eAAe,SAAS,MAAM,MAAM,CAAC;AAC7C;AAcO,SAAS,gBAAgB,OAAoC,GAAW;AAC9E,SAAO,eAAe,MAAM,SAAS,MAAM,CAAC;AAC7C;AAcO,SAAS,gBACf,OACA,OACC;AACD,SAAO,eAAe,SAAS,MAAM,SAAS,MAAM,CAAC,EAAE,CAAC;AACzD;AAaO,SAAS,cAAc,QAAqC,MAAM;AACxE,SAAO,eAAe,OAAO,MAAM,CAAC,EAAE,CAAC;AACxC;AAaO,SAAS,cAAc,QAAqC,MAAM;AACxE,SAAO,eAAe,MAAM,OAAO,CAAC,EAAE,CAAC;AACxC;AAcO,SAAS,WAAW,GAAW,QAAQ,MAAkB;AAC/D,SAAO,CAAC,OAAO,GAAG,eAAe,OAAO,MAAM,CAAC,CAAC;AACjD;AAkBO,SAAS,YAA2C,GAAM,GAAM;AACtE,MAAI,EAAE,QAAQ,EAAE,OAAO;AACtB,WAAO;AAAA,EACR,WAAW,EAAE,QAAQ,EAAE,OAAO;AAC7B,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAQO,SAAS,iBAAwD,GAAM,GAAM;AACnF,MAAI,EAAE,SAAS,EAAE,OAAO;AACvB,WAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACjC;AACA,MAAI,EAAE,SAAS,EAAE,SAAS,MAAM;AAC/B,WAAO;AAAA,EACR;AACA,MAAI,EAAE,SAAS,QAAQ,EAAE,SAAS,MAAM;AACvC,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -102,7 +102,7 @@ import { registerTldrawLibraryVersion as registerTldrawLibraryVersion2 } from ".
|
|
|
102
102
|
import { warnDeprecatedGetter, warnOnce } from "./lib/warn.mjs";
|
|
103
103
|
registerTldrawLibraryVersion(
|
|
104
104
|
"@tldraw/utils",
|
|
105
|
-
"5.2.0-next.
|
|
105
|
+
"5.2.0-next.7b677d60c152",
|
|
106
106
|
"esm"
|
|
107
107
|
);
|
|
108
108
|
export {
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
2
|
+
const ZERO = DIGITS[0];
|
|
3
|
+
const LARGEST_DIGIT = DIGITS[DIGITS.length - 1];
|
|
4
|
+
const SMALLEST_INTEGER = "A" + ZERO.repeat(26);
|
|
5
|
+
const JITTER_BITS = 16;
|
|
6
|
+
function getIntegerLength(head) {
|
|
7
|
+
if (head >= "a" && head <= "z") {
|
|
8
|
+
return head.charCodeAt(0) - "a".charCodeAt(0) + 2;
|
|
9
|
+
} else if (head >= "A" && head <= "Z") {
|
|
10
|
+
return "Z".charCodeAt(0) - head.charCodeAt(0) + 2;
|
|
11
|
+
}
|
|
12
|
+
throw new Error("invalid order key head: " + head);
|
|
13
|
+
}
|
|
14
|
+
function getIntegerPart(key) {
|
|
15
|
+
const integerPartLength = getIntegerLength(key[0]);
|
|
16
|
+
if (integerPartLength > key.length) {
|
|
17
|
+
throw new Error("invalid order key: " + key);
|
|
18
|
+
}
|
|
19
|
+
return key.slice(0, integerPartLength);
|
|
20
|
+
}
|
|
21
|
+
function digitIndex(char) {
|
|
22
|
+
const code = char.charCodeAt(0);
|
|
23
|
+
if (code <= 57) return code - 48;
|
|
24
|
+
if (code <= 90) return code - 55;
|
|
25
|
+
return code - 61;
|
|
26
|
+
}
|
|
27
|
+
function validateOrderKey(key) {
|
|
28
|
+
if (key === SMALLEST_INTEGER) {
|
|
29
|
+
throw new Error("invalid order key: " + key);
|
|
30
|
+
}
|
|
31
|
+
const i = getIntegerPart(key);
|
|
32
|
+
if (key.length > i.length && key[key.length - 1] === ZERO) {
|
|
33
|
+
throw new Error("invalid order key: " + key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function midpoint(a, b) {
|
|
37
|
+
if (b != null && a >= b) {
|
|
38
|
+
throw new Error(a + " >= " + b);
|
|
39
|
+
}
|
|
40
|
+
if (a.slice(-1) === ZERO || b && b.slice(-1) === ZERO) {
|
|
41
|
+
throw new Error("trailing zero");
|
|
42
|
+
}
|
|
43
|
+
if (b) {
|
|
44
|
+
let n = 0;
|
|
45
|
+
while ((a[n] || ZERO) === b[n]) {
|
|
46
|
+
n++;
|
|
47
|
+
}
|
|
48
|
+
if (n > 0) {
|
|
49
|
+
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const digitA = a ? digitIndex(a[0]) : 0;
|
|
53
|
+
const digitB = b != null ? digitIndex(b[0]) : DIGITS.length;
|
|
54
|
+
if (digitB - digitA > 1) {
|
|
55
|
+
const midDigit = Math.round(0.5 * (digitA + digitB));
|
|
56
|
+
return DIGITS[midDigit];
|
|
57
|
+
}
|
|
58
|
+
if (b && b.length > 1) {
|
|
59
|
+
return b.slice(0, 1);
|
|
60
|
+
}
|
|
61
|
+
return DIGITS[digitA] + midpoint(a.slice(1), null);
|
|
62
|
+
}
|
|
63
|
+
function incrementInteger(x) {
|
|
64
|
+
const [head, ...digs] = x.split("");
|
|
65
|
+
let carry = true;
|
|
66
|
+
for (let i = digs.length - 1; carry && i >= 0; i--) {
|
|
67
|
+
const d = digitIndex(digs[i]) + 1;
|
|
68
|
+
if (d === DIGITS.length) {
|
|
69
|
+
digs[i] = ZERO;
|
|
70
|
+
} else {
|
|
71
|
+
digs[i] = DIGITS[d];
|
|
72
|
+
carry = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (carry) {
|
|
76
|
+
if (head === "Z") return "a" + ZERO;
|
|
77
|
+
if (head === "z") return null;
|
|
78
|
+
const h = String.fromCharCode(head.charCodeAt(0) + 1);
|
|
79
|
+
if (h > "a") {
|
|
80
|
+
digs.push(ZERO);
|
|
81
|
+
} else {
|
|
82
|
+
digs.pop();
|
|
83
|
+
}
|
|
84
|
+
return h + digs.join("");
|
|
85
|
+
}
|
|
86
|
+
return head + digs.join("");
|
|
87
|
+
}
|
|
88
|
+
function decrementInteger(x) {
|
|
89
|
+
const [head, ...digs] = x.split("");
|
|
90
|
+
let borrow = true;
|
|
91
|
+
for (let i = digs.length - 1; borrow && i >= 0; i--) {
|
|
92
|
+
const d = digitIndex(digs[i]) - 1;
|
|
93
|
+
if (d === -1) {
|
|
94
|
+
digs[i] = LARGEST_DIGIT;
|
|
95
|
+
} else {
|
|
96
|
+
digs[i] = DIGITS[d];
|
|
97
|
+
borrow = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (borrow) {
|
|
101
|
+
if (head === "a") return "Z" + LARGEST_DIGIT;
|
|
102
|
+
if (head === "A") return null;
|
|
103
|
+
const h = String.fromCharCode(head.charCodeAt(0) - 1);
|
|
104
|
+
if (h < "Z") {
|
|
105
|
+
digs.push(LARGEST_DIGIT);
|
|
106
|
+
} else {
|
|
107
|
+
digs.pop();
|
|
108
|
+
}
|
|
109
|
+
return h + digs.join("");
|
|
110
|
+
}
|
|
111
|
+
return head + digs.join("");
|
|
112
|
+
}
|
|
113
|
+
function keyBetweenUnchecked(a, b) {
|
|
114
|
+
if (a != null && b != null && a >= b) {
|
|
115
|
+
throw new Error(a + " >= " + b);
|
|
116
|
+
}
|
|
117
|
+
if (a == null) {
|
|
118
|
+
if (b == null) return "a" + ZERO;
|
|
119
|
+
const ib2 = getIntegerPart(b);
|
|
120
|
+
const fb2 = b.slice(ib2.length);
|
|
121
|
+
if (ib2 === SMALLEST_INTEGER) {
|
|
122
|
+
return ib2 + midpoint("", fb2);
|
|
123
|
+
}
|
|
124
|
+
if (ib2 < b) return ib2;
|
|
125
|
+
const res = decrementInteger(ib2);
|
|
126
|
+
if (res == null) throw new Error("cannot decrement any more");
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
if (b == null) {
|
|
130
|
+
const ia2 = getIntegerPart(a);
|
|
131
|
+
const fa2 = a.slice(ia2.length);
|
|
132
|
+
const i2 = incrementInteger(ia2);
|
|
133
|
+
return i2 == null ? ia2 + midpoint(fa2, null) : i2;
|
|
134
|
+
}
|
|
135
|
+
const ia = getIntegerPart(a);
|
|
136
|
+
const fa = a.slice(ia.length);
|
|
137
|
+
const ib = getIntegerPart(b);
|
|
138
|
+
const fb = b.slice(ib.length);
|
|
139
|
+
if (ia === ib) {
|
|
140
|
+
return ia + midpoint(fa, fb);
|
|
141
|
+
}
|
|
142
|
+
const i = incrementInteger(ia);
|
|
143
|
+
if (i == null) throw new Error("cannot increment any more");
|
|
144
|
+
if (i < b) return i;
|
|
145
|
+
return ia + midpoint(fa, null);
|
|
146
|
+
}
|
|
147
|
+
function nKeysBetweenUnchecked(a, b, n) {
|
|
148
|
+
if (n === 0) return [];
|
|
149
|
+
if (n === 1) return [keyBetweenUnchecked(a, b)];
|
|
150
|
+
if (b == null) {
|
|
151
|
+
let c2 = keyBetweenUnchecked(a, b);
|
|
152
|
+
const result = [c2];
|
|
153
|
+
for (let i = 0; i < n - 1; i++) {
|
|
154
|
+
c2 = keyBetweenUnchecked(c2, b);
|
|
155
|
+
result.push(c2);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
if (a == null) {
|
|
160
|
+
let c2 = keyBetweenUnchecked(a, b);
|
|
161
|
+
const result = [c2];
|
|
162
|
+
for (let i = 0; i < n - 1; i++) {
|
|
163
|
+
c2 = keyBetweenUnchecked(a, c2);
|
|
164
|
+
result.push(c2);
|
|
165
|
+
}
|
|
166
|
+
result.reverse();
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
const mid = Math.floor(n / 2);
|
|
170
|
+
const c = keyBetweenUnchecked(a, b);
|
|
171
|
+
return [...nKeysBetweenUnchecked(a, c, mid), c, ...nKeysBetweenUnchecked(c, b, n - mid - 1)];
|
|
172
|
+
}
|
|
173
|
+
function jitterBetween(low, high) {
|
|
174
|
+
let mid = keyBetweenUnchecked(low, high);
|
|
175
|
+
for (let i = 0; i < JITTER_BITS; i++) {
|
|
176
|
+
if (Math.random() < 0.5) {
|
|
177
|
+
low = mid;
|
|
178
|
+
} else {
|
|
179
|
+
high = mid;
|
|
180
|
+
}
|
|
181
|
+
mid = keyBetweenUnchecked(low, high);
|
|
182
|
+
}
|
|
183
|
+
return mid;
|
|
184
|
+
}
|
|
185
|
+
function generateNJitteredKeysBetween(a, b, n) {
|
|
186
|
+
if (n === 0) return [];
|
|
187
|
+
if (a != null) validateOrderKey(a);
|
|
188
|
+
if (b != null) validateOrderKey(b);
|
|
189
|
+
const keys = nKeysBetweenUnchecked(a, b, n + 1);
|
|
190
|
+
const result = new Array(n);
|
|
191
|
+
for (let i = 0; i < n; i++) {
|
|
192
|
+
result[i] = jitterBetween(keys[i], keys[i + 1]);
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
function generateNKeysBetween(a, b, n) {
|
|
197
|
+
if (n === 0) return [];
|
|
198
|
+
if (a != null) validateOrderKey(a);
|
|
199
|
+
if (b != null) validateOrderKey(b);
|
|
200
|
+
return nKeysBetweenUnchecked(a, b, n);
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
generateNJitteredKeysBetween,
|
|
204
|
+
generateNKeysBetween,
|
|
205
|
+
validateOrderKey
|
|
206
|
+
};
|
|
207
|
+
//# sourceMappingURL=fractionalIndexing.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/fractionalIndexing.ts"],
|
|
4
|
+
"sourcesContent": ["// Fractional indexing, specialized to tldraw's needs.\n//\n// This is a vendored and trimmed version of the `fractional-indexing` (by\n// arv@rocicorp.dev) and `jittered-fractional-indexing` (by\n// github@nathanhleung.com) packages, both released under CC0-1.0 (public\n// domain). See https://observablehq.com/@dgreensp/implementing-fractional-indexing\n// for the original algorithm.\n//\n// We only ever use the default base-62 alphabet, so the `digits` parameter has\n// been specialized away. This lets us hoist the per-call allocations that the\n// upstream `validateOrderKey` made (notably `digits[0].repeat(26)`) into module\n// constants, and skip redundant re-validation inside the jitter loop, which are\n// both on hot paths (every index generation and every IndexKey validation).\n\n// Digits must be in ascending character-code order.\nconst DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst ZERO = DIGITS[0] // '0'\nconst LARGEST_DIGIT = DIGITS[DIGITS.length - 1] // 'z'\n// The reserved \"smallest integer\" key, which has no valid predecessor.\nconst SMALLEST_INTEGER = 'A' + ZERO.repeat(26)\n// Number of random bisections used to spread concurrently-generated keys apart,\n// reducing the chance of collisions when multiple clients insert into the same\n// gap at once. Each bit costs one extra key generation and ~0.17 characters of\n// key length, so this trades collision resistance against compute and key size.\n// 16 keeps collisions below ~0.1% even for ~10 simultaneous same-position\n// inserts, which is ample headroom for real multiplayer use.\nconst JITTER_BITS = 16\n\nfunction getIntegerLength(head: string): number {\n\tif (head >= 'a' && head <= 'z') {\n\t\treturn head.charCodeAt(0) - 'a'.charCodeAt(0) + 2\n\t} else if (head >= 'A' && head <= 'Z') {\n\t\treturn 'Z'.charCodeAt(0) - head.charCodeAt(0) + 2\n\t}\n\tthrow new Error('invalid order key head: ' + head)\n}\n\nfunction getIntegerPart(key: string): string {\n\tconst integerPartLength = getIntegerLength(key[0])\n\tif (integerPartLength > key.length) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n\treturn key.slice(0, integerPartLength)\n}\n\n// Map a base-62 digit to its value. Faster than DIGITS.indexOf() (which scans\n// the alphabet) on the hot key-generation path. DIGITS is in char-code order:\n// '0'-'9' -> 0-9, 'A'-'Z' -> 10-35, 'a'-'z' -> 36-61.\nfunction digitIndex(char: string): number {\n\tconst code = char.charCodeAt(0)\n\tif (code <= 57) return code - 48\n\tif (code <= 90) return code - 55\n\treturn code - 61\n}\n\n/**\n * Throws if `key` is not a canonical order key. Allocation-free in the common\n * case: no `repeat`/`slice` per call.\n */\nexport function validateOrderKey(key: string): void {\n\tif (key === SMALLEST_INTEGER) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n\t// getIntegerPart throws if the first character is bad or the key is too short.\n\tconst i = getIntegerPart(key)\n\t// The fractional part (everything after the integer part) must not end in the\n\t// smallest digit, as a trailing zero is a non-canonical representation.\n\tif (key.length > i.length && key[key.length - 1] === ZERO) {\n\t\tthrow new Error('invalid order key: ' + key)\n\t}\n}\n\n// `a` may be empty string, `b` is null or non-empty string.\n// `a < b` lexicographically if `b` is non-null. No trailing zeros allowed.\nfunction midpoint(a: string, b: string | null): string {\n\tif (b != null && a >= b) {\n\t\tthrow new Error(a + ' >= ' + b)\n\t}\n\tif (a.slice(-1) === ZERO || (b && b.slice(-1) === ZERO)) {\n\t\tthrow new Error('trailing zero')\n\t}\n\tif (b) {\n\t\t// Remove the longest common prefix, padding `a` with zeros as we go. We\n\t\t// don't need to pad `b`, because it can't end before `a` while traversing\n\t\t// the common prefix.\n\t\tlet n = 0\n\t\twhile ((a[n] || ZERO) === b[n]) {\n\t\t\tn++\n\t\t}\n\t\tif (n > 0) {\n\t\t\treturn b.slice(0, n) + midpoint(a.slice(n), b.slice(n))\n\t\t}\n\t}\n\t// First digits (or lack of digit) are different.\n\tconst digitA = a ? digitIndex(a[0]) : 0\n\tconst digitB = b != null ? digitIndex(b[0]) : DIGITS.length\n\tif (digitB - digitA > 1) {\n\t\tconst midDigit = Math.round(0.5 * (digitA + digitB))\n\t\treturn DIGITS[midDigit]\n\t}\n\t// First digits are consecutive.\n\tif (b && b.length > 1) {\n\t\treturn b.slice(0, 1)\n\t}\n\t// `b` is null or has length 1 (a single digit). The first digit of `a` is the\n\t// previous digit to `b`, or the largest digit if `b` is null.\n\treturn DIGITS[digitA] + midpoint(a.slice(1), null)\n}\n\n// May return null, as there is a largest integer.\nfunction incrementInteger(x: string): string | null {\n\tconst [head, ...digs] = x.split('')\n\tlet carry = true\n\tfor (let i = digs.length - 1; carry && i >= 0; i--) {\n\t\tconst d = digitIndex(digs[i]) + 1\n\t\tif (d === DIGITS.length) {\n\t\t\tdigs[i] = ZERO\n\t\t} else {\n\t\t\tdigs[i] = DIGITS[d]\n\t\t\tcarry = false\n\t\t}\n\t}\n\tif (carry) {\n\t\tif (head === 'Z') return 'a' + ZERO\n\t\tif (head === 'z') return null\n\t\tconst h = String.fromCharCode(head.charCodeAt(0) + 1)\n\t\tif (h > 'a') {\n\t\t\tdigs.push(ZERO)\n\t\t} else {\n\t\t\tdigs.pop()\n\t\t}\n\t\treturn h + digs.join('')\n\t}\n\treturn head + digs.join('')\n}\n\n// May return null, as there is a smallest integer.\nfunction decrementInteger(x: string): string | null {\n\tconst [head, ...digs] = x.split('')\n\tlet borrow = true\n\tfor (let i = digs.length - 1; borrow && i >= 0; i--) {\n\t\tconst d = digitIndex(digs[i]) - 1\n\t\tif (d === -1) {\n\t\t\tdigs[i] = LARGEST_DIGIT\n\t\t} else {\n\t\t\tdigs[i] = DIGITS[d]\n\t\t\tborrow = false\n\t\t}\n\t}\n\tif (borrow) {\n\t\tif (head === 'a') return 'Z' + LARGEST_DIGIT\n\t\tif (head === 'A') return null\n\t\tconst h = String.fromCharCode(head.charCodeAt(0) - 1)\n\t\tif (h < 'Z') {\n\t\t\tdigs.push(LARGEST_DIGIT)\n\t\t} else {\n\t\t\tdigs.pop()\n\t\t}\n\t\treturn h + digs.join('')\n\t}\n\treturn head + digs.join('')\n}\n\n// Generate a key strictly between `a` and `b`, without validating the inputs.\n// `a`/`b` are order keys or null (START/END). `a < b` if both are non-null.\nfunction keyBetweenUnchecked(a: string | null, b: string | null): string {\n\tif (a != null && b != null && a >= b) {\n\t\tthrow new Error(a + ' >= ' + b)\n\t}\n\tif (a == null) {\n\t\tif (b == null) return 'a' + ZERO\n\t\tconst ib = getIntegerPart(b)\n\t\tconst fb = b.slice(ib.length)\n\t\tif (ib === SMALLEST_INTEGER) {\n\t\t\treturn ib + midpoint('', fb)\n\t\t}\n\t\tif (ib < b) return ib\n\t\tconst res = decrementInteger(ib)\n\t\tif (res == null) throw new Error('cannot decrement any more')\n\t\treturn res\n\t}\n\tif (b == null) {\n\t\tconst ia = getIntegerPart(a)\n\t\tconst fa = a.slice(ia.length)\n\t\tconst i = incrementInteger(ia)\n\t\treturn i == null ? ia + midpoint(fa, null) : i\n\t}\n\tconst ia = getIntegerPart(a)\n\tconst fa = a.slice(ia.length)\n\tconst ib = getIntegerPart(b)\n\tconst fb = b.slice(ib.length)\n\tif (ia === ib) {\n\t\treturn ia + midpoint(fa, fb)\n\t}\n\tconst i = incrementInteger(ia)\n\tif (i == null) throw new Error('cannot increment any more')\n\tif (i < b) return i\n\treturn ia + midpoint(fa, null)\n}\n\n// Generate `n` keys between `a` and `b`, without validating the inputs.\n// Intermediate keys are valid by construction, so they're never re-validated.\nfunction nKeysBetweenUnchecked(a: string | null, b: string | null, n: number): string[] {\n\tif (n === 0) return []\n\tif (n === 1) return [keyBetweenUnchecked(a, b)]\n\tif (b == null) {\n\t\tlet c = keyBetweenUnchecked(a, b)\n\t\tconst result = [c]\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tc = keyBetweenUnchecked(c, b)\n\t\t\tresult.push(c)\n\t\t}\n\t\treturn result\n\t}\n\tif (a == null) {\n\t\tlet c = keyBetweenUnchecked(a, b)\n\t\tconst result = [c]\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tc = keyBetweenUnchecked(a, c)\n\t\t\tresult.push(c)\n\t\t}\n\t\tresult.reverse()\n\t\treturn result\n\t}\n\tconst mid = Math.floor(n / 2)\n\tconst c = keyBetweenUnchecked(a, b)\n\treturn [...nKeysBetweenUnchecked(a, c, mid), c, ...nKeysBetweenUnchecked(c, b, n - mid - 1)]\n}\n\n// Bisect `[low, high]` JITTER_BITS times, following a random walk, so that keys\n// generated concurrently land in different sub-ranges. `low`/`high` must already\n// be valid (or null); the intermediate midpoints are valid by construction.\nfunction jitterBetween(low: string | null, high: string | null): string {\n\tlet mid = keyBetweenUnchecked(low, high)\n\tfor (let i = 0; i < JITTER_BITS; i++) {\n\t\tif (Math.random() < 0.5) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t\tmid = keyBetweenUnchecked(low, high)\n\t}\n\treturn mid\n}\n\n/**\n * Generate `n` jittered keys evenly spaced between `a` and `b`. Inputs are\n * validated once; everything downstream is generated, hence trusted.\n */\nexport function generateNJitteredKeysBetween(\n\ta: string | null,\n\tb: string | null,\n\tn: number\n): string[] {\n\tif (n === 0) return []\n\tif (a != null) validateOrderKey(a)\n\tif (b != null) validateOrderKey(b)\n\tconst keys = nKeysBetweenUnchecked(a, b, n + 1)\n\tconst result = new Array<string>(n)\n\tfor (let i = 0; i < n; i++) {\n\t\tresult[i] = jitterBetween(keys[i], keys[i + 1])\n\t}\n\treturn result\n}\n\n/**\n * Generate `n` keys evenly spaced between `a` and `b`, without jitter. Used in\n * tests for deterministic output.\n */\nexport function generateNKeysBetween(a: string | null, b: string | null, n: number): string[] {\n\tif (n === 0) return []\n\tif (a != null) validateOrderKey(a)\n\tif (b != null) validateOrderKey(b)\n\treturn nKeysBetweenUnchecked(a, b, n)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["ib", "fb", "ia", "fa", "i", "c"]
|
|
7
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
generateNJitteredKeysBetween,
|
|
3
|
+
generateNKeysBetween,
|
|
4
|
+
validateOrderKey
|
|
5
|
+
} from "./fractionalIndexing.mjs";
|
|
6
|
+
const generateKeysFn = process.env.NODE_ENV === "test" ? generateNKeysBetween : generateNJitteredKeysBetween;
|
|
6
7
|
const ZERO_INDEX_KEY = "a0";
|
|
7
8
|
function validateIndexKey(index) {
|
|
8
9
|
try {
|
|
9
|
-
|
|
10
|
+
validateOrderKey(index);
|
|
10
11
|
} catch {
|
|
11
12
|
throw new Error("invalid index: " + index);
|
|
12
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/reordering.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tgenerateNJitteredKeysBetween,\n\tgenerateNKeysBetween,\n\tvalidateOrderKey,\n} from './fractionalIndexing'\n\nconst generateKeysFn =\n\tprocess.env.NODE_ENV === 'test' ? generateNKeysBetween : generateNJitteredKeysBetween\n\n/**\n * A string made up of an integer part followed by a fraction part. The fraction point consists of\n * zero or more digits with no trailing zeros. Based on\n * {@link https://observablehq.com/@dgreensp/implementing-fractional-indexing}.\n *\n * @public\n */\nexport type IndexKey = string & { __brand: 'indexKey' }\n\n/**\n * The index key for the first index - 'a0'.\n * @public\n */\nexport const ZERO_INDEX_KEY = 'a0' as IndexKey\n\n/**\n * Validates that a string is a valid IndexKey.\n * @param index - The string to validate.\n * @throws Error if the index is invalid.\n * @internal\n */\nexport function validateIndexKey(index: string): asserts index is IndexKey {\n\ttry {\n\t\tvalidateOrderKey(index)\n\t} catch {\n\t\tthrow new Error('invalid index: ' + index)\n\t}\n}\n\n/**\n * Get a number of indices between two indices.\n * @param below - The index below.\n * @param above - The index above.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values between below and above.\n * @example\n * ```ts\n * const indices = getIndicesBetween('a0' as IndexKey, 'a2' as IndexKey, 2)\n * console.log(indices) // ['a0V', 'a1']\n * ```\n * @public\n */\nexport function getIndicesBetween(\n\tbelow: IndexKey | null | undefined,\n\tabove: IndexKey | null | undefined,\n\tn: number\n) {\n\treturn generateKeysFn(below ?? null, above ?? null, n) as IndexKey[]\n}\n\n/**\n * Get a number of indices above an index.\n * @param below - The index below.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values above the given index.\n * @example\n * ```ts\n * const indices = getIndicesAbove('a0' as IndexKey, 3)\n * console.log(indices) // ['a1', 'a2', 'a3']\n * ```\n * @public\n */\nexport function getIndicesAbove(below: IndexKey | null | undefined, n: number) {\n\treturn generateKeysFn(below ?? null, null, n) as IndexKey[]\n}\n\n/**\n * Get a number of indices below an index.\n * @param above - The index above.\n * @param n - The number of indices to get.\n * @returns An array of n IndexKey values below the given index.\n * @example\n * ```ts\n * const indices = getIndicesBelow('a2' as IndexKey, 2)\n * console.log(indices) // ['a1', 'a0V']\n * ```\n * @public\n */\nexport function getIndicesBelow(above: IndexKey | null | undefined, n: number) {\n\treturn generateKeysFn(null, above ?? null, n) as IndexKey[]\n}\n\n/**\n * Get the index between two indices.\n * @param below - The index below.\n * @param above - The index above.\n * @returns A single IndexKey value between below and above.\n * @example\n * ```ts\n * const index = getIndexBetween('a0' as IndexKey, 'a2' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexBetween(\n\tbelow: IndexKey | null | undefined,\n\tabove: IndexKey | null | undefined\n) {\n\treturn generateKeysFn(below ?? null, above ?? null, 1)[0] as IndexKey\n}\n\n/**\n * Get the index above a given index.\n * @param below - The index below.\n * @returns An IndexKey value above the given index.\n * @example\n * ```ts\n * const index = getIndexAbove('a0' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexAbove(below: IndexKey | null | undefined = null) {\n\treturn generateKeysFn(below, null, 1)[0] as IndexKey\n}\n\n/**\n * Get the index below a given index.\n * @param above - The index above.\n * @returns An IndexKey value below the given index.\n * @example\n * ```ts\n * const index = getIndexBelow('a2' as IndexKey)\n * console.log(index) // 'a1'\n * ```\n * @public\n */\nexport function getIndexBelow(above: IndexKey | null | undefined = null) {\n\treturn generateKeysFn(null, above, 1)[0] as IndexKey\n}\n\n/**\n * Get n number of indices, starting at an index.\n * @param n - The number of indices to get.\n * @param start - The index to start at.\n * @returns An array containing the start index plus n additional IndexKey values.\n * @example\n * ```ts\n * const indices = getIndices(3, 'a1' as IndexKey)\n * console.log(indices) // ['a1', 'a2', 'a3', 'a4']\n * ```\n * @public\n */\nexport function getIndices(n: number, start = 'a1' as IndexKey) {\n\treturn [start, ...generateKeysFn(start, null, n)] as IndexKey[]\n}\n\n/**\n * Sort by index.\n * @param a - An object with an index property.\n * @param b - An object with an index property.\n * @returns A number indicating sort order (-1, 0, or 1).\n * @example\n * ```ts\n * const shapes = [\n * { id: 'b', index: 'a2' as IndexKey },\n * { id: 'a', index: 'a1' as IndexKey }\n * ]\n * const sorted = shapes.sort(sortByIndex)\n * console.log(sorted) // [{ id: 'a', index: 'a1' }, { id: 'b', index: 'a2' }]\n * ```\n * @public\n */\nexport function sortByIndex<T extends { index: IndexKey }>(a: T, b: T) {\n\tif (a.index < b.index) {\n\t\treturn -1\n\t} else if (a.index > b.index) {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n/**\n * Sort by index, or null.\n * @param a - An object with an index property.\n * @param b - An object with an index property.\n * @public\n */\nexport function sortByMaybeIndex<T extends { index?: IndexKey | null }>(a: T, b: T) {\n\tif (a.index && b.index) {\n\t\treturn a.index < b.index ? -1 : 1\n\t}\n\tif (a.index && b.index == null) {\n\t\treturn -1\n\t}\n\tif (a.index == null && b.index == null) {\n\t\treturn 0\n\t}\n\treturn 1\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAEP,MAAM,iBACL,QAAQ,IAAI,aAAa,SAAS,uBAAuB;AAenD,MAAM,iBAAiB;AAQvB,SAAS,iBAAiB,OAA0C;AAC1E,MAAI;AACH,qBAAiB,KAAK;AAAA,EACvB,QAAQ;AACP,UAAM,IAAI,MAAM,oBAAoB,KAAK;AAAA,EAC1C;AACD;AAeO,SAAS,kBACf,OACA,OACA,GACC;AACD,SAAO,eAAe,SAAS,MAAM,SAAS,MAAM,CAAC;AACtD;AAcO,SAAS,gBAAgB,OAAoC,GAAW;AAC9E,SAAO,eAAe,SAAS,MAAM,MAAM,CAAC;AAC7C;AAcO,SAAS,gBAAgB,OAAoC,GAAW;AAC9E,SAAO,eAAe,MAAM,SAAS,MAAM,CAAC;AAC7C;AAcO,SAAS,gBACf,OACA,OACC;AACD,SAAO,eAAe,SAAS,MAAM,SAAS,MAAM,CAAC,EAAE,CAAC;AACzD;AAaO,SAAS,cAAc,QAAqC,MAAM;AACxE,SAAO,eAAe,OAAO,MAAM,CAAC,EAAE,CAAC;AACxC;AAaO,SAAS,cAAc,QAAqC,MAAM;AACxE,SAAO,eAAe,MAAM,OAAO,CAAC,EAAE,CAAC;AACxC;AAcO,SAAS,WAAW,GAAW,QAAQ,MAAkB;AAC/D,SAAO,CAAC,OAAO,GAAG,eAAe,OAAO,MAAM,CAAC,CAAC;AACjD;AAkBO,SAAS,YAA2C,GAAM,GAAM;AACtE,MAAI,EAAE,QAAQ,EAAE,OAAO;AACtB,WAAO;AAAA,EACR,WAAW,EAAE,QAAQ,EAAE,OAAO;AAC7B,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAQO,SAAS,iBAAwD,GAAM,GAAM;AACnF,MAAI,EAAE,SAAS,EAAE,OAAO;AACvB,WAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACjC;AACA,MAAI,EAAE,SAAS,EAAE,SAAS,MAAM;AAC/B,WAAO;AAAA,EACR;AACA,MAAI,EAAE,SAAS,QAAQ,EAAE,SAAS,MAAM;AACvC,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/utils",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (private utilities).",
|
|
4
|
-
"version": "5.2.0-next.
|
|
4
|
+
"version": "5.2.0-next.7b677d60c152",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"jittered-fractional-indexing": "^1.0.0",
|
|
48
47
|
"lodash.isequal": "^4.5.0",
|
|
49
48
|
"lodash.isequalwith": "^4.4.0",
|
|
50
49
|
"lodash.throttle": "^4.1.1",
|
|
@@ -58,6 +57,9 @@
|
|
|
58
57
|
"lazyrepo": "0.0.0-alpha.27",
|
|
59
58
|
"vitest": "^4.1.7"
|
|
60
59
|
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=22.12.0"
|
|
62
|
+
},
|
|
61
63
|
"module": "dist-esm/index.mjs",
|
|
62
64
|
"source": "src/index.ts",
|
|
63
65
|
"exports": {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
generateNJitteredKeysBetween,
|
|
4
|
+
generateNKeysBetween,
|
|
5
|
+
validateOrderKey,
|
|
6
|
+
} from './fractionalIndexing'
|
|
7
|
+
|
|
8
|
+
// Single-key helper: the non-jittered generator with n=1.
|
|
9
|
+
const keyBetween = (a: string | null, b: string | null) => generateNKeysBetween(a, b, 1)[0]
|
|
10
|
+
|
|
11
|
+
describe('fractionalIndexing', () => {
|
|
12
|
+
// Golden vectors from the upstream `fractional-indexing` README. These pin the
|
|
13
|
+
// exact base-62 output, proving the vendored/specialized core stays
|
|
14
|
+
// byte-for-byte compatible with the original library.
|
|
15
|
+
describe('generateKeyBetween (golden vectors)', () => {
|
|
16
|
+
it.each([
|
|
17
|
+
[null, null, 'a0'],
|
|
18
|
+
['a0', null, 'a1'],
|
|
19
|
+
['a1', null, 'a2'],
|
|
20
|
+
[null, 'a0', 'Zz'],
|
|
21
|
+
['a0', 'a1', 'a0V'],
|
|
22
|
+
['a1', 'a2', 'a1V'],
|
|
23
|
+
])('between %o and %o is %o', (a, b, expected) => {
|
|
24
|
+
expect(keyBetween(a, b)).toBe(expected)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('generateNKeysBetween (golden vectors)', () => {
|
|
29
|
+
it.each<[string | null, string | null, number, string[]]>([
|
|
30
|
+
[null, null, 2, ['a0', 'a1']],
|
|
31
|
+
['a1', null, 2, ['a2', 'a3']],
|
|
32
|
+
[null, 'a0', 2, ['Zy', 'Zz']],
|
|
33
|
+
['a0', 'a1', 2, ['a0G', 'a0V']],
|
|
34
|
+
[null, null, 5, ['a0', 'a1', 'a2', 'a3', 'a4']],
|
|
35
|
+
])('%o keys between %o and %o', (a, b, n, expected) => {
|
|
36
|
+
expect(generateNKeysBetween(a, b, n)).toEqual(expected)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns an empty array for n = 0', () => {
|
|
40
|
+
expect(generateNKeysBetween(null, null, 0)).toEqual([])
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('errors', () => {
|
|
45
|
+
it('throws when a >= b', () => {
|
|
46
|
+
expect(() => keyBetween('a1', 'a0')).toThrow()
|
|
47
|
+
expect(() => keyBetween('a0', 'a0')).toThrow()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('throws on invalid input keys', () => {
|
|
51
|
+
expect(() => keyBetween('a00', null)).toThrow() // trailing zero
|
|
52
|
+
expect(() => keyBetween('', null)).toThrow()
|
|
53
|
+
expect(() => keyBetween('foo', null)).toThrow()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('validateOrderKey', () => {
|
|
58
|
+
it.each(['a0', 'a1', 'a0V', 'Zz', 'a0G', 'b127'])('accepts %s', (key) => {
|
|
59
|
+
expect(() => validateOrderKey(key)).not.toThrow()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it.each([
|
|
63
|
+
'', // empty
|
|
64
|
+
'a', // integer part too short
|
|
65
|
+
'1', // invalid head
|
|
66
|
+
'a00', // trailing zero in fraction
|
|
67
|
+
'foo', // invalid head
|
|
68
|
+
'A' + '0'.repeat(26), // the reserved smallest-integer key
|
|
69
|
+
])('rejects %o', (key) => {
|
|
70
|
+
expect(() => validateOrderKey(key)).toThrow()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Invariant tests: independent of exact output, these catch ordering or
|
|
75
|
+
// digit-mapping regressions across the whole alphabet (e.g. a bad charCode
|
|
76
|
+
// offset in digitIndex, or carry/borrow bugs when integers roll over).
|
|
77
|
+
describe('invariants', () => {
|
|
78
|
+
it('generateNKeysBetween yields strictly increasing, valid, in-bounds keys', () => {
|
|
79
|
+
const keys = generateNKeysBetween('a0', 'a1', 200)
|
|
80
|
+
expect(keys).toHaveLength(200)
|
|
81
|
+
let prev = 'a0'
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
expect(() => validateOrderKey(key)).not.toThrow()
|
|
84
|
+
expect(key > prev).toBe(true)
|
|
85
|
+
expect(key < 'a1').toBe(true)
|
|
86
|
+
prev = key
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('stays ordered while repeatedly inserting after the last key (integer carries)', () => {
|
|
91
|
+
// 200 sequential keys force the integer part to roll over many digits,
|
|
92
|
+
// exercising incrementInteger across the full base-62 alphabet.
|
|
93
|
+
let key = keyBetween(null, null)
|
|
94
|
+
let prev = ''
|
|
95
|
+
for (let i = 0; i < 200; i++) {
|
|
96
|
+
expect(() => validateOrderKey(key)).not.toThrow()
|
|
97
|
+
expect(key > prev).toBe(true)
|
|
98
|
+
prev = key
|
|
99
|
+
key = keyBetween(key, null)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('stays ordered while repeatedly inserting before the first key (integer borrows)', () => {
|
|
104
|
+
let key = keyBetween(null, null)
|
|
105
|
+
let next = 'zzzzz'
|
|
106
|
+
for (let i = 0; i < 200; i++) {
|
|
107
|
+
expect(() => validateOrderKey(key)).not.toThrow()
|
|
108
|
+
expect(key < next).toBe(true)
|
|
109
|
+
next = key
|
|
110
|
+
key = keyBetween(null, key)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('generateNJitteredKeysBetween yields ordered, valid, in-bounds, distinct keys', () => {
|
|
115
|
+
const keys = generateNJitteredKeysBetween('a0', 'a5', 100)
|
|
116
|
+
expect(keys).toHaveLength(100)
|
|
117
|
+
expect(new Set(keys).size).toBe(100)
|
|
118
|
+
let prev = 'a0'
|
|
119
|
+
for (const key of keys) {
|
|
120
|
+
expect(() => validateOrderKey(key)).not.toThrow()
|
|
121
|
+
expect(key > prev).toBe(true)
|
|
122
|
+
expect(key < 'a5').toBe(true)
|
|
123
|
+
prev = key
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('jittered keys land strictly between their neighbours', () => {
|
|
128
|
+
const a = keyBetween(null, null)
|
|
129
|
+
const b = keyBetween(a, null)
|
|
130
|
+
for (let i = 0; i < 50; i++) {
|
|
131
|
+
const mid = generateNJitteredKeysBetween(a, b, 1)[0]
|
|
132
|
+
expect(mid > a).toBe(true)
|
|
133
|
+
expect(mid < b).toBe(true)
|
|
134
|
+
expect(() => validateOrderKey(mid)).not.toThrow()
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
})
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Fractional indexing, specialized to tldraw's needs.
|
|
2
|
+
//
|
|
3
|
+
// This is a vendored and trimmed version of the `fractional-indexing` (by
|
|
4
|
+
// arv@rocicorp.dev) and `jittered-fractional-indexing` (by
|
|
5
|
+
// github@nathanhleung.com) packages, both released under CC0-1.0 (public
|
|
6
|
+
// domain). See https://observablehq.com/@dgreensp/implementing-fractional-indexing
|
|
7
|
+
// for the original algorithm.
|
|
8
|
+
//
|
|
9
|
+
// We only ever use the default base-62 alphabet, so the `digits` parameter has
|
|
10
|
+
// been specialized away. This lets us hoist the per-call allocations that the
|
|
11
|
+
// upstream `validateOrderKey` made (notably `digits[0].repeat(26)`) into module
|
|
12
|
+
// constants, and skip redundant re-validation inside the jitter loop, which are
|
|
13
|
+
// both on hot paths (every index generation and every IndexKey validation).
|
|
14
|
+
|
|
15
|
+
// Digits must be in ascending character-code order.
|
|
16
|
+
const DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
17
|
+
const ZERO = DIGITS[0] // '0'
|
|
18
|
+
const LARGEST_DIGIT = DIGITS[DIGITS.length - 1] // 'z'
|
|
19
|
+
// The reserved "smallest integer" key, which has no valid predecessor.
|
|
20
|
+
const SMALLEST_INTEGER = 'A' + ZERO.repeat(26)
|
|
21
|
+
// Number of random bisections used to spread concurrently-generated keys apart,
|
|
22
|
+
// reducing the chance of collisions when multiple clients insert into the same
|
|
23
|
+
// gap at once. Each bit costs one extra key generation and ~0.17 characters of
|
|
24
|
+
// key length, so this trades collision resistance against compute and key size.
|
|
25
|
+
// 16 keeps collisions below ~0.1% even for ~10 simultaneous same-position
|
|
26
|
+
// inserts, which is ample headroom for real multiplayer use.
|
|
27
|
+
const JITTER_BITS = 16
|
|
28
|
+
|
|
29
|
+
function getIntegerLength(head: string): number {
|
|
30
|
+
if (head >= 'a' && head <= 'z') {
|
|
31
|
+
return head.charCodeAt(0) - 'a'.charCodeAt(0) + 2
|
|
32
|
+
} else if (head >= 'A' && head <= 'Z') {
|
|
33
|
+
return 'Z'.charCodeAt(0) - head.charCodeAt(0) + 2
|
|
34
|
+
}
|
|
35
|
+
throw new Error('invalid order key head: ' + head)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getIntegerPart(key: string): string {
|
|
39
|
+
const integerPartLength = getIntegerLength(key[0])
|
|
40
|
+
if (integerPartLength > key.length) {
|
|
41
|
+
throw new Error('invalid order key: ' + key)
|
|
42
|
+
}
|
|
43
|
+
return key.slice(0, integerPartLength)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Map a base-62 digit to its value. Faster than DIGITS.indexOf() (which scans
|
|
47
|
+
// the alphabet) on the hot key-generation path. DIGITS is in char-code order:
|
|
48
|
+
// '0'-'9' -> 0-9, 'A'-'Z' -> 10-35, 'a'-'z' -> 36-61.
|
|
49
|
+
function digitIndex(char: string): number {
|
|
50
|
+
const code = char.charCodeAt(0)
|
|
51
|
+
if (code <= 57) return code - 48
|
|
52
|
+
if (code <= 90) return code - 55
|
|
53
|
+
return code - 61
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Throws if `key` is not a canonical order key. Allocation-free in the common
|
|
58
|
+
* case: no `repeat`/`slice` per call.
|
|
59
|
+
*/
|
|
60
|
+
export function validateOrderKey(key: string): void {
|
|
61
|
+
if (key === SMALLEST_INTEGER) {
|
|
62
|
+
throw new Error('invalid order key: ' + key)
|
|
63
|
+
}
|
|
64
|
+
// getIntegerPart throws if the first character is bad or the key is too short.
|
|
65
|
+
const i = getIntegerPart(key)
|
|
66
|
+
// The fractional part (everything after the integer part) must not end in the
|
|
67
|
+
// smallest digit, as a trailing zero is a non-canonical representation.
|
|
68
|
+
if (key.length > i.length && key[key.length - 1] === ZERO) {
|
|
69
|
+
throw new Error('invalid order key: ' + key)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// `a` may be empty string, `b` is null or non-empty string.
|
|
74
|
+
// `a < b` lexicographically if `b` is non-null. No trailing zeros allowed.
|
|
75
|
+
function midpoint(a: string, b: string | null): string {
|
|
76
|
+
if (b != null && a >= b) {
|
|
77
|
+
throw new Error(a + ' >= ' + b)
|
|
78
|
+
}
|
|
79
|
+
if (a.slice(-1) === ZERO || (b && b.slice(-1) === ZERO)) {
|
|
80
|
+
throw new Error('trailing zero')
|
|
81
|
+
}
|
|
82
|
+
if (b) {
|
|
83
|
+
// Remove the longest common prefix, padding `a` with zeros as we go. We
|
|
84
|
+
// don't need to pad `b`, because it can't end before `a` while traversing
|
|
85
|
+
// the common prefix.
|
|
86
|
+
let n = 0
|
|
87
|
+
while ((a[n] || ZERO) === b[n]) {
|
|
88
|
+
n++
|
|
89
|
+
}
|
|
90
|
+
if (n > 0) {
|
|
91
|
+
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// First digits (or lack of digit) are different.
|
|
95
|
+
const digitA = a ? digitIndex(a[0]) : 0
|
|
96
|
+
const digitB = b != null ? digitIndex(b[0]) : DIGITS.length
|
|
97
|
+
if (digitB - digitA > 1) {
|
|
98
|
+
const midDigit = Math.round(0.5 * (digitA + digitB))
|
|
99
|
+
return DIGITS[midDigit]
|
|
100
|
+
}
|
|
101
|
+
// First digits are consecutive.
|
|
102
|
+
if (b && b.length > 1) {
|
|
103
|
+
return b.slice(0, 1)
|
|
104
|
+
}
|
|
105
|
+
// `b` is null or has length 1 (a single digit). The first digit of `a` is the
|
|
106
|
+
// previous digit to `b`, or the largest digit if `b` is null.
|
|
107
|
+
return DIGITS[digitA] + midpoint(a.slice(1), null)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// May return null, as there is a largest integer.
|
|
111
|
+
function incrementInteger(x: string): string | null {
|
|
112
|
+
const [head, ...digs] = x.split('')
|
|
113
|
+
let carry = true
|
|
114
|
+
for (let i = digs.length - 1; carry && i >= 0; i--) {
|
|
115
|
+
const d = digitIndex(digs[i]) + 1
|
|
116
|
+
if (d === DIGITS.length) {
|
|
117
|
+
digs[i] = ZERO
|
|
118
|
+
} else {
|
|
119
|
+
digs[i] = DIGITS[d]
|
|
120
|
+
carry = false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (carry) {
|
|
124
|
+
if (head === 'Z') return 'a' + ZERO
|
|
125
|
+
if (head === 'z') return null
|
|
126
|
+
const h = String.fromCharCode(head.charCodeAt(0) + 1)
|
|
127
|
+
if (h > 'a') {
|
|
128
|
+
digs.push(ZERO)
|
|
129
|
+
} else {
|
|
130
|
+
digs.pop()
|
|
131
|
+
}
|
|
132
|
+
return h + digs.join('')
|
|
133
|
+
}
|
|
134
|
+
return head + digs.join('')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// May return null, as there is a smallest integer.
|
|
138
|
+
function decrementInteger(x: string): string | null {
|
|
139
|
+
const [head, ...digs] = x.split('')
|
|
140
|
+
let borrow = true
|
|
141
|
+
for (let i = digs.length - 1; borrow && i >= 0; i--) {
|
|
142
|
+
const d = digitIndex(digs[i]) - 1
|
|
143
|
+
if (d === -1) {
|
|
144
|
+
digs[i] = LARGEST_DIGIT
|
|
145
|
+
} else {
|
|
146
|
+
digs[i] = DIGITS[d]
|
|
147
|
+
borrow = false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (borrow) {
|
|
151
|
+
if (head === 'a') return 'Z' + LARGEST_DIGIT
|
|
152
|
+
if (head === 'A') return null
|
|
153
|
+
const h = String.fromCharCode(head.charCodeAt(0) - 1)
|
|
154
|
+
if (h < 'Z') {
|
|
155
|
+
digs.push(LARGEST_DIGIT)
|
|
156
|
+
} else {
|
|
157
|
+
digs.pop()
|
|
158
|
+
}
|
|
159
|
+
return h + digs.join('')
|
|
160
|
+
}
|
|
161
|
+
return head + digs.join('')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Generate a key strictly between `a` and `b`, without validating the inputs.
|
|
165
|
+
// `a`/`b` are order keys or null (START/END). `a < b` if both are non-null.
|
|
166
|
+
function keyBetweenUnchecked(a: string | null, b: string | null): string {
|
|
167
|
+
if (a != null && b != null && a >= b) {
|
|
168
|
+
throw new Error(a + ' >= ' + b)
|
|
169
|
+
}
|
|
170
|
+
if (a == null) {
|
|
171
|
+
if (b == null) return 'a' + ZERO
|
|
172
|
+
const ib = getIntegerPart(b)
|
|
173
|
+
const fb = b.slice(ib.length)
|
|
174
|
+
if (ib === SMALLEST_INTEGER) {
|
|
175
|
+
return ib + midpoint('', fb)
|
|
176
|
+
}
|
|
177
|
+
if (ib < b) return ib
|
|
178
|
+
const res = decrementInteger(ib)
|
|
179
|
+
if (res == null) throw new Error('cannot decrement any more')
|
|
180
|
+
return res
|
|
181
|
+
}
|
|
182
|
+
if (b == null) {
|
|
183
|
+
const ia = getIntegerPart(a)
|
|
184
|
+
const fa = a.slice(ia.length)
|
|
185
|
+
const i = incrementInteger(ia)
|
|
186
|
+
return i == null ? ia + midpoint(fa, null) : i
|
|
187
|
+
}
|
|
188
|
+
const ia = getIntegerPart(a)
|
|
189
|
+
const fa = a.slice(ia.length)
|
|
190
|
+
const ib = getIntegerPart(b)
|
|
191
|
+
const fb = b.slice(ib.length)
|
|
192
|
+
if (ia === ib) {
|
|
193
|
+
return ia + midpoint(fa, fb)
|
|
194
|
+
}
|
|
195
|
+
const i = incrementInteger(ia)
|
|
196
|
+
if (i == null) throw new Error('cannot increment any more')
|
|
197
|
+
if (i < b) return i
|
|
198
|
+
return ia + midpoint(fa, null)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Generate `n` keys between `a` and `b`, without validating the inputs.
|
|
202
|
+
// Intermediate keys are valid by construction, so they're never re-validated.
|
|
203
|
+
function nKeysBetweenUnchecked(a: string | null, b: string | null, n: number): string[] {
|
|
204
|
+
if (n === 0) return []
|
|
205
|
+
if (n === 1) return [keyBetweenUnchecked(a, b)]
|
|
206
|
+
if (b == null) {
|
|
207
|
+
let c = keyBetweenUnchecked(a, b)
|
|
208
|
+
const result = [c]
|
|
209
|
+
for (let i = 0; i < n - 1; i++) {
|
|
210
|
+
c = keyBetweenUnchecked(c, b)
|
|
211
|
+
result.push(c)
|
|
212
|
+
}
|
|
213
|
+
return result
|
|
214
|
+
}
|
|
215
|
+
if (a == null) {
|
|
216
|
+
let c = keyBetweenUnchecked(a, b)
|
|
217
|
+
const result = [c]
|
|
218
|
+
for (let i = 0; i < n - 1; i++) {
|
|
219
|
+
c = keyBetweenUnchecked(a, c)
|
|
220
|
+
result.push(c)
|
|
221
|
+
}
|
|
222
|
+
result.reverse()
|
|
223
|
+
return result
|
|
224
|
+
}
|
|
225
|
+
const mid = Math.floor(n / 2)
|
|
226
|
+
const c = keyBetweenUnchecked(a, b)
|
|
227
|
+
return [...nKeysBetweenUnchecked(a, c, mid), c, ...nKeysBetweenUnchecked(c, b, n - mid - 1)]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Bisect `[low, high]` JITTER_BITS times, following a random walk, so that keys
|
|
231
|
+
// generated concurrently land in different sub-ranges. `low`/`high` must already
|
|
232
|
+
// be valid (or null); the intermediate midpoints are valid by construction.
|
|
233
|
+
function jitterBetween(low: string | null, high: string | null): string {
|
|
234
|
+
let mid = keyBetweenUnchecked(low, high)
|
|
235
|
+
for (let i = 0; i < JITTER_BITS; i++) {
|
|
236
|
+
if (Math.random() < 0.5) {
|
|
237
|
+
low = mid
|
|
238
|
+
} else {
|
|
239
|
+
high = mid
|
|
240
|
+
}
|
|
241
|
+
mid = keyBetweenUnchecked(low, high)
|
|
242
|
+
}
|
|
243
|
+
return mid
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Generate `n` jittered keys evenly spaced between `a` and `b`. Inputs are
|
|
248
|
+
* validated once; everything downstream is generated, hence trusted.
|
|
249
|
+
*/
|
|
250
|
+
export function generateNJitteredKeysBetween(
|
|
251
|
+
a: string | null,
|
|
252
|
+
b: string | null,
|
|
253
|
+
n: number
|
|
254
|
+
): string[] {
|
|
255
|
+
if (n === 0) return []
|
|
256
|
+
if (a != null) validateOrderKey(a)
|
|
257
|
+
if (b != null) validateOrderKey(b)
|
|
258
|
+
const keys = nKeysBetweenUnchecked(a, b, n + 1)
|
|
259
|
+
const result = new Array<string>(n)
|
|
260
|
+
for (let i = 0; i < n; i++) {
|
|
261
|
+
result[i] = jitterBetween(keys[i], keys[i + 1])
|
|
262
|
+
}
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Generate `n` keys evenly spaced between `a` and `b`, without jitter. Used in
|
|
268
|
+
* tests for deterministic output.
|
|
269
|
+
*/
|
|
270
|
+
export function generateNKeysBetween(a: string | null, b: string | null, n: number): string[] {
|
|
271
|
+
if (n === 0) return []
|
|
272
|
+
if (a != null) validateOrderKey(a)
|
|
273
|
+
if (b != null) validateOrderKey(b)
|
|
274
|
+
return nKeysBetweenUnchecked(a, b, n)
|
|
275
|
+
}
|
package/src/lib/reordering.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
generateNJitteredKeysBetween,
|
|
3
|
+
generateNKeysBetween,
|
|
4
|
+
validateOrderKey,
|
|
5
|
+
} from './fractionalIndexing'
|
|
6
6
|
|
|
7
7
|
const generateKeysFn =
|
|
8
|
-
process.env.NODE_ENV === 'test' ?
|
|
8
|
+
process.env.NODE_ENV === 'test' ? generateNKeysBetween : generateNJitteredKeysBetween
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A string made up of an integer part followed by a fraction part. The fraction point consists of
|
|
@@ -30,7 +30,7 @@ export const ZERO_INDEX_KEY = 'a0' as IndexKey
|
|
|
30
30
|
*/
|
|
31
31
|
export function validateIndexKey(index: string): asserts index is IndexKey {
|
|
32
32
|
try {
|
|
33
|
-
|
|
33
|
+
validateOrderKey(index)
|
|
34
34
|
} catch {
|
|
35
35
|
throw new Error('invalid index: ' + index)
|
|
36
36
|
}
|