@minnowdb/core 0.7.0 → 0.7.1

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.
Files changed (95) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -2
@@ -4,354 +4,320 @@ const INITIAL_KEY_CAPACITY = 32;
4
4
  const INDEX_FIELDS = 4;
5
5
  const UINT32_BYTES = Uint32Array.BYTES_PER_ELEMENT;
6
6
  const INITIAL_SCRATCH_CAPACITY = 64;
7
- // Every probe encodes its key into this shared scratch arena instead of allocating a fresh buffer
8
- // per row. Encoding is synchronous and the bytes are consumed before the next probe begins, so a
9
- // single arena serves every index; the miss path copies out of it before any caller code runs.
10
7
  let scratch = new Uint8Array(INITIAL_SCRATCH_CAPACITY);
11
8
  let scratchView = new DataView(scratch.buffer);
12
- // The arena grows to fit the largest key ever encoded, so a single pathological key (a
13
- // multi-megabyte string) would otherwise pin that memory for the life of the process — hostile
14
- // to low-end devices. Each fresh encoding starts by shedding anything beyond the retention cap;
15
- // ordinary keys never reach it, so the check is one branch per operation.
16
9
  const SCRATCH_RETAIN_BYTES = 1024 * 1024;
17
10
  function reclaimScratch() {
18
- if (scratch.byteLength <= SCRATCH_RETAIN_BYTES)
19
- return;
20
- scratch = new Uint8Array(SCRATCH_RETAIN_BYTES);
21
- scratchView = new DataView(scratch.buffer);
11
+ if (scratch.byteLength <= SCRATCH_RETAIN_BYTES)
12
+ return;
13
+ scratch = new Uint8Array(SCRATCH_RETAIN_BYTES);
14
+ scratchView = new DataView(scratch.buffer);
22
15
  }
23
- /** Byte-addressable, insertion-ordered grouping index with fully reserved typed storage. */
24
- export class ByteGroupIndex {
25
- #memory;
26
- #values = [];
27
- #buckets = new Int32Array(0);
28
- #hashes = new Uint32Array(0);
29
- #offsets = new Uint32Array(0);
30
- #lengths = new Uint32Array(0);
31
- #next = new Int32Array(0);
32
- #keys = new Uint8Array(0);
33
- #keyLength = 0;
34
- #entryReservation;
35
- #bucketReservation;
36
- #keyReservation;
37
- constructor(memory) {
38
- this.#memory = memory;
16
+ class ByteGroupIndex {
17
+ #memory;
18
+ #values = [];
19
+ #buckets = new Int32Array(0);
20
+ #hashes = new Uint32Array(0);
21
+ #offsets = new Uint32Array(0);
22
+ #lengths = new Uint32Array(0);
23
+ #next = new Int32Array(0);
24
+ #keys = new Uint8Array(0);
25
+ #keyLength = 0;
26
+ #entryReservation;
27
+ #bucketReservation;
28
+ #keyReservation;
29
+ constructor(memory) {
30
+ this.#memory = memory;
31
+ }
32
+ get size() {
33
+ return this.#values.length;
34
+ }
35
+ get(keys) {
36
+ const length = encodeGroupKey(keys);
37
+ const index = this.#find(length, hashScratch(length));
38
+ return index < 0 ? void 0 : this.#values[index];
39
+ }
40
+ getEmpty() {
41
+ return this.get([]);
42
+ }
43
+ getOne(key) {
44
+ const length = encodeSingleScalarKey(key);
45
+ const index = this.#find(length, hashScratch(length));
46
+ return index < 0 ? void 0 : this.#values[index];
47
+ }
48
+ set(keys, value) {
49
+ const length = encodeGroupKey(keys);
50
+ const hash = hashScratch(length);
51
+ const existing = this.#find(length, hash);
52
+ if (existing >= 0) {
53
+ this.#values[existing] = value;
54
+ return;
39
55
  }
40
- get size() {
41
- return this.#values.length;
56
+ this.#insert(scratch.slice(0, length), hash, value);
57
+ }
58
+ getOrInsert(keys, create) {
59
+ const length = encodeGroupKey(keys);
60
+ return this.#getOrInsertScratch(length, create);
61
+ }
62
+ getOrInsertOne(key, create) {
63
+ const length = encodeSingleScalarKey(key);
64
+ return this.#getOrInsertScratch(length, create);
65
+ }
66
+ #getOrInsertScratch(length, create) {
67
+ const hash = hashScratch(length);
68
+ const existing = this.#find(length, hash);
69
+ if (existing >= 0)
70
+ return this.#values[existing];
71
+ const encoded = scratch.slice(0, length);
72
+ const value = create();
73
+ this.#insert(encoded, hash, value);
74
+ return value;
75
+ }
76
+ #insert(encoded, hash, value) {
77
+ const index = this.#values.length;
78
+ this.#ensureEntryCapacity(index + 1);
79
+ this.#ensureBucketCapacity(index + 1);
80
+ this.#ensureKeyCapacity(encoded.byteLength);
81
+ const offset = this.#keyLength;
82
+ this.#keys.set(encoded, offset);
83
+ this.#keyLength += encoded.byteLength;
84
+ this.#hashes[index] = hash;
85
+ this.#offsets[index] = offset;
86
+ this.#lengths[index] = encoded.byteLength;
87
+ const bucket = hash & this.#buckets.length - 1;
88
+ this.#next[index] = this.#buckets[bucket] ?? -1;
89
+ this.#buckets[bucket] = index;
90
+ this.#values.push(value);
91
+ }
92
+ setEmpty(value) {
93
+ this.set([], value);
94
+ }
95
+ setOne(key, value) {
96
+ this.set([key], value);
97
+ }
98
+ values() {
99
+ return this.#values;
100
+ }
101
+ #find(length, hash) {
102
+ if (this.#buckets.length === 0)
103
+ return -1;
104
+ let index = this.#buckets[hash & this.#buckets.length - 1] ?? -1;
105
+ while (index >= 0) {
106
+ if (this.#hashes[index] === hash && this.#lengths[index] === length && equalsScratch(this.#keys, this.#offsets[index] ?? 0, length)) {
107
+ return index;
108
+ }
109
+ index = this.#next[index] ?? -1;
42
110
  }
43
- get(keys) {
44
- const length = encodeGroupKey(keys);
45
- const index = this.#find(length, hashScratch(length));
46
- return index < 0 ? undefined : this.#values[index];
47
- }
48
- getEmpty() {
49
- return this.get([]);
50
- }
51
- getOne(key) {
52
- const length = encodeSingleScalarKey(key);
53
- const index = this.#find(length, hashScratch(length));
54
- return index < 0 ? undefined : this.#values[index];
55
- }
56
- set(keys, value) {
57
- const length = encodeGroupKey(keys);
58
- const hash = hashScratch(length);
59
- const existing = this.#find(length, hash);
60
- if (existing >= 0) {
61
- this.#values[existing] = value;
62
- return;
63
- }
64
- this.#insert(scratch.slice(0, length), hash, value);
65
- }
66
- getOrInsert(keys, create) {
67
- const length = encodeGroupKey(keys);
68
- return this.#getOrInsertScratch(length, create);
69
- }
70
- getOrInsertOne(key, create) {
71
- const length = encodeSingleScalarKey(key);
72
- return this.#getOrInsertScratch(length, create);
73
- }
74
- /**
75
- * Resolves the key currently held in the scratch arena. A hit touches no allocation at all; a
76
- * miss — at most one per distinct group — copies the key out before `create` runs, so caller
77
- * code can never observe or disturb the shared arena.
78
- */
79
- #getOrInsertScratch(length, create) {
80
- const hash = hashScratch(length);
81
- const existing = this.#find(length, hash);
82
- if (existing >= 0)
83
- return this.#values[existing];
84
- const encoded = scratch.slice(0, length);
85
- const value = create();
86
- this.#insert(encoded, hash, value);
87
- return value;
88
- }
89
- #insert(encoded, hash, value) {
90
- const index = this.#values.length;
91
- this.#ensureEntryCapacity(index + 1);
92
- this.#ensureBucketCapacity(index + 1);
93
- this.#ensureKeyCapacity(encoded.byteLength);
94
- const offset = this.#keyLength;
95
- this.#keys.set(encoded, offset);
96
- this.#keyLength += encoded.byteLength;
97
- this.#hashes[index] = hash;
98
- this.#offsets[index] = offset;
99
- this.#lengths[index] = encoded.byteLength;
100
- const bucket = hash & (this.#buckets.length - 1);
101
- this.#next[index] = this.#buckets[bucket] ?? -1;
102
- this.#buckets[bucket] = index;
103
- this.#values.push(value);
104
- }
105
- setEmpty(value) {
106
- this.set([], value);
107
- }
108
- setOne(key, value) {
109
- this.set([key], value);
110
- }
111
- values() {
112
- return this.#values;
113
- }
114
- /** Matches the scratch arena's first `length` bytes against stored keys without copying them. */
115
- #find(length, hash) {
116
- if (this.#buckets.length === 0)
117
- return -1;
118
- let index = this.#buckets[hash & (this.#buckets.length - 1)] ?? -1;
119
- while (index >= 0) {
120
- if (this.#hashes[index] === hash &&
121
- this.#lengths[index] === length &&
122
- equalsScratch(this.#keys, this.#offsets[index] ?? 0, length)) {
123
- return index;
124
- }
125
- index = this.#next[index] ?? -1;
126
- }
127
- return -1;
111
+ return -1;
112
+ }
113
+ #ensureEntryCapacity(required) {
114
+ if (required <= this.#hashes.length)
115
+ return;
116
+ let capacity = Math.max(INITIAL_ENTRY_CAPACITY, this.#hashes.length * 2);
117
+ while (capacity < required)
118
+ capacity = safeDouble(capacity, "Group index entries");
119
+ const reservation = this.#memory.reserve(safeProduct(capacity, INDEX_FIELDS * UINT32_BYTES, "Group index entry storage"), "Group index entry storage");
120
+ try {
121
+ const hashes = new Uint32Array(capacity);
122
+ const offsets = new Uint32Array(capacity);
123
+ const lengths = new Uint32Array(capacity);
124
+ const next = new Int32Array(capacity);
125
+ next.fill(-1);
126
+ hashes.set(this.#hashes);
127
+ offsets.set(this.#offsets);
128
+ lengths.set(this.#lengths);
129
+ next.set(this.#next);
130
+ this.#hashes = hashes;
131
+ this.#offsets = offsets;
132
+ this.#lengths = lengths;
133
+ this.#next = next;
134
+ this.#entryReservation?.release();
135
+ this.#entryReservation = reservation;
136
+ } catch (error) {
137
+ reservation.release();
138
+ throw error;
128
139
  }
129
- #ensureEntryCapacity(required) {
130
- if (required <= this.#hashes.length)
131
- return;
132
- let capacity = Math.max(INITIAL_ENTRY_CAPACITY, this.#hashes.length * 2);
133
- while (capacity < required)
134
- capacity = safeDouble(capacity, "Group index entries");
135
- const reservation = this.#memory.reserve(safeProduct(capacity, INDEX_FIELDS * UINT32_BYTES, "Group index entry storage"), "Group index entry storage");
136
- try {
137
- const hashes = new Uint32Array(capacity);
138
- const offsets = new Uint32Array(capacity);
139
- const lengths = new Uint32Array(capacity);
140
- const next = new Int32Array(capacity);
141
- next.fill(-1);
142
- hashes.set(this.#hashes);
143
- offsets.set(this.#offsets);
144
- lengths.set(this.#lengths);
145
- next.set(this.#next);
146
- this.#hashes = hashes;
147
- this.#offsets = offsets;
148
- this.#lengths = lengths;
149
- this.#next = next;
150
- this.#entryReservation?.release();
151
- this.#entryReservation = reservation;
152
- }
153
- catch (error) {
154
- reservation.release();
155
- throw error;
156
- }
140
+ }
141
+ #ensureBucketCapacity(requiredEntries) {
142
+ let capacity = this.#buckets.length;
143
+ if (capacity === 0)
144
+ capacity = INITIAL_BUCKET_CAPACITY;
145
+ while (requiredEntries * 4 > capacity * 3) {
146
+ capacity = safeDouble(capacity, "Group index buckets");
157
147
  }
158
- #ensureBucketCapacity(requiredEntries) {
159
- let capacity = this.#buckets.length;
160
- if (capacity === 0)
161
- capacity = INITIAL_BUCKET_CAPACITY;
162
- while (requiredEntries * 4 > capacity * 3) {
163
- capacity = safeDouble(capacity, "Group index buckets");
164
- }
165
- if (capacity === this.#buckets.length)
166
- return;
167
- const reservation = this.#memory.reserve(safeProduct(capacity, Int32Array.BYTES_PER_ELEMENT, "Group index bucket storage"), "Group index bucket storage");
168
- try {
169
- const buckets = new Int32Array(capacity);
170
- buckets.fill(-1);
171
- for (let index = 0; index < this.#values.length; index += 1) {
172
- const bucket = (this.#hashes[index] ?? 0) & (capacity - 1);
173
- this.#next[index] = buckets[bucket] ?? -1;
174
- buckets[bucket] = index;
175
- }
176
- this.#buckets = buckets;
177
- this.#bucketReservation?.release();
178
- this.#bucketReservation = reservation;
179
- }
180
- catch (error) {
181
- reservation.release();
182
- throw error;
183
- }
148
+ if (capacity === this.#buckets.length)
149
+ return;
150
+ const reservation = this.#memory.reserve(safeProduct(capacity, Int32Array.BYTES_PER_ELEMENT, "Group index bucket storage"), "Group index bucket storage");
151
+ try {
152
+ const buckets = new Int32Array(capacity);
153
+ buckets.fill(-1);
154
+ for (let index = 0; index < this.#values.length; index += 1) {
155
+ const bucket = (this.#hashes[index] ?? 0) & capacity - 1;
156
+ this.#next[index] = buckets[bucket] ?? -1;
157
+ buckets[bucket] = index;
158
+ }
159
+ this.#buckets = buckets;
160
+ this.#bucketReservation?.release();
161
+ this.#bucketReservation = reservation;
162
+ } catch (error) {
163
+ reservation.release();
164
+ throw error;
184
165
  }
185
- #ensureKeyCapacity(additionalBytes) {
186
- const required = this.#keyLength + additionalBytes;
187
- if (!Number.isSafeInteger(required))
188
- throw new RangeError("Group key arena is too large");
189
- if (required > 0xffff_ffff)
190
- throw new RangeError("Group key arena exceeds uint32 offsets");
191
- if (required <= this.#keys.byteLength)
192
- return;
193
- let capacity = Math.max(INITIAL_KEY_CAPACITY, this.#keys.byteLength * 2);
194
- while (capacity < required)
195
- capacity = safeDouble(capacity, "Group key arena");
196
- const reservation = this.#memory.reserve(capacity, "Group key arena");
197
- try {
198
- const keys = new Uint8Array(capacity);
199
- keys.set(this.#keys.subarray(0, this.#keyLength));
200
- this.#keys = keys;
201
- this.#keyReservation?.release();
202
- this.#keyReservation = reservation;
203
- }
204
- catch (error) {
205
- reservation.release();
206
- throw error;
207
- }
166
+ }
167
+ #ensureKeyCapacity(additionalBytes) {
168
+ const required = this.#keyLength + additionalBytes;
169
+ if (!Number.isSafeInteger(required))
170
+ throw new RangeError("Group key arena is too large");
171
+ if (required > 4294967295)
172
+ throw new RangeError("Group key arena exceeds uint32 offsets");
173
+ if (required <= this.#keys.byteLength)
174
+ return;
175
+ let capacity = Math.max(INITIAL_KEY_CAPACITY, this.#keys.byteLength * 2);
176
+ while (capacity < required)
177
+ capacity = safeDouble(capacity, "Group key arena");
178
+ const reservation = this.#memory.reserve(capacity, "Group key arena");
179
+ try {
180
+ const keys = new Uint8Array(capacity);
181
+ keys.set(this.#keys.subarray(0, this.#keyLength));
182
+ this.#keys = keys;
183
+ this.#keyReservation?.release();
184
+ this.#keyReservation = reservation;
185
+ } catch (error) {
186
+ reservation.release();
187
+ throw error;
208
188
  }
189
+ }
209
190
  }
210
- /**
211
- * Writes the canonical tagged encoding of `keys` into the scratch arena and returns its byte
212
- * length. The byte layout is unchanged from the allocating encoder it replaces — a one-byte tag
213
- * per key, little-endian float64 numbers, and length-prefixed UTF-8 strings — so stored keys and
214
- * their hashes stay comparable across both paths.
215
- */
216
191
  function encodeGroupKey(keys) {
217
- reclaimScratch();
218
- let offset = 0;
219
- for (const key of keys)
220
- offset = writeGroupKey(key, offset);
221
- return offset;
192
+ reclaimScratch();
193
+ let offset = 0;
194
+ for (const key of keys)
195
+ offset = writeGroupKey(key, offset);
196
+ return offset;
222
197
  }
223
- /**
224
- * Encodes one scalar key into the shared scratch arena and returns its byte length. Exported for
225
- * the join index, which shares the arena and byte layout: both consume the encoding synchronously
226
- * before any other key operation can run, so a single arena serves every index. NaN is the one
227
- * excluded number — group callers canonicalize non-finite to null, join callers skip NaN keys —
228
- * while ±Infinity encodes as an ordinary float64 so computed join keys can overflow and still match.
229
- */
230
- export function encodeSingleScalarKey(key) {
231
- reclaimScratch();
232
- return writeGroupKey(key, 0);
198
+ function encodeSingleScalarKey(key) {
199
+ reclaimScratch();
200
+ return writeGroupKey(key, 0);
233
201
  }
234
202
  function writeGroupKey(key, offset) {
235
- if (key === null) {
236
- ensureScratchCapacity(offset + 1);
237
- scratch[offset] = 0;
238
- return offset + 1;
203
+ if (key === null) {
204
+ ensureScratchCapacity(offset + 1);
205
+ scratch[offset] = 0;
206
+ return offset + 1;
207
+ }
208
+ if (typeof key === "boolean") {
209
+ ensureScratchCapacity(offset + 1);
210
+ scratch[offset] = key ? 2 : 1;
211
+ return offset + 1;
212
+ }
213
+ if (typeof key === "number") {
214
+ if (Number.isNaN(key))
215
+ throw new TypeError("Scalar index keys cannot be NaN");
216
+ ensureScratchCapacity(offset + 1 + Float64Array.BYTES_PER_ELEMENT);
217
+ scratch[offset] = 3;
218
+ scratchView.setFloat64(offset + 1, key === 0 ? 0 : key, true);
219
+ return offset + 1 + Float64Array.BYTES_PER_ELEMENT;
220
+ }
221
+ const bound = safeSum(offset + 5, safeProduct(key.length, 3, "Encoded group key"), "Encoded group key");
222
+ if (bound > 4294967295)
223
+ throw new RangeError("Encoded group key is too large");
224
+ ensureScratchCapacity(bound);
225
+ scratch[offset] = 4;
226
+ const payloadStart = offset + 5;
227
+ const payloadEnd = writeUtf8(key, payloadStart);
228
+ scratchView.setUint32(offset + 1, payloadEnd - payloadStart, true);
229
+ return payloadEnd;
230
+ }
231
+ function writeUtf8(value, offset) {
232
+ let write = offset;
233
+ for (let index = 0; index < value.length; index += 1) {
234
+ let code = value.charCodeAt(index);
235
+ if (code < 128) {
236
+ scratch[write] = code;
237
+ write += 1;
238
+ continue;
239
239
  }
240
- if (typeof key === "boolean") {
241
- ensureScratchCapacity(offset + 1);
242
- scratch[offset] = key ? 2 : 1;
243
- return offset + 1;
240
+ if (code < 2048) {
241
+ scratch[write] = 192 | code >>> 6;
242
+ scratch[write + 1] = 128 | code & 63;
243
+ write += 2;
244
+ continue;
244
245
  }
245
- if (typeof key === "number") {
246
- if (Number.isNaN(key))
247
- throw new TypeError("Scalar index keys cannot be NaN");
248
- ensureScratchCapacity(offset + 1 + Float64Array.BYTES_PER_ELEMENT);
249
- scratch[offset] = 3;
250
- scratchView.setFloat64(offset + 1, key === 0 ? 0 : key, true);
251
- return offset + 1 + Float64Array.BYTES_PER_ELEMENT;
246
+ if (code >= 55296 && code <= 56319) {
247
+ const low = index + 1 < value.length ? value.charCodeAt(index + 1) : 0;
248
+ if (low >= 56320 && low <= 57343) {
249
+ code = 65536 + (code - 55296 << 10) + (low - 56320);
250
+ index += 1;
251
+ scratch[write] = 240 | code >>> 18;
252
+ scratch[write + 1] = 128 | code >>> 12 & 63;
253
+ scratch[write + 2] = 128 | code >>> 6 & 63;
254
+ scratch[write + 3] = 128 | code & 63;
255
+ write += 4;
256
+ continue;
257
+ }
252
258
  }
253
- // A code unit never expands past three UTF-8 bytes. Reserving the one conservative bound keeps
254
- // this query hot path single-pass and branch-light; oversized scratch is reclaimed before the
255
- // next key operation.
256
- const bound = safeSum(offset + 5, safeProduct(key.length, 3, "Encoded group key"), "Encoded group key");
257
- if (bound > 0xffff_ffff)
258
- throw new RangeError("Encoded group key is too large");
259
- ensureScratchCapacity(bound);
260
- scratch[offset] = 4;
261
- const payloadStart = offset + 5;
262
- const payloadEnd = writeUtf8(key, payloadStart);
263
- scratchView.setUint32(offset + 1, payloadEnd - payloadStart, true);
264
- return payloadEnd;
265
- }
266
- /**
267
- * Encodes `value` as UTF-8 in one pass. Isolated surrogates fail before the encoded bytes can
268
- * participate in a lookup or insertion.
269
- */
270
- function writeUtf8(value, offset) {
271
- let write = offset;
272
- for (let index = 0; index < value.length; index += 1) {
273
- let code = value.charCodeAt(index);
274
- if (code < 0x80) {
275
- scratch[write] = code;
276
- write += 1;
277
- continue;
278
- }
279
- if (code < 0x800) {
280
- scratch[write] = 0xc0 | (code >>> 6);
281
- scratch[write + 1] = 0x80 | (code & 0x3f);
282
- write += 2;
283
- continue;
284
- }
285
- if (code >= 0xd800 && code <= 0xdbff) {
286
- const low = index + 1 < value.length ? value.charCodeAt(index + 1) : 0;
287
- if (low >= 0xdc00 && low <= 0xdfff) {
288
- code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
289
- index += 1;
290
- scratch[write] = 0xf0 | (code >>> 18);
291
- scratch[write + 1] = 0x80 | ((code >>> 12) & 0x3f);
292
- scratch[write + 2] = 0x80 | ((code >>> 6) & 0x3f);
293
- scratch[write + 3] = 0x80 | (code & 0x3f);
294
- write += 4;
295
- continue;
296
- }
297
- }
298
- if (code >= 0xd800 && code <= 0xdfff) {
299
- throw new TypeError(`String value contains an unpaired surrogate at UTF-16 index ${String(index)}`);
300
- }
301
- scratch[write] = 0xe0 | (code >>> 12);
302
- scratch[write + 1] = 0x80 | ((code >>> 6) & 0x3f);
303
- scratch[write + 2] = 0x80 | (code & 0x3f);
304
- write += 3;
259
+ if (code >= 55296 && code <= 57343) {
260
+ throw new TypeError(`String value contains an unpaired surrogate at UTF-16 index ${String(index)}`);
305
261
  }
306
- return write;
262
+ scratch[write] = 224 | code >>> 12;
263
+ scratch[write + 1] = 128 | code >>> 6 & 63;
264
+ scratch[write + 2] = 128 | code & 63;
265
+ write += 3;
266
+ }
267
+ return write;
307
268
  }
308
269
  function ensureScratchCapacity(required) {
309
- if (required <= scratch.byteLength)
310
- return;
311
- let capacity = scratch.byteLength;
312
- while (capacity < required)
313
- capacity = safeDouble(capacity, "Group key scratch arena");
314
- const grown = new Uint8Array(capacity);
315
- // Earlier keys of a compound encoding already occupy the arena, so growth carries them over.
316
- grown.set(scratch);
317
- scratch = grown;
318
- scratchView = new DataView(scratch.buffer);
270
+ if (required <= scratch.byteLength)
271
+ return;
272
+ let capacity = scratch.byteLength;
273
+ while (capacity < required)
274
+ capacity = safeDouble(capacity, "Group key scratch arena");
275
+ const grown = new Uint8Array(capacity);
276
+ grown.set(scratch);
277
+ scratch = grown;
278
+ scratchView = new DataView(scratch.buffer);
319
279
  }
320
- /** FNV-1a over the scratch arena's first `length` bytes. Exported for the join index. */
321
- export function hashScratch(length) {
322
- let hash = 0x811c9dc5;
323
- for (let index = 0; index < length; index += 1) {
324
- hash = Math.imul(hash ^ (scratch[index] ?? 0), 0x01000193) >>> 0;
325
- }
326
- return hash;
280
+ function hashScratch(length) {
281
+ let hash = 2166136261;
282
+ for (let index = 0; index < length; index += 1) {
283
+ hash = Math.imul(hash ^ (scratch[index] ?? 0), 16777619) >>> 0;
284
+ }
285
+ return hash;
327
286
  }
328
- /** Matches `arena[offset..offset+length)` against the scratch arena. Exported for the join index. */
329
- export function equalsScratch(arena, offset, length) {
330
- for (let index = 0; index < length; index += 1) {
331
- if (arena[offset + index] !== scratch[index])
332
- return false;
333
- }
334
- return true;
287
+ function equalsScratch(arena, offset, length) {
288
+ for (let index = 0; index < length; index += 1) {
289
+ if (arena[offset + index] !== scratch[index])
290
+ return false;
291
+ }
292
+ return true;
335
293
  }
336
- /** Copies the scratch arena's first `length` bytes into owned storage. Exported for the join index. */
337
- export function copyScratchKey(length) {
338
- return scratch.slice(0, length);
294
+ function copyScratchKey(length) {
295
+ return scratch.slice(0, length);
339
296
  }
340
- export function safeDouble(value, label) {
341
- const doubled = value * 2;
342
- if (!Number.isSafeInteger(doubled))
343
- throw new RangeError(`${label} exceeds the safe integer range`);
344
- return doubled;
297
+ function safeDouble(value, label) {
298
+ const doubled = value * 2;
299
+ if (!Number.isSafeInteger(doubled))
300
+ throw new RangeError(`${label} exceeds the safe integer range`);
301
+ return doubled;
345
302
  }
346
- export function safeProduct(left, right, label) {
347
- const product = left * right;
348
- if (!Number.isSafeInteger(product))
349
- throw new RangeError(`${label} exceeds the safe integer range`);
350
- return product;
303
+ function safeProduct(left, right, label) {
304
+ const product = left * right;
305
+ if (!Number.isSafeInteger(product))
306
+ throw new RangeError(`${label} exceeds the safe integer range`);
307
+ return product;
351
308
  }
352
309
  function safeSum(left, right, label) {
353
- const sum = left + right;
354
- if (!Number.isSafeInteger(sum))
355
- throw new RangeError(`${label} exceeds the safe integer range`);
356
- return sum;
310
+ const sum = left + right;
311
+ if (!Number.isSafeInteger(sum))
312
+ throw new RangeError(`${label} exceeds the safe integer range`);
313
+ return sum;
357
314
  }
315
+ export {
316
+ ByteGroupIndex,
317
+ copyScratchKey,
318
+ encodeSingleScalarKey,
319
+ equalsScratch,
320
+ hashScratch,
321
+ safeDouble,
322
+ safeProduct
323
+ };