@moyodb/sdk 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +46 -0
- package/README.md +4 -2
- package/dist/assets/browser-worker-p7uX85l-.js +4 -0
- package/dist/assets/browser-worker-p7uX85l-.js.map +1 -0
- package/dist/assets/shared-worker-BALTqEUe.js +2 -0
- package/dist/assets/shared-worker-BALTqEUe.js.map +1 -0
- package/dist/chunks/{indexing-CKtbpaZn.js → indexing-DdpU69Fd.js} +344 -185
- package/dist/chunks/indexing-DdpU69Fd.js.map +1 -0
- package/dist/codec.d.ts.map +1 -1
- package/dist/compression.d.ts.map +1 -1
- package/dist/demo.d.ts.map +1 -1
- package/dist/engine/README.md +4 -2
- package/dist/engine/moyodb_engine.d.ts +4 -2
- package/dist/engine/moyodb_engine.js +45 -22
- package/dist/engine/moyodb_engine_bg.wasm +0 -0
- package/dist/engine/moyodb_engine_bg.wasm.d.ts +3 -2
- package/dist/engine/package.json +1 -1
- package/dist/engine/snippets/{moyodb-engine-54a406e33b0fb849 → moyodb-engine-1f8134232168280b}/js/opfs_shim.js +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/indexing.d.ts +7 -0
- package/dist/indexing.d.ts.map +1 -1
- package/dist/moyodb-sdk.js +1265 -872
- package/dist/moyodb-sdk.js.map +1 -1
- package/dist/node-engine.js +411 -81
- package/dist/node-engine.js.map +1 -1
- package/dist/node.js +354 -74
- package/dist/node.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/subscriptions.d.ts.map +1 -1
- package/dist/worker-api.d.ts +2 -5
- package/dist/worker-api.d.ts.map +1 -1
- package/dist/worker-client.d.ts.map +1 -1
- package/dist/worker-protocol.d.ts +7 -2
- package/dist/worker-protocol.d.ts.map +1 -1
- package/dist/worker-server.d.ts.map +1 -1
- package/dist/worker.d.ts +12 -5
- package/dist/worker.d.ts.map +1 -1
- package/package.json +5 -3
- package/dist/assets/browser-worker-DBt51gsS.js +0 -4
- package/dist/assets/browser-worker-DBt51gsS.js.map +0 -1
- package/dist/assets/shared-worker-NdniRuEL.js +0 -2
- package/dist/assets/shared-worker-NdniRuEL.js.map +0 -1
- package/dist/chunks/indexing-CKtbpaZn.js.map +0 -1
package/dist/node-engine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bt as unpackPackedBinaryList, Ct as isWorkerCommand, Dt as isWorkerProtocolRequestBatchMessage, Gt as clampTimerDelayMs, Ht as unpackPackedOptionalValues, It as prepareWorkerResponsePayload, L as utf8Encode, Lt as serializeWorkerError, Mt as packedBinaryListBytes, Nt as packedOptionalValues, Ot as isWorkerProtocolRequestMessage, Pt as packedScanRows, Rt as unpackPackedBatchOpKeys, S as normalizeIndexDefinitions, St as isAutocommitCommand, Ut as unpackPackedScanRows, Vt as unpackPackedBinaryPairKeys, Wt as workerProtocolError, _t as WORKER_PROTOCOL_RESPONSE_BATCH, a as cloneNormalizedIndexDefinitions, c as decodeIndexEntryKey, d as encodeIndexEntryKey, f as encodeIndexMetadataKey, g as findIndexDefinition, gt as WORKER_PROTOCOL_RESPONSE, h as extractLogicalIndexKeyFromDocument, i as assertValidScanRange, jt as packedBatchOpsBytes, l as decodeIndexMetadataValue, m as extractLogicalIndexKey, o as compareNormalizedIndexDefinitions, p as encodeIndexMetadataValue, pt as WORKER_PROTOCOL_READY, qt as isRecord, s as createIndexKeyExtractor, t as INDEX_METADATA_STORE, u as decodeIndexedDocument, v as indexKeyExactRange, vt as captureWorkerPayload, w as toPublicIndexDefinitions, wt as isWorkerProtocolEnvelope, x as isInternalStoreName, y as indexRangeToPhysicalRange, yt as decodeWorkerCommandPayload, zt as unpackPackedBatchOps } from "./chunks/indexing-DdpU69Fd.js";
|
|
2
2
|
//#region src/worker-server.ts
|
|
3
3
|
/** The first argument identifies the transaction lane. */
|
|
4
4
|
var TRANSACTION_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -128,10 +128,20 @@ var ResponseQueue = class {
|
|
|
128
128
|
constructor(scope) {
|
|
129
129
|
this.scope = scope;
|
|
130
130
|
}
|
|
131
|
-
enqueue
|
|
131
|
+
enqueue(response, transfer = [], flushNow = false) {
|
|
132
132
|
if (this.closed) return;
|
|
133
|
-
const
|
|
134
|
-
if (this.queued.length > 0 && this.queuedBytes +
|
|
133
|
+
const byteLength = payloadBytes(response);
|
|
134
|
+
if (this.queued.length > 0 && this.queuedBytes + byteLength > 1048576) this.flush();
|
|
135
|
+
if (flushNow || this.queued.length + 1 >= 128 || this.queuedBytes + byteLength >= 1048576) {
|
|
136
|
+
this.queued.push({
|
|
137
|
+
response,
|
|
138
|
+
transfer
|
|
139
|
+
});
|
|
140
|
+
this.queuedBytes += byteLength;
|
|
141
|
+
this.flush();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const captured = snapshotQueuedPayload(response, transfer);
|
|
135
145
|
this.queued.push({
|
|
136
146
|
response: captured.value,
|
|
137
147
|
transfer: captured.transfer
|
|
@@ -148,8 +158,7 @@ var ResponseQueue = class {
|
|
|
148
158
|
this.flushChannel.port2.postMessage(null);
|
|
149
159
|
});
|
|
150
160
|
}
|
|
151
|
-
|
|
152
|
-
};
|
|
161
|
+
}
|
|
153
162
|
close() {
|
|
154
163
|
this.closed = true;
|
|
155
164
|
this.queued = [];
|
|
@@ -190,9 +199,8 @@ function exposeWorkerApi(api, scope = self) {
|
|
|
190
199
|
if (isWorkerProtocolRequestBatchMessage(event.data)) {
|
|
191
200
|
let remaining = event.data.requests.length;
|
|
192
201
|
const respond = (response, transfer) => {
|
|
193
|
-
responses.enqueue(response, transfer);
|
|
194
202
|
remaining -= 1;
|
|
195
|
-
|
|
203
|
+
responses.enqueue(response, transfer, remaining === 0);
|
|
196
204
|
};
|
|
197
205
|
for (const request of event.data.requests) dispatchWorkerRequest(scope, api, scheduler, request, respond);
|
|
198
206
|
return;
|
|
@@ -316,6 +324,203 @@ function postWorkerResponse(scope, response, transfer = []) {
|
|
|
316
324
|
} catch {}
|
|
317
325
|
scope.postMessage(response);
|
|
318
326
|
}
|
|
327
|
+
var STANDARD_VIEW_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
328
|
+
DataView,
|
|
329
|
+
Uint8Array,
|
|
330
|
+
Uint8ClampedArray,
|
|
331
|
+
Uint16Array,
|
|
332
|
+
Uint32Array,
|
|
333
|
+
Int8Array,
|
|
334
|
+
Int16Array,
|
|
335
|
+
Int32Array,
|
|
336
|
+
Float32Array,
|
|
337
|
+
Float64Array,
|
|
338
|
+
BigInt64Array,
|
|
339
|
+
BigUint64Array
|
|
340
|
+
]);
|
|
341
|
+
/**
|
|
342
|
+
* Freeze a queued payload so the later postMessage is its only structured clone.
|
|
343
|
+
* Listed buffers move now. Other ArrayBuffers are copied. SharedArrayBuffers stay shared.
|
|
344
|
+
*/
|
|
345
|
+
function snapshotQueuedPayload(value, transfer) {
|
|
346
|
+
const moving = /* @__PURE__ */ new Set();
|
|
347
|
+
for (const item of transfer) {
|
|
348
|
+
if (!(item instanceof ArrayBuffer) || isResizableBuffer(item)) return captureWorkerPayload(value, transfer);
|
|
349
|
+
moving.add(item);
|
|
350
|
+
}
|
|
351
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
352
|
+
if (!isPlainPayload(value, geometries, /* @__PURE__ */ new WeakSet())) return captureWorkerPayload(value, transfer);
|
|
353
|
+
const relocated = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const buffer of moving) relocated.set(buffer, moveArrayBuffer(buffer));
|
|
355
|
+
const owned = [];
|
|
356
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
357
|
+
const cloneValue = (item) => {
|
|
358
|
+
if (item === null || typeof item !== "object") return item;
|
|
359
|
+
const existing = seen.get(item);
|
|
360
|
+
if (existing !== void 0) return existing;
|
|
361
|
+
if (typeof SharedArrayBuffer !== "undefined" && item instanceof SharedArrayBuffer) {
|
|
362
|
+
seen.set(item, item);
|
|
363
|
+
return item;
|
|
364
|
+
}
|
|
365
|
+
if (item instanceof ArrayBuffer) {
|
|
366
|
+
const next = relocated.get(item) ?? item.slice(0);
|
|
367
|
+
seen.set(item, next);
|
|
368
|
+
owned.push(next);
|
|
369
|
+
return next;
|
|
370
|
+
}
|
|
371
|
+
if (ArrayBuffer.isView(item)) {
|
|
372
|
+
const geometry = geometries.get(item);
|
|
373
|
+
if (!geometry) throw workerProtocolError("WorkerProtocolError", "worker payload could not be queued");
|
|
374
|
+
const buffer = item.buffer;
|
|
375
|
+
const out = retargetView(item, typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer ? buffer : cloneValue(buffer), geometry);
|
|
376
|
+
seen.set(item, out);
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
if (Array.isArray(item)) {
|
|
380
|
+
const out = new Array(item.length);
|
|
381
|
+
seen.set(item, out);
|
|
382
|
+
for (let index = 0; index < item.length; index += 1) if (index in item) out[index] = cloneValue(item[index]);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
const out = Object.getPrototypeOf(item) === null ? Object.create(null) : {};
|
|
386
|
+
seen.set(item, out);
|
|
387
|
+
const record = item;
|
|
388
|
+
for (const key in record) if (Object.prototype.hasOwnProperty.call(record, key)) out[key] = cloneValue(record[key]);
|
|
389
|
+
return out;
|
|
390
|
+
};
|
|
391
|
+
const cloned = cloneValue(value);
|
|
392
|
+
return {
|
|
393
|
+
value: cloned,
|
|
394
|
+
transfer: owned,
|
|
395
|
+
byteLength: payloadBytes(cloned)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function payloadBytes(value) {
|
|
399
|
+
let byteLength = 0;
|
|
400
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
401
|
+
const visit = (item) => {
|
|
402
|
+
if (!Number.isFinite(byteLength)) return;
|
|
403
|
+
if (typeof item === "string") {
|
|
404
|
+
byteLength += 8 + item.length * 2;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (item === null || typeof item !== "object") {
|
|
408
|
+
byteLength += 8;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (seen.has(item)) return;
|
|
412
|
+
seen.add(item);
|
|
413
|
+
byteLength += 8;
|
|
414
|
+
if (item instanceof ArrayBuffer) byteLength += item.byteLength;
|
|
415
|
+
else if (typeof SharedArrayBuffer !== "undefined" && item instanceof SharedArrayBuffer) byteLength += item.byteLength;
|
|
416
|
+
else if (ArrayBuffer.isView(item)) visit(item.buffer);
|
|
417
|
+
else if (Array.isArray(item)) {
|
|
418
|
+
if (Object.getPrototypeOf(item) !== Array.prototype || hasExtraKeys(item)) {
|
|
419
|
+
byteLength = Number.POSITIVE_INFINITY;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
for (let index = 0; index < item.length; index += 1) {
|
|
423
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
|
|
424
|
+
if (descriptor && !("value" in descriptor)) {
|
|
425
|
+
byteLength = Number.POSITIVE_INFINITY;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
visit(descriptor?.value);
|
|
429
|
+
}
|
|
430
|
+
} else if (item instanceof Map) for (const [key, child] of item) {
|
|
431
|
+
visit(key);
|
|
432
|
+
visit(child);
|
|
433
|
+
}
|
|
434
|
+
else if (item instanceof Set) for (const child of item) visit(child);
|
|
435
|
+
else {
|
|
436
|
+
const record = item;
|
|
437
|
+
for (const key in record) if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
438
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
439
|
+
if (key === "__proto__" || !descriptor || !("value" in descriptor)) {
|
|
440
|
+
byteLength = Number.POSITIVE_INFINITY;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
byteLength += 8 + key.length * 2;
|
|
444
|
+
visit(descriptor.value);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
visit(value);
|
|
449
|
+
return byteLength;
|
|
450
|
+
}
|
|
451
|
+
function isPlainPayload(value, geometries, seen) {
|
|
452
|
+
if (typeof value === "function" || typeof value === "symbol") return false;
|
|
453
|
+
if (value === null || typeof value !== "object") return true;
|
|
454
|
+
if (seen.has(value)) return true;
|
|
455
|
+
seen.add(value);
|
|
456
|
+
if (typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer) return !isResizableBuffer(value);
|
|
457
|
+
if (value instanceof ArrayBuffer) return !isResizableBuffer(value);
|
|
458
|
+
if (ArrayBuffer.isView(value)) {
|
|
459
|
+
if (!isStandardView(value)) return false;
|
|
460
|
+
const buffer = value.buffer;
|
|
461
|
+
if (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer) {
|
|
462
|
+
if (isResizableBuffer(buffer)) return false;
|
|
463
|
+
geometries.set(value, viewGeometry(value));
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
if (!(buffer instanceof ArrayBuffer) || isResizableBuffer(buffer)) return false;
|
|
467
|
+
geometries.set(value, viewGeometry(value));
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
if (value instanceof Map || value instanceof Set || value instanceof Date || value instanceof RegExp) return false;
|
|
471
|
+
if (Array.isArray(value)) {
|
|
472
|
+
if (hasExtraKeys(value) || hasEnumerableSymbols(value)) return false;
|
|
473
|
+
for (const child of value) if (!isPlainPayload(child, geometries, seen)) return false;
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
const proto = Object.getPrototypeOf(value);
|
|
477
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
478
|
+
if (hasEnumerableSymbols(value)) return false;
|
|
479
|
+
const record = value;
|
|
480
|
+
for (const key in record) if (Object.prototype.hasOwnProperty.call(record, key) && !isPlainPayload(record[key], geometries, seen)) return false;
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
function hasExtraKeys(value) {
|
|
484
|
+
const record = value;
|
|
485
|
+
for (const key in record) {
|
|
486
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
|
|
487
|
+
const index = Number(key);
|
|
488
|
+
if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return true;
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
function hasEnumerableSymbols(value) {
|
|
493
|
+
for (const symbol of Object.getOwnPropertySymbols(value)) if (Object.prototype.propertyIsEnumerable.call(value, symbol)) return true;
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
function isStandardView(view) {
|
|
497
|
+
return STANDARD_VIEW_CONSTRUCTORS.has(view.constructor);
|
|
498
|
+
}
|
|
499
|
+
function viewGeometry(view) {
|
|
500
|
+
if (view instanceof DataView) return {
|
|
501
|
+
offset: view.byteOffset,
|
|
502
|
+
length: view.byteLength,
|
|
503
|
+
dataView: true
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
offset: view.byteOffset,
|
|
507
|
+
length: view.length,
|
|
508
|
+
dataView: false
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function retargetView(view, buffer, geometry) {
|
|
512
|
+
if (geometry.dataView) return new DataView(buffer, geometry.offset, geometry.length);
|
|
513
|
+
const Typed = view.constructor;
|
|
514
|
+
return new Typed(buffer, geometry.offset, geometry.length);
|
|
515
|
+
}
|
|
516
|
+
function moveArrayBuffer(buffer) {
|
|
517
|
+
const movable = buffer;
|
|
518
|
+
if (typeof movable.transfer === "function") return movable.transfer();
|
|
519
|
+
return structuredClone(buffer, { transfer: [buffer] });
|
|
520
|
+
}
|
|
521
|
+
function isResizableBuffer(buffer) {
|
|
522
|
+
return buffer.resizable === true;
|
|
523
|
+
}
|
|
319
524
|
//#endregion
|
|
320
525
|
//#region src/snappy.ts
|
|
321
526
|
var BLOCK_SIZE = 65536;
|
|
@@ -543,6 +748,25 @@ function writeEnvelope(out, offset, magic, kindTag, rawLength, payload) {
|
|
|
543
748
|
view.setUint32(10, rawLength, true);
|
|
544
749
|
view.setUint32(14, checksum, true);
|
|
545
750
|
}
|
|
751
|
+
function writeEnvelopeHeader(out, magic, kindTag, rawLength, payload) {
|
|
752
|
+
const checksum = crc32(payload);
|
|
753
|
+
out.set(magic);
|
|
754
|
+
out[8] = ENVELOPE_VERSION;
|
|
755
|
+
out[9] = kindTag;
|
|
756
|
+
const view = new DataView(out.buffer, out.byteOffset, ENVELOPE_HEADER_SIZE);
|
|
757
|
+
view.setUint32(10, rawLength, true);
|
|
758
|
+
view.setUint32(14, checksum, true);
|
|
759
|
+
}
|
|
760
|
+
function envelopeFromChunks(magic, kindTag, rawLength, chunks, totalBytes) {
|
|
761
|
+
const out = new Uint8Array(ENVELOPE_HEADER_SIZE + totalBytes);
|
|
762
|
+
let offset = ENVELOPE_HEADER_SIZE;
|
|
763
|
+
for (const chunk of chunks) {
|
|
764
|
+
out.set(chunk, offset);
|
|
765
|
+
offset += chunk.byteLength;
|
|
766
|
+
}
|
|
767
|
+
writeEnvelopeHeader(out, magic, kindTag, rawLength, out.subarray(ENVELOPE_HEADER_SIZE));
|
|
768
|
+
return out;
|
|
769
|
+
}
|
|
546
770
|
function compressionIsProfitable(rawLength, compressedLength) {
|
|
547
771
|
return (compressedLength + ENVELOPE_HEADER_SIZE) * 100 <= rawLength * 90;
|
|
548
772
|
}
|
|
@@ -555,10 +779,6 @@ function snappyPreflightIsProfitable(value) {
|
|
|
555
779
|
}
|
|
556
780
|
return compressionIsProfitable(STORE_VALUE_COMPRESSION_PREFLIGHT_WINDOW_BYTES * 3, compressedLength);
|
|
557
781
|
}
|
|
558
|
-
function buildStoreRecord(input, compressed, compression) {
|
|
559
|
-
if (!compressionIsProfitable(input.byteLength, compressed.byteLength)) return buildEnvelope(STORE_RECORD_MAGIC, COMPRESSION_TAG_NONE, input.byteLength, input);
|
|
560
|
-
return buildEnvelope(STORE_RECORD_MAGIC, compressionTag(compression), input.byteLength, compressed);
|
|
561
|
-
}
|
|
562
782
|
function validateStoreValueLength(value) {
|
|
563
783
|
if (value.byteLength > MAX_DECOMPRESSED_STORE_VALUE_BYTES) throw namedError("ValueTooLargeError", `value has ${value.byteLength} bytes, exceeding the ${MAX_DECOMPRESSED_STORE_VALUE_BYTES} byte limit`);
|
|
564
784
|
}
|
|
@@ -623,19 +843,41 @@ function readEnvelopeHeader(magic, bytes, strict = true) {
|
|
|
623
843
|
payloadChecksum: view.getUint32(14, true)
|
|
624
844
|
};
|
|
625
845
|
}
|
|
626
|
-
async function
|
|
846
|
+
async function collectCompressed(input, kind, stopAtBytes) {
|
|
627
847
|
ensureCompressionRuntime();
|
|
628
848
|
let stream;
|
|
629
849
|
try {
|
|
630
|
-
stream = byteStream(
|
|
850
|
+
stream = byteStream(input).pipeThrough(new CompressionStream(kind));
|
|
631
851
|
} catch {
|
|
632
852
|
throw compressionRuntimeUnavailable();
|
|
633
853
|
}
|
|
854
|
+
const reader = stream.getReader();
|
|
855
|
+
const chunks = [];
|
|
856
|
+
let totalBytes = 0;
|
|
857
|
+
let stopped = false;
|
|
634
858
|
try {
|
|
635
|
-
|
|
859
|
+
for (let next = await reader.read(); !next.done; next = await reader.read()) {
|
|
860
|
+
const part = next.value;
|
|
861
|
+
totalBytes += part.byteLength;
|
|
862
|
+
if (stopAtBytes !== void 0 && totalBytes >= stopAtBytes) {
|
|
863
|
+
stopped = true;
|
|
864
|
+
await reader.cancel("compressed output is not smaller than the raw payload");
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
chunks.push(part);
|
|
868
|
+
}
|
|
636
869
|
} catch (error) {
|
|
637
|
-
throw namedError("InternalError", `failed to compress bytes with ${kind}: ${String(error)}`);
|
|
870
|
+
if (!stopped) throw namedError("InternalError", `failed to compress bytes with ${kind}: ${String(error)}`);
|
|
871
|
+
} finally {
|
|
872
|
+
try {
|
|
873
|
+
reader.releaseLock();
|
|
874
|
+
} catch {}
|
|
638
875
|
}
|
|
876
|
+
if (stopped || stopAtBytes !== void 0 && totalBytes >= stopAtBytes) return null;
|
|
877
|
+
return {
|
|
878
|
+
chunks,
|
|
879
|
+
totalBytes
|
|
880
|
+
};
|
|
639
881
|
}
|
|
640
882
|
async function decompressBytes(data, kind, label, maxOutputBytes) {
|
|
641
883
|
if (kind === "snappy") return decodeSnappy(data, maxOutputBytes ?? MAX_DECOMPRESSED_STORE_VALUE_BYTES, maxOutputBytes);
|
|
@@ -689,6 +931,7 @@ function compressionFromStoreFlags(flags) {
|
|
|
689
931
|
}
|
|
690
932
|
}
|
|
691
933
|
async function encodeStoreValueRecord(value, compression) {
|
|
934
|
+
validateStoreValueLength(value);
|
|
692
935
|
if (compression === false) return value;
|
|
693
936
|
if (compression === "snappy") {
|
|
694
937
|
const record = prepareFastStoreValueRecord(value, compression);
|
|
@@ -696,10 +939,16 @@ async function encodeStoreValueRecord(value, compression) {
|
|
|
696
939
|
writePreparedStoreValueRecord(record, output, 0);
|
|
697
940
|
return output;
|
|
698
941
|
}
|
|
699
|
-
validateStoreValueLength(value);
|
|
700
942
|
if (value.byteLength < 1024) return buildEnvelope(STORE_RECORD_MAGIC, COMPRESSION_TAG_NONE, value.byteLength, value);
|
|
701
|
-
const
|
|
702
|
-
|
|
943
|
+
const stored = new Uint8Array(ENVELOPE_HEADER_SIZE + value.byteLength);
|
|
944
|
+
const payload = stored.subarray(ENVELOPE_HEADER_SIZE);
|
|
945
|
+
payload.set(value);
|
|
946
|
+
const compressed = await collectCompressed(payload, compression, payload.byteLength);
|
|
947
|
+
if (compressed === null || !compressionIsProfitable(payload.byteLength, compressed.totalBytes)) {
|
|
948
|
+
writeEnvelopeHeader(stored, STORE_RECORD_MAGIC, COMPRESSION_TAG_NONE, payload.byteLength, payload);
|
|
949
|
+
return stored;
|
|
950
|
+
}
|
|
951
|
+
return envelopeFromChunks(STORE_RECORD_MAGIC, compressionTag(compression), payload.byteLength, compressed.chunks, compressed.totalBytes);
|
|
703
952
|
}
|
|
704
953
|
async function decodeStoreValueRecord(value, options) {
|
|
705
954
|
const header = readEnvelopeHeader(STORE_RECORD_MAGIC, value, options.strict);
|
|
@@ -735,9 +984,11 @@ async function decodeStoreValueRecord(value, options) {
|
|
|
735
984
|
async function wrapSnapshotWithCompression(snapshot, compression) {
|
|
736
985
|
if (compression === false) return snapshot;
|
|
737
986
|
if (!["gzip", "deflate"].includes(compression)) throw namedError("InternalError", `unsupported snapshot compression kind: ${String(compression)}`);
|
|
987
|
+
if (snapshot.byteLength > MAX_DECOMPRESSED_SNAPSHOT_BYTES) throw namedError("SerializationError", `snapshot has ${snapshot.byteLength} bytes, exceeding the ${MAX_DECOMPRESSED_SNAPSHOT_BYTES} byte limit`);
|
|
738
988
|
const input = copyBytes(snapshot);
|
|
739
|
-
const compressed = await
|
|
740
|
-
|
|
989
|
+
const compressed = await collectCompressed(input, compression);
|
|
990
|
+
if (compressed === null) throw namedError("InternalError", `failed to compress bytes with ${compression}`);
|
|
991
|
+
return envelopeFromChunks(SNAPSHOT_EXPORT_MAGIC, compressionTag(compression), input.byteLength, compressed.chunks, compressed.totalBytes);
|
|
741
992
|
}
|
|
742
993
|
async function unwrapSnapshotCompression(snapshot) {
|
|
743
994
|
const header = readEnvelopeHeader(SNAPSHOT_EXPORT_MAGIC, snapshot);
|
|
@@ -759,7 +1010,9 @@ function toWasmU64(value) {
|
|
|
759
1010
|
return BigInt(value);
|
|
760
1011
|
}
|
|
761
1012
|
function fromWasmU64(value) {
|
|
762
|
-
|
|
1013
|
+
const number = typeof value === "bigint" ? Number(value) : value;
|
|
1014
|
+
if (!Number.isSafeInteger(number) || number < 0) throw remoteError("SerializationError", "WASM integer exceeds the JavaScript safe integer range");
|
|
1015
|
+
return number;
|
|
763
1016
|
}
|
|
764
1017
|
function fromOptionalWasmU64(value) {
|
|
765
1018
|
return value === null || value === void 0 ? null : fromWasmU64(value);
|
|
@@ -771,6 +1024,7 @@ function toWasmPutOptions(options) {
|
|
|
771
1024
|
};
|
|
772
1025
|
}
|
|
773
1026
|
function toWasmRange(range) {
|
|
1027
|
+
assertValidScanRange(range);
|
|
774
1028
|
const normalized = {};
|
|
775
1029
|
if (range.gt !== void 0) normalized.gt = range.gt;
|
|
776
1030
|
if (range.gte !== void 0) normalized.gte = range.gte;
|
|
@@ -786,6 +1040,7 @@ var MAX_ENGINE_KEY_BYTES = 1024;
|
|
|
786
1040
|
var MAX_PACKED_BYTES = 4294967295;
|
|
787
1041
|
var INDEX_PUT_BATCH_ROWS = 64;
|
|
788
1042
|
var INDEX_PUT_BATCH_BYTES = 4194304;
|
|
1043
|
+
var MAX_PLAIN_STORE_VALUE_BYTES = 8388608;
|
|
789
1044
|
var INDEX_SCAN_PAGE_ROWS = 256;
|
|
790
1045
|
var INDEX_SCAN_PAGE_BYTES = 8389662;
|
|
791
1046
|
var INDEX_SCAN_RAW_CHUNK_ROWS = 512;
|
|
@@ -931,6 +1186,29 @@ function rangesEqual(left, right) {
|
|
|
931
1186
|
}
|
|
932
1187
|
return left.reverse === right.reverse && left.limit === right.limit;
|
|
933
1188
|
}
|
|
1189
|
+
function sameBound(cached, next) {
|
|
1190
|
+
if (cached === void 0 || next === void 0) return cached === next;
|
|
1191
|
+
return bytesEqual(cached, next);
|
|
1192
|
+
}
|
|
1193
|
+
function copyBound(value) {
|
|
1194
|
+
return value === void 0 ? void 0 : value.slice();
|
|
1195
|
+
}
|
|
1196
|
+
function compoundEntryKeyLength(logicalKey, primaryKey) {
|
|
1197
|
+
return escapedPartLength(logicalKey) + escapedPartLength(primaryKey);
|
|
1198
|
+
}
|
|
1199
|
+
function escapedPartLength(part) {
|
|
1200
|
+
let length = 2;
|
|
1201
|
+
for (let index = 0; index < part.length; index += 1) length += part[index] === 0 ? 2 : 1;
|
|
1202
|
+
return length;
|
|
1203
|
+
}
|
|
1204
|
+
function limitPlainStoreValue(value) {
|
|
1205
|
+
if (value.byteLength > MAX_PLAIN_STORE_VALUE_BYTES) {
|
|
1206
|
+
const error = /* @__PURE__ */ new Error(`stored value has ${value.byteLength} bytes, exceeding the ${MAX_PLAIN_STORE_VALUE_BYTES} byte limit`);
|
|
1207
|
+
error.name = "ValueTooLargeError";
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
return value;
|
|
1211
|
+
}
|
|
934
1212
|
function normalizeWasmBytes(value) {
|
|
935
1213
|
if (value instanceof Uint8Array) return value.byteOffset === 0 && value.byteLength === value.buffer.byteLength ? value : value.slice();
|
|
936
1214
|
return new Uint8Array(value);
|
|
@@ -1088,6 +1366,9 @@ var DbWorker = class {
|
|
|
1088
1366
|
*/
|
|
1089
1367
|
txIndexSchemas = /* @__PURE__ */ new Map();
|
|
1090
1368
|
indexesBySchema = /* @__PURE__ */ new WeakMap();
|
|
1369
|
+
/** Readonly scans reuse a resolved index; readwrite scans recheck so TTL staging still runs. */
|
|
1370
|
+
readonlyIndexCache = /* @__PURE__ */ new Map();
|
|
1371
|
+
indexBoundCache = null;
|
|
1091
1372
|
txEnsuredRawStores = /* @__PURE__ */ new Map();
|
|
1092
1373
|
txIndexSchemaChanged = /* @__PURE__ */ new Set();
|
|
1093
1374
|
txStoreCompression = /* @__PURE__ */ new Map();
|
|
@@ -1326,9 +1607,14 @@ var DbWorker = class {
|
|
|
1326
1607
|
return Promise.resolve(this.withEngine((engine) => engine.has(toWasmU64(txId), store, key)));
|
|
1327
1608
|
}
|
|
1328
1609
|
async put(txId, store, key, value, options = {}) {
|
|
1329
|
-
|
|
1610
|
+
await this.putEntry(txId, store, key, value, options);
|
|
1611
|
+
}
|
|
1612
|
+
async putEntry(txId, store, key, value, options, knownDefs, knownCompression) {
|
|
1613
|
+
const defs = knownDefs ?? this.indexesForStoreInTx(txId, store);
|
|
1614
|
+
const compression = knownCompression ?? this.storeCompressionForTx(txId, store);
|
|
1330
1615
|
if (defs.length === 0) {
|
|
1331
|
-
const
|
|
1616
|
+
const preparedValue = this.prepareStoredValue(store, value, compression);
|
|
1617
|
+
const storedValue = preparedValue instanceof Uint8Array ? preparedValue : await preparedValue;
|
|
1332
1618
|
const baselineExists = this.withEngine((engine) => engine.put(toWasmU64(txId), store, key, storedValue, toWasmPutOptions(options)));
|
|
1333
1619
|
this.recordPutChange(txId, store, key, baselineExists);
|
|
1334
1620
|
return;
|
|
@@ -1348,7 +1634,7 @@ var DbWorker = class {
|
|
|
1348
1634
|
this.recordPutChanges(txId, store, entries, partial);
|
|
1349
1635
|
throw error;
|
|
1350
1636
|
}
|
|
1351
|
-
if (baselines.length !== entries.length) throw
|
|
1637
|
+
if (baselines.length !== entries.length) throw outcomeCountMismatch("put_many", baselines.length, entries.length);
|
|
1352
1638
|
this.recordPutChanges(txId, store, entries, baselines);
|
|
1353
1639
|
return;
|
|
1354
1640
|
}
|
|
@@ -1380,7 +1666,7 @@ var DbWorker = class {
|
|
|
1380
1666
|
this.recordPutKeyChanges(txId, store, keys, partial);
|
|
1381
1667
|
throw error;
|
|
1382
1668
|
}
|
|
1383
|
-
if (baselines.length !== keys.length) throw
|
|
1669
|
+
if (baselines.length !== keys.length) throw outcomeCountMismatch("put_many_packed", baselines.length, keys.length);
|
|
1384
1670
|
this.recordPutKeyChanges(txId, store, keys, baselines);
|
|
1385
1671
|
}
|
|
1386
1672
|
async delete(txId, store, key) {
|
|
@@ -1410,7 +1696,7 @@ var DbWorker = class {
|
|
|
1410
1696
|
for (let i = 0; i < partial.length && i < keys.length; i += 1) this.recordDeleteChange(txId, store, keys[i], partial[i]);
|
|
1411
1697
|
throw error;
|
|
1412
1698
|
}
|
|
1413
|
-
if (deleted.length !== keys.length) throw
|
|
1699
|
+
if (deleted.length !== keys.length) throw outcomeCountMismatch("delete_many", deleted.length, keys.length);
|
|
1414
1700
|
for (let i = 0; i < keys.length; i += 1) this.recordDeleteChange(txId, store, keys[i], deleted[i]);
|
|
1415
1701
|
return;
|
|
1416
1702
|
}
|
|
@@ -1432,7 +1718,7 @@ var DbWorker = class {
|
|
|
1432
1718
|
for (let i = 0; i < partial.length && i < keys.length; i += 1) this.recordDeleteChange(txId, store, keys[i], partial[i]);
|
|
1433
1719
|
throw error;
|
|
1434
1720
|
}
|
|
1435
|
-
if (deleted.length !== keys.length) throw
|
|
1721
|
+
if (deleted.length !== keys.length) throw outcomeCountMismatch("delete_many_packed", deleted.length, keys.length);
|
|
1436
1722
|
for (let i = 0; i < keys.length; i += 1) this.recordDeleteChange(txId, store, keys[i], deleted[i] === 1);
|
|
1437
1723
|
}
|
|
1438
1724
|
async applyBatch(txId, store, ops) {
|
|
@@ -1446,7 +1732,7 @@ var DbWorker = class {
|
|
|
1446
1732
|
this.recordBatchOutcomes(txId, store, ops, partialBatchOutcomes(error));
|
|
1447
1733
|
throw error;
|
|
1448
1734
|
}
|
|
1449
|
-
if (outcomes.length !== ops.length) throw
|
|
1735
|
+
if (outcomes.length !== ops.length) throw outcomeCountMismatch("apply_batch", outcomes.length, ops.length);
|
|
1450
1736
|
this.recordBatchOutcomes(txId, store, ops, outcomes);
|
|
1451
1737
|
return;
|
|
1452
1738
|
}
|
|
@@ -1466,10 +1752,10 @@ var DbWorker = class {
|
|
|
1466
1752
|
try {
|
|
1467
1753
|
flags = this.withEngine((engine) => engine.apply_batch_packed(toWasmU64(txId), store, packedOps));
|
|
1468
1754
|
} catch (error) {
|
|
1469
|
-
this.
|
|
1755
|
+
this.recordBatchOutcomes(txId, store, ops, partialBatchOutcomes(error));
|
|
1470
1756
|
throw error;
|
|
1471
1757
|
}
|
|
1472
|
-
if (flags.length !== ops.length) throw
|
|
1758
|
+
if (flags.length !== ops.length) throw outcomeCountMismatch("apply_batch_packed", flags.length, ops.length);
|
|
1473
1759
|
for (let i = 0; i < ops.length; i += 1) {
|
|
1474
1760
|
const op = ops[i];
|
|
1475
1761
|
if (op.kind === "put") this.recordPutChange(txId, store, op.key, flags[i] === 1);
|
|
@@ -1520,7 +1806,7 @@ var DbWorker = class {
|
|
|
1520
1806
|
if (rows !== null) {
|
|
1521
1807
|
bytes = 4;
|
|
1522
1808
|
for (const row of rows) {
|
|
1523
|
-
row.value = await
|
|
1809
|
+
row.value = await decodeStoreValueRecord(row.value, { strict: true });
|
|
1524
1810
|
bytes += 8 + row.key.byteLength + row.value.byteLength;
|
|
1525
1811
|
if (bytes > request.maxBytes) throw remoteError("ValueTooLargeError", "decoded scan page exceeds maxBytes");
|
|
1526
1812
|
}
|
|
@@ -1558,12 +1844,15 @@ var DbWorker = class {
|
|
|
1558
1844
|
return page.rows.length === 0 ? null : page.rows[0].value;
|
|
1559
1845
|
}
|
|
1560
1846
|
async scanByIndex(txId, store, indexName, range = {}) {
|
|
1847
|
+
assertValidScanRange(range);
|
|
1561
1848
|
const def = this.resolveIndexDefinition(txId, store, indexName);
|
|
1562
1849
|
const rows = [];
|
|
1563
1850
|
let remaining = range.limit ?? Number.POSITIVE_INFINITY;
|
|
1851
|
+
if (remaining <= 0) return rows;
|
|
1852
|
+
const physical = this.physicalIndexRange(txId, def, range);
|
|
1564
1853
|
let cursor = null;
|
|
1565
1854
|
while (remaining > 0) {
|
|
1566
|
-
const page = await this.loadVisibleIndexPage(txId, def, range, cursor, Math.min(remaining, INDEX_SCAN_PAGE_ROWS));
|
|
1855
|
+
const page = await this.loadVisibleIndexPage(txId, def, range, cursor, Math.min(remaining, INDEX_SCAN_PAGE_ROWS), INDEX_SCAN_PAGE_BYTES, physical);
|
|
1567
1856
|
rows.push(...page.rows);
|
|
1568
1857
|
remaining -= page.rows.length;
|
|
1569
1858
|
if (page.cursor === null) break;
|
|
@@ -1575,7 +1864,7 @@ var DbWorker = class {
|
|
|
1575
1864
|
if (!Number.isSafeInteger(limit) || limit <= 0) throw remoteError("InvalidRangeError", "index scan page limit must be a positive integer");
|
|
1576
1865
|
if (!Number.isSafeInteger(maxBytes) || maxBytes < 4 || maxBytes > MAX_PACKED_BYTES) throw remoteError("InvalidRangeError", "index scan page maxBytes must be a u32 integer of at least 4");
|
|
1577
1866
|
const def = this.resolveIndexDefinition(txId, store, indexName);
|
|
1578
|
-
return this.loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes);
|
|
1867
|
+
return this.loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes, this.physicalIndexRange(txId, def, range));
|
|
1579
1868
|
}
|
|
1580
1869
|
getIndexes(txId) {
|
|
1581
1870
|
return Promise.resolve(toPublicIndexDefinitions(txId === void 0 ? this.loadCommittedIndexSchema() : this.indexSchemaForTx(txId)));
|
|
@@ -1605,7 +1894,7 @@ var DbWorker = class {
|
|
|
1605
1894
|
let rows;
|
|
1606
1895
|
if (sourceRows !== null) {
|
|
1607
1896
|
const visible = this.withEngine((engine) => engine.has_many(toWasmU64(txId), def.store, sourceRows.map((row) => row.key)));
|
|
1608
|
-
if (visible.length !== sourceRows.length) throw
|
|
1897
|
+
if (visible.length !== sourceRows.length) throw outcomeCountMismatch("has_many", visible.length, sourceRows.length);
|
|
1609
1898
|
rows = sourceRows.filter((_row, index) => visible[index]);
|
|
1610
1899
|
} else {
|
|
1611
1900
|
rows = await this.readStoreScan(txId, def.store, EMPTY_RANGE);
|
|
@@ -1697,12 +1986,9 @@ var DbWorker = class {
|
|
|
1697
1986
|
return await wrapSnapshotWithCompression(wrapSnapshotWithIndexDefinitions(normalizeWasmBytes(this.withEngine((engine) => engine.export_snapshot())), defs), options.compression ?? false);
|
|
1698
1987
|
}
|
|
1699
1988
|
async importSnapshot(data) {
|
|
1700
|
-
this.
|
|
1989
|
+
this.ensureNotInMaintenance();
|
|
1701
1990
|
const parsed = unwrapSnapshotWithIndexDefinitions(await unwrapSnapshotCompression(data));
|
|
1702
|
-
this.
|
|
1703
|
-
this.committedIndexes = [];
|
|
1704
|
-
this.committedStoreCompression = null;
|
|
1705
|
-
await this.applyCommittedIndexSchema(parsed.indexes);
|
|
1991
|
+
await this.runGenerationOperation("import", parsed);
|
|
1706
1992
|
}
|
|
1707
1993
|
async reset() {
|
|
1708
1994
|
const dbName = this.dbName;
|
|
@@ -1730,16 +2016,16 @@ var DbWorker = class {
|
|
|
1730
2016
|
}
|
|
1731
2017
|
}
|
|
1732
2018
|
async compact() {
|
|
1733
|
-
return this.
|
|
2019
|
+
return this.runGenerationOperation("compact");
|
|
1734
2020
|
}
|
|
1735
2021
|
async rebuild() {
|
|
1736
|
-
return this.
|
|
2022
|
+
return this.runGenerationOperation("rebuild");
|
|
1737
2023
|
}
|
|
1738
2024
|
stats() {
|
|
1739
2025
|
const stats = this.withEngine((engine) => engine.stats());
|
|
1740
2026
|
return Promise.resolve({
|
|
1741
2027
|
...stats,
|
|
1742
|
-
db_id:
|
|
2028
|
+
db_id: typeof stats.db_id === "bigint" ? Number(stats.db_id) : stats.db_id,
|
|
1743
2029
|
catalog_root_page_id: fromWasmU64(stats.catalog_root_page_id),
|
|
1744
2030
|
next_page_id: fromWasmU64(stats.next_page_id),
|
|
1745
2031
|
last_committed_txid: fromWasmU64(stats.last_committed_txid),
|
|
@@ -1800,7 +2086,7 @@ var DbWorker = class {
|
|
|
1800
2086
|
});
|
|
1801
2087
|
try {
|
|
1802
2088
|
await this.reconcileIndexes(txId, toPublicIndexDefinitions(normalized));
|
|
1803
|
-
this.withEngine((engine) => engine.commit_tx(toWasmU64(txId)), { allowDuringMaintenance: true });
|
|
2089
|
+
fromWasmU64(this.withEngine((engine) => engine.commit_tx(toWasmU64(txId)), { allowDuringMaintenance: true }));
|
|
1804
2090
|
this.committedIndexes = cloneNormalizedIndexDefinitions(normalized);
|
|
1805
2091
|
} catch (error) {
|
|
1806
2092
|
try {
|
|
@@ -1816,18 +2102,19 @@ var DbWorker = class {
|
|
|
1816
2102
|
* A failed swap may have reached storage; read the control file before cleanup.
|
|
1817
2103
|
* After publication, abandon the old engine to avoid a checkpoint into inactive files.
|
|
1818
2104
|
*/
|
|
1819
|
-
async
|
|
2105
|
+
async runGenerationOperation(operation, imported) {
|
|
1820
2106
|
const dbName = this.requireDbName();
|
|
1821
2107
|
if (this.maintenanceOperation) throw remoteError("DatabaseBusyError", `database ${dbName} is busy: ${this.maintenanceOperation} in progress`);
|
|
1822
2108
|
this.maintenanceOperation = operation;
|
|
1823
2109
|
const start = performance.now();
|
|
1824
|
-
|
|
2110
|
+
let wasm = null;
|
|
1825
2111
|
let rebuiltEngine = null;
|
|
1826
2112
|
let generationName = null;
|
|
1827
2113
|
let published = false;
|
|
1828
2114
|
try {
|
|
2115
|
+
wasm = await this.loadWasm();
|
|
1829
2116
|
const sizeBefore = await wasm.dbDirectorySize(dbName);
|
|
1830
|
-
const defs = cloneNormalizedIndexDefinitions(this.loadCommittedIndexSchema());
|
|
2117
|
+
const defs = cloneNormalizedIndexDefinitions(imported?.indexes ?? this.loadCommittedIndexSchema());
|
|
1831
2118
|
this.rollbackAllTransactions({ allowDuringMaintenance: true });
|
|
1832
2119
|
const expectedGeneration = await wasm.readActiveGeneration(dbName);
|
|
1833
2120
|
generationName = (await wasm.prepareRebuildTarget(dbName)).generationName;
|
|
@@ -1837,8 +2124,8 @@ var DbWorker = class {
|
|
|
1837
2124
|
cache_pages: this.currentCachePages()
|
|
1838
2125
|
});
|
|
1839
2126
|
const target = rebuiltEngine;
|
|
1840
|
-
this.withEngine((engine) => operation === "rebuild" ? engine.compact_into_skipping_stores(target, [...defs.map((def) => def.internalStore), INDEX_METADATA_STORE]) : engine.compact_into(target), { allowDuringMaintenance: true });
|
|
1841
|
-
if (operation === "rebuild" && defs.length > 0) {
|
|
2127
|
+
this.withEngine((engine) => imported ? fromWasmU64(engine.import_snapshot_into(target, imported.snapshot)) : operation === "rebuild" ? engine.compact_into_skipping_stores(target, [...defs.map((def) => def.internalStore), INDEX_METADATA_STORE]) : engine.compact_into(target), { allowDuringMaintenance: true });
|
|
2128
|
+
if ((operation === "rebuild" || imported) && defs.length > 0) {
|
|
1842
2129
|
const previousEngine = this.engine;
|
|
1843
2130
|
const previousMaintenance = this.maintenanceOperation;
|
|
1844
2131
|
this.engine = target;
|
|
@@ -1871,7 +2158,7 @@ var DbWorker = class {
|
|
|
1871
2158
|
try {
|
|
1872
2159
|
await wasm.cleanupInactiveEntries(dbName);
|
|
1873
2160
|
} catch {}
|
|
1874
|
-
const sizeAfter = await wasm.dbDirectorySize(dbName);
|
|
2161
|
+
const sizeAfter = imported ? sizeBefore : await wasm.dbDirectorySize(dbName);
|
|
1875
2162
|
return {
|
|
1876
2163
|
sizeBefore,
|
|
1877
2164
|
sizeAfter,
|
|
@@ -1885,7 +2172,7 @@ var DbWorker = class {
|
|
|
1885
2172
|
try {
|
|
1886
2173
|
rebuiltEngine?.abandon();
|
|
1887
2174
|
} catch {}
|
|
1888
|
-
if (generationName !== null) {
|
|
2175
|
+
if (generationName !== null && wasm !== null) {
|
|
1889
2176
|
const active = await readActiveGenerationOrNull(wasm, dbName);
|
|
1890
2177
|
if (active !== void 0 && active !== generationName) try {
|
|
1891
2178
|
await wasm.cleanupInactiveEntries(dbName);
|
|
@@ -1927,9 +2214,11 @@ var DbWorker = class {
|
|
|
1927
2214
|
this.txChanges.delete(txId);
|
|
1928
2215
|
this.txModes.delete(txId);
|
|
1929
2216
|
this.txIndexSchemas.delete(txId);
|
|
2217
|
+
this.readonlyIndexCache.delete(txId);
|
|
1930
2218
|
this.txEnsuredRawStores.delete(txId);
|
|
1931
2219
|
this.txIndexSchemaChanged.delete(txId);
|
|
1932
2220
|
this.txStoreCompression.delete(txId);
|
|
2221
|
+
if (this.indexBoundCache?.txId === txId) this.indexBoundCache = null;
|
|
1933
2222
|
}
|
|
1934
2223
|
getOrCreateTrackedTxn(txId) {
|
|
1935
2224
|
return getOrInsert(this.txChanges, txId, () => /* @__PURE__ */ new Map());
|
|
@@ -1995,14 +2284,6 @@ var DbWorker = class {
|
|
|
1995
2284
|
else if (op.kind === "delete" && outcome.kind === "delete") this.recordDeleteChange(txId, store, op.key, outcome.deleted);
|
|
1996
2285
|
}
|
|
1997
2286
|
}
|
|
1998
|
-
recordBatchKeyOutcomes(txId, store, ops, outcomes) {
|
|
1999
|
-
for (let i = 0; i < outcomes.length && i < ops.length; i += 1) {
|
|
2000
|
-
const op = ops[i];
|
|
2001
|
-
const outcome = outcomes[i];
|
|
2002
|
-
if (op.kind === "put" && outcome.kind === "put") this.recordPutChange(txId, store, op.key, outcome.baselineExists);
|
|
2003
|
-
else if (op.kind === "delete" && outcome.kind === "delete") this.recordDeleteChange(txId, store, op.key, outcome.deleted);
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
2287
|
finalizeTrackedChanges(tracked) {
|
|
2007
2288
|
const stores = [];
|
|
2008
2289
|
for (const [store, trackedStore] of tracked) {
|
|
@@ -2225,22 +2506,20 @@ var DbWorker = class {
|
|
|
2225
2506
|
} : op);
|
|
2226
2507
|
return storedOps;
|
|
2227
2508
|
}
|
|
2228
|
-
|
|
2229
|
-
if (isInternalStoreName(store)) return value;
|
|
2230
|
-
return encodeStoreValueRecord(value, this.storeCompressionForTx(txId, store));
|
|
2231
|
-
}
|
|
2232
|
-
async decodeStoreValue(txId, store, value) {
|
|
2509
|
+
prepareStoredValue(store, value, compression) {
|
|
2233
2510
|
if (isInternalStoreName(store)) return value;
|
|
2234
|
-
if (
|
|
2235
|
-
return
|
|
2511
|
+
if (compression === false) return limitPlainStoreValue(value);
|
|
2512
|
+
return encodeStoreValueRecord(value, compression);
|
|
2236
2513
|
}
|
|
2237
2514
|
async decodeStoreValueForFeed(store, value) {
|
|
2238
2515
|
if (isInternalStoreName(store)) return value;
|
|
2239
2516
|
return decodeStoreValueRecord(value, { strict: (this.loadCommittedStoreCompression().get(store) ?? false) !== false });
|
|
2240
2517
|
}
|
|
2241
|
-
async readStoreValue(txId, store, key) {
|
|
2518
|
+
async readStoreValue(txId, store, key, knownCompression) {
|
|
2242
2519
|
const value = this.readRawValue(txId, store, key);
|
|
2243
|
-
|
|
2520
|
+
if (value === null || isInternalStoreName(store)) return value;
|
|
2521
|
+
if ((knownCompression ?? this.storeCompressionForTx(txId, store)) === false) return value;
|
|
2522
|
+
return await decodeStoreValueRecord(value, { strict: true });
|
|
2244
2523
|
}
|
|
2245
2524
|
async readStoreScan(txId, store, range) {
|
|
2246
2525
|
const rows = this.readRawScan(txId, store, range);
|
|
@@ -2250,7 +2529,7 @@ var DbWorker = class {
|
|
|
2250
2529
|
for (let index = 0; index < rows.length; index += 1) rows[index].value = values[index];
|
|
2251
2530
|
return rows;
|
|
2252
2531
|
}
|
|
2253
|
-
async decodeStoreValues(
|
|
2532
|
+
async decodeStoreValues(_txId, _store, values) {
|
|
2254
2533
|
const state = {
|
|
2255
2534
|
next: 0,
|
|
2256
2535
|
failed: false,
|
|
@@ -2262,7 +2541,7 @@ var DbWorker = class {
|
|
|
2262
2541
|
const value = values[index];
|
|
2263
2542
|
if (value === null) continue;
|
|
2264
2543
|
try {
|
|
2265
|
-
values[index] = await
|
|
2544
|
+
values[index] = await decodeStoreValueRecord(value, { strict: true });
|
|
2266
2545
|
} catch (error) {
|
|
2267
2546
|
if (!state.failed) {
|
|
2268
2547
|
state.failed = true;
|
|
@@ -2290,11 +2569,42 @@ var DbWorker = class {
|
|
|
2290
2569
|
return byStore.get(store) ?? [];
|
|
2291
2570
|
}
|
|
2292
2571
|
resolveIndexDefinition(txId, store, indexName) {
|
|
2572
|
+
if (this.txModes.get(txId) === "readonly") {
|
|
2573
|
+
const byStore = this.readonlyIndexCache.get(txId)?.get(store);
|
|
2574
|
+
if (byStore) {
|
|
2575
|
+
const cached = byStore.get(indexName);
|
|
2576
|
+
if (cached) return cached;
|
|
2577
|
+
const def = findIndexDefinition(this.indexesForStoreInTx(txId, store), store, indexName);
|
|
2578
|
+
if (!def) throw remoteError("IndexNotFoundError", `index not found: ${store}.${indexName}`);
|
|
2579
|
+
byStore.set(indexName, def);
|
|
2580
|
+
return def;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2293
2583
|
if (!this.rawStoreExistsInTx(txId, store)) throw remoteError("StoreNotFoundError", `store not found: ${store}`);
|
|
2294
2584
|
const def = findIndexDefinition(this.indexesForStoreInTx(txId, store), store, indexName);
|
|
2295
2585
|
if (!def) throw remoteError("IndexNotFoundError", `index not found: ${store}.${indexName}`);
|
|
2586
|
+
if (this.txModes.get(txId) === "readonly") getOrInsert(getOrInsert(this.readonlyIndexCache, txId, () => /* @__PURE__ */ new Map()), store, () => /* @__PURE__ */ new Map()).set(indexName, def);
|
|
2296
2587
|
return def;
|
|
2297
2588
|
}
|
|
2589
|
+
physicalIndexRange(txId, def, range) {
|
|
2590
|
+
assertValidScanRange(range);
|
|
2591
|
+
const reverse = range.reverse === true;
|
|
2592
|
+
const cached = this.indexBoundCache;
|
|
2593
|
+
if (cached !== null && cached.txId === txId && cached.def === def && cached.reverse === reverse && sameBound(cached.gt, range.gt) && sameBound(cached.gte, range.gte) && sameBound(cached.lt, range.lt) && sameBound(cached.lte, range.lte)) return cached.physical;
|
|
2594
|
+
const { limit: _ignored, ...withoutLimit } = range;
|
|
2595
|
+
const physical = indexRangeToPhysicalRange(withoutLimit);
|
|
2596
|
+
this.indexBoundCache = {
|
|
2597
|
+
txId,
|
|
2598
|
+
def,
|
|
2599
|
+
reverse,
|
|
2600
|
+
gt: copyBound(range.gt),
|
|
2601
|
+
gte: copyBound(range.gte),
|
|
2602
|
+
lt: copyBound(range.lt),
|
|
2603
|
+
lte: copyBound(range.lte),
|
|
2604
|
+
physical
|
|
2605
|
+
};
|
|
2606
|
+
return physical;
|
|
2607
|
+
}
|
|
2298
2608
|
indexSchemaForTx(txId) {
|
|
2299
2609
|
const override = this.txIndexSchemas.get(txId);
|
|
2300
2610
|
if (override) return override;
|
|
@@ -2474,7 +2784,10 @@ var DbWorker = class {
|
|
|
2474
2784
|
if (physicalKey.byteLength > MAX_ENGINE_KEY_BYTES) throw remoteError("KeyTooLargeError", `index key too large for ${def.store}.${def.name}`);
|
|
2475
2785
|
return physicalKey;
|
|
2476
2786
|
}
|
|
2477
|
-
|
|
2787
|
+
assertIndexEntryKeySize(def, logicalKey, primaryKey) {
|
|
2788
|
+
if (compoundEntryKeyLength(logicalKey, primaryKey) > MAX_ENGINE_KEY_BYTES) throw remoteError("KeyTooLargeError", `index key too large for ${def.store}.${def.name}`);
|
|
2789
|
+
}
|
|
2790
|
+
async assertUniqueIndexAvailability(txId, def, logicalKey, primaryKey, knownCompression) {
|
|
2478
2791
|
if (!def.unique) return;
|
|
2479
2792
|
const physical = indexKeyExactRange(logicalKey);
|
|
2480
2793
|
let cursor = null;
|
|
@@ -2499,7 +2812,7 @@ var DbWorker = class {
|
|
|
2499
2812
|
continue;
|
|
2500
2813
|
}
|
|
2501
2814
|
if (state === "trusted") throw remoteError("UniqueIndexConstraintError", `unique index constraint violation on ${def.store}.${def.name}`);
|
|
2502
|
-
const value = await this.readStoreValue(txId, def.store, decoded.primaryKey);
|
|
2815
|
+
const value = await this.readStoreValue(txId, def.store, decoded.primaryKey, knownCompression);
|
|
2503
2816
|
if (value === null) {
|
|
2504
2817
|
this.cleanupStaleIndexRow(txId, def, row.key);
|
|
2505
2818
|
continue;
|
|
@@ -2520,10 +2833,11 @@ var DbWorker = class {
|
|
|
2520
2833
|
* chunks prevent stale entries from forcing the whole range into memory.
|
|
2521
2834
|
* The output limit counts only live rows.
|
|
2522
2835
|
*/
|
|
2523
|
-
async loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes = INDEX_SCAN_PAGE_BYTES) {
|
|
2836
|
+
async loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes = INDEX_SCAN_PAGE_BYTES, physicalRange) {
|
|
2524
2837
|
const { limit: _ignored, ...withoutLimit } = range;
|
|
2525
|
-
const physical = indexRangeToPhysicalRange(withoutLimit);
|
|
2838
|
+
const physical = physicalRange ?? indexRangeToPhysicalRange(withoutLimit);
|
|
2526
2839
|
const reverse = physical.reverse === true;
|
|
2840
|
+
const compression = this.storeCompressionForTx(txId, def.store);
|
|
2527
2841
|
let resumeAfter = cursor;
|
|
2528
2842
|
const visible = [];
|
|
2529
2843
|
let outputBytes = 4;
|
|
@@ -2562,9 +2876,10 @@ var DbWorker = class {
|
|
|
2562
2876
|
batchStart = index;
|
|
2563
2877
|
batch = this.readIndexPrimaryValues(txId, def, rawRows, index, Math.min(rawRows.length, index + limit - visible.length), maxBytes - outputBytes);
|
|
2564
2878
|
batchEnd = index + (batch?.entries.length ?? 1);
|
|
2879
|
+
if (batch && compression !== false) this.stabilizeWasmValues(batch.values);
|
|
2565
2880
|
}
|
|
2566
2881
|
const row = rawRows[index];
|
|
2567
|
-
const resolved = batch ? await this.resolveVisibleIndexedValue(txId, def, row.key, batch.entries[index - batchStart], batch.values[index - batchStart], batch.trusted[index - batchStart]) : await this.
|
|
2882
|
+
const resolved = batch ? await this.resolveVisibleIndexedValue(txId, def, row.key, batch.entries[index - batchStart], batch.values[index - batchStart], batch.trusted[index - batchStart], compression, true) : await this.visibleIndexedRow(txId, def, row.key, compression);
|
|
2568
2883
|
if (batch !== null) batch.values[index - batchStart] = null;
|
|
2569
2884
|
if (!resolved) {
|
|
2570
2885
|
resumeAfter = row.key;
|
|
@@ -2584,7 +2899,7 @@ var DbWorker = class {
|
|
|
2584
2899
|
resumeAfter = row.key;
|
|
2585
2900
|
if (visible.length >= limit) return {
|
|
2586
2901
|
rows: visible,
|
|
2587
|
-
cursor: row.key
|
|
2902
|
+
cursor: row.key
|
|
2588
2903
|
};
|
|
2589
2904
|
}
|
|
2590
2905
|
if (rawRows.length < chunkLimit) return {
|
|
@@ -2646,7 +2961,7 @@ var DbWorker = class {
|
|
|
2646
2961
|
} catch {
|
|
2647
2962
|
return null;
|
|
2648
2963
|
}
|
|
2649
|
-
if (loaded.length !== selectedIndices.length) throw
|
|
2964
|
+
if (loaded.length !== selectedIndices.length) throw outcomeCountMismatch("get_many", loaded.length, selectedIndices.length);
|
|
2650
2965
|
for (let index = 0; index < loaded.length; index += 1) {
|
|
2651
2966
|
values[selectedIndices[index]] = loaded[index];
|
|
2652
2967
|
loaded[index] = null;
|
|
@@ -2657,7 +2972,13 @@ var DbWorker = class {
|
|
|
2657
2972
|
trusted
|
|
2658
2973
|
};
|
|
2659
2974
|
}
|
|
2660
|
-
|
|
2975
|
+
stabilizeWasmValues(values) {
|
|
2976
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
2977
|
+
const value = values[index];
|
|
2978
|
+
if (value !== null) values[index] = normalizeWasmBytes(value);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
visibleIndexedRow(txId, def, physicalKey, compression) {
|
|
2661
2982
|
const decoded = decodeIndexEntryKey(physicalKey);
|
|
2662
2983
|
const state = this.inspectIndexedValue(txId, def, physicalKey, decoded.primaryKey);
|
|
2663
2984
|
if (state === "missing") {
|
|
@@ -2665,14 +2986,18 @@ var DbWorker = class {
|
|
|
2665
2986
|
return null;
|
|
2666
2987
|
}
|
|
2667
2988
|
const value = this.readRawValue(txId, def.store, decoded.primaryKey);
|
|
2668
|
-
return this.resolveVisibleIndexedValue(txId, def, physicalKey, decoded, value, state === "trusted");
|
|
2989
|
+
return this.resolveVisibleIndexedValue(txId, def, physicalKey, decoded, value, state === "trusted", compression, true);
|
|
2669
2990
|
}
|
|
2670
|
-
|
|
2991
|
+
resolveVisibleIndexedValue(txId, def, physicalKey, decoded, rawValue, trusted = false, compression, stable) {
|
|
2671
2992
|
if (rawValue === null) {
|
|
2672
2993
|
this.cleanupStaleIndexRow(txId, def, physicalKey);
|
|
2673
2994
|
return null;
|
|
2674
2995
|
}
|
|
2675
|
-
const
|
|
2996
|
+
const bytes = stable ? rawValue : normalizeWasmBytes(rawValue);
|
|
2997
|
+
if (compression === false) return this.finishVisibleIndexedValue(txId, def, physicalKey, decoded, bytes, trusted);
|
|
2998
|
+
return decodeStoreValueRecord(bytes, { strict: true }).then((value) => this.finishVisibleIndexedValue(txId, def, physicalKey, decoded, value, trusted));
|
|
2999
|
+
}
|
|
3000
|
+
finishVisibleIndexedValue(txId, def, physicalKey, decoded, value, trusted) {
|
|
2676
3001
|
if (!trusted) {
|
|
2677
3002
|
const currentLogicalKey = extractLogicalIndexKey(def, value);
|
|
2678
3003
|
if (currentLogicalKey === null || !bytesEqual(currentLogicalKey, decoded.logicalKey)) {
|
|
@@ -2726,6 +3051,8 @@ var DbWorker = class {
|
|
|
2726
3051
|
this.txChanges.clear();
|
|
2727
3052
|
this.txModes.clear();
|
|
2728
3053
|
this.txIndexSchemas.clear();
|
|
3054
|
+
this.readonlyIndexCache.clear();
|
|
3055
|
+
this.indexBoundCache = null;
|
|
2729
3056
|
this.txEnsuredRawStores.clear();
|
|
2730
3057
|
this.txIndexSchemaChanged.clear();
|
|
2731
3058
|
this.txStoreCompression.clear();
|
|
@@ -2808,6 +3135,9 @@ function normalizeEngineHealth(health) {
|
|
|
2808
3135
|
};
|
|
2809
3136
|
return { state: health.state };
|
|
2810
3137
|
}
|
|
3138
|
+
function outcomeCountMismatch(operation, actual, expected) {
|
|
3139
|
+
return remoteError("InternalError", `${operation} outcome count mismatch: ${actual} != ${expected}`);
|
|
3140
|
+
}
|
|
2811
3141
|
function partialBooleanOutcomes(error) {
|
|
2812
3142
|
if (!isRecord(error) || !Array.isArray(error.partial)) return [];
|
|
2813
3143
|
return error.partial.filter((value) => typeof value === "boolean");
|