@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 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.79b13319d317",
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 import_jittered_fractional_indexing = require("jittered-fractional-indexing");
35
- const generateNKeysBetweenWithNoJitter = (a, b, n) => {
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, import_jittered_fractional_indexing.generateKeyBetween)(index, null);
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 { generateKeyBetween, generateNKeysBetween } from 'jittered-fractional-indexing'\n\nconst generateNKeysBetweenWithNoJitter = (a: string | null, b: string | null, n: number) => {\n\treturn generateNKeysBetween(a, b, n, { jitterBits: 0 })\n}\n\nconst generateKeysFn =\n\tprocess.env.NODE_ENV === 'test' ? generateNKeysBetweenWithNoJitter : generateNKeysBetween\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\tgenerateKeyBetween(index, null)\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,0CAAyD;AAEzD,MAAM,mCAAmC,CAAC,GAAkB,GAAkB,MAAc;AAC3F,aAAO,0DAAqB,GAAG,GAAG,GAAG,EAAE,YAAY,EAAE,CAAC;AACvD;AAEA,MAAM,iBACL,QAAQ,IAAI,aAAa,SAAS,mCAAmC;AAe/D,MAAM,iBAAiB;AAQvB,SAAS,iBAAiB,OAA0C;AAC1E,MAAI;AACH,gEAAmB,OAAO,IAAI;AAAA,EAC/B,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;",
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
  }
@@ -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.79b13319d317",
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 { generateKeyBetween, generateNKeysBetween } from "jittered-fractional-indexing";
2
- const generateNKeysBetweenWithNoJitter = (a, b, n) => {
3
- return generateNKeysBetween(a, b, n, { jitterBits: 0 });
4
- };
5
- const generateKeysFn = process.env.NODE_ENV === "test" ? generateNKeysBetweenWithNoJitter : generateNKeysBetween;
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
- generateKeyBetween(index, null);
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 { generateKeyBetween, generateNKeysBetween } from 'jittered-fractional-indexing'\n\nconst generateNKeysBetweenWithNoJitter = (a: string | null, b: string | null, n: number) => {\n\treturn generateNKeysBetween(a, b, n, { jitterBits: 0 })\n}\n\nconst generateKeysFn =\n\tprocess.env.NODE_ENV === 'test' ? generateNKeysBetweenWithNoJitter : generateNKeysBetween\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\tgenerateKeyBetween(index, null)\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,SAAS,oBAAoB,4BAA4B;AAEzD,MAAM,mCAAmC,CAAC,GAAkB,GAAkB,MAAc;AAC3F,SAAO,qBAAqB,GAAG,GAAG,GAAG,EAAE,YAAY,EAAE,CAAC;AACvD;AAEA,MAAM,iBACL,QAAQ,IAAI,aAAa,SAAS,mCAAmC;AAe/D,MAAM,iBAAiB;AAQvB,SAAS,iBAAiB,OAA0C;AAC1E,MAAI;AACH,uBAAmB,OAAO,IAAI;AAAA,EAC/B,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;",
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.79b13319d317",
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
+ }
@@ -1,11 +1,11 @@
1
- import { generateKeyBetween, generateNKeysBetween } from 'jittered-fractional-indexing'
2
-
3
- const generateNKeysBetweenWithNoJitter = (a: string | null, b: string | null, n: number) => {
4
- return generateNKeysBetween(a, b, n, { jitterBits: 0 })
5
- }
1
+ import {
2
+ generateNJitteredKeysBetween,
3
+ generateNKeysBetween,
4
+ validateOrderKey,
5
+ } from './fractionalIndexing'
6
6
 
7
7
  const generateKeysFn =
8
- process.env.NODE_ENV === 'test' ? generateNKeysBetweenWithNoJitter : generateNKeysBetween
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
- generateKeyBetween(index, null)
33
+ validateOrderKey(index)
34
34
  } catch {
35
35
  throw new Error('invalid index: ' + index)
36
36
  }