@moyodb/sdk 1.4.0 → 1.5.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 (36) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/browser-worker-p7uX85l-.js +4 -0
  3. package/dist/assets/browser-worker-p7uX85l-.js.map +1 -0
  4. package/dist/assets/shared-worker-BALTqEUe.js +2 -0
  5. package/dist/assets/shared-worker-BALTqEUe.js.map +1 -0
  6. package/dist/chunks/{indexing-B0duqlUs.js → indexing-DdpU69Fd.js} +302 -69
  7. package/dist/chunks/{indexing-B0duqlUs.js.map → indexing-DdpU69Fd.js.map} +1 -1
  8. package/dist/codec.d.ts.map +1 -1
  9. package/dist/compression.d.ts.map +1 -1
  10. package/dist/engine/README.md +2 -2
  11. package/dist/engine/moyodb_engine.d.ts +3 -3
  12. package/dist/engine/moyodb_engine.js +34 -34
  13. package/dist/engine/moyodb_engine_bg.wasm +0 -0
  14. package/dist/engine/moyodb_engine_bg.wasm.d.ts +2 -2
  15. package/dist/engine/package.json +1 -1
  16. package/dist/engine/snippets/{moyodb-engine-1d53039816cbc2f7 → moyodb-engine-e9fc900519b160aa}/js/opfs_shim.js +8 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/indexing.d.ts +7 -0
  19. package/dist/indexing.d.ts.map +1 -1
  20. package/dist/moyodb-sdk.js +1329 -931
  21. package/dist/moyodb-sdk.js.map +1 -1
  22. package/dist/node-engine.js +382 -47
  23. package/dist/node-engine.js.map +1 -1
  24. package/dist/node.js +332 -44
  25. package/dist/node.js.map +1 -1
  26. package/dist/registry.d.ts.map +1 -1
  27. package/dist/subscriptions.d.ts.map +1 -1
  28. package/dist/worker-client.d.ts.map +1 -1
  29. package/dist/worker-server.d.ts.map +1 -1
  30. package/dist/worker.d.ts +10 -3
  31. package/dist/worker.d.ts.map +1 -1
  32. package/package.json +2 -1
  33. package/dist/assets/browser-worker-Bj2ErRvC.js +0 -4
  34. package/dist/assets/browser-worker-Bj2ErRvC.js.map +0 -1
  35. package/dist/assets/shared-worker-CMeTcusa.js +0 -2
  36. package/dist/assets/shared-worker-CMeTcusa.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { At as packedBinaryListBytes, Bt as unpackPackedOptionalValues, Et as isWorkerProtocolRequestMessage, F as utf8Encode, Ft as serializeWorkerError, Gt as isRecord, Ht as workerProtocolError, It as unpackPackedBatchOpKeys, Lt as unpackPackedBatchOps, Mt as packedScanRows, Pt as prepareWorkerResponsePayload, Rt as unpackPackedBinaryList, S as toPublicIndexDefinitions, St as isWorkerProtocolEnvelope, Tt as isWorkerProtocolRequestBatchMessage, Ut as clampTimerDelayMs, Vt as unpackPackedScanRows, _ as indexRangeToPhysicalRange, _t as decodeWorkerCommandPayload, a as createIndexKeyExtractor, b as normalizeIndexDefinitions, bt as isAutocommitCommand, c as decodeIndexedDocument, d as encodeIndexMetadataValue, dt as WORKER_PROTOCOL_READY, f as extractLogicalIndexKey, g as indexKeyExactRange, gt as captureWorkerPayload, ht as WORKER_PROTOCOL_RESPONSE_BATCH, i as compareNormalizedIndexDefinitions, jt as packedOptionalValues, kt as packedBatchOpsBytes, l as encodeIndexEntryKey, m as findIndexDefinition, mt as WORKER_PROTOCOL_RESPONSE, o as decodeIndexEntryKey, p as extractLogicalIndexKeyFromDocument, r as cloneNormalizedIndexDefinitions, s as decodeIndexMetadataValue, t as INDEX_METADATA_STORE, u as encodeIndexMetadataKey, xt as isWorkerCommand, y as isInternalStoreName, zt as unpackPackedBinaryPairKeys } from "./chunks/indexing-B0duqlUs.js";
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 = (response, transfer = []) => {
131
+ enqueue(response, transfer = [], flushNow = false) {
132
132
  if (this.closed) return;
133
- const captured = captureWorkerPayload(response, transfer);
134
- if (this.queued.length > 0 && this.queuedBytes + captured.byteLength > 1048576) this.flush();
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
- if (this.queued.length >= 128 || this.queuedBytes >= 1048576) this.flush();
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
- if (remaining === 0) responses.flush();
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 compressBytes(data, kind) {
846
+ async function collectCompressed(input, kind, stopAtBytes) {
627
847
  ensureCompressionRuntime();
628
848
  let stream;
629
849
  try {
630
- stream = byteStream(data).pipeThrough(new CompressionStream(kind));
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
- return new Uint8Array(await new Response(stream).arrayBuffer());
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 input = copyBytes(value);
702
- return buildStoreRecord(input, await compressBytes(input, compression), compression);
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 compressBytes(input, compression);
740
- return buildEnvelope(SNAPSHOT_EXPORT_MAGIC, compressionTag(compression), input.byteLength, compressed);
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);
@@ -773,6 +1024,7 @@ function toWasmPutOptions(options) {
773
1024
  };
774
1025
  }
775
1026
  function toWasmRange(range) {
1027
+ assertValidScanRange(range);
776
1028
  const normalized = {};
777
1029
  if (range.gt !== void 0) normalized.gt = range.gt;
778
1030
  if (range.gte !== void 0) normalized.gte = range.gte;
@@ -788,6 +1040,7 @@ var MAX_ENGINE_KEY_BYTES = 1024;
788
1040
  var MAX_PACKED_BYTES = 4294967295;
789
1041
  var INDEX_PUT_BATCH_ROWS = 64;
790
1042
  var INDEX_PUT_BATCH_BYTES = 4194304;
1043
+ var MAX_PLAIN_STORE_VALUE_BYTES = 8388608;
791
1044
  var INDEX_SCAN_PAGE_ROWS = 256;
792
1045
  var INDEX_SCAN_PAGE_BYTES = 8389662;
793
1046
  var INDEX_SCAN_RAW_CHUNK_ROWS = 512;
@@ -933,6 +1186,29 @@ function rangesEqual(left, right) {
933
1186
  }
934
1187
  return left.reverse === right.reverse && left.limit === right.limit;
935
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
+ }
936
1212
  function normalizeWasmBytes(value) {
937
1213
  if (value instanceof Uint8Array) return value.byteOffset === 0 && value.byteLength === value.buffer.byteLength ? value : value.slice();
938
1214
  return new Uint8Array(value);
@@ -1090,6 +1366,9 @@ var DbWorker = class {
1090
1366
  */
1091
1367
  txIndexSchemas = /* @__PURE__ */ new Map();
1092
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;
1093
1372
  txEnsuredRawStores = /* @__PURE__ */ new Map();
1094
1373
  txIndexSchemaChanged = /* @__PURE__ */ new Set();
1095
1374
  txStoreCompression = /* @__PURE__ */ new Map();
@@ -1328,9 +1607,14 @@ var DbWorker = class {
1328
1607
  return Promise.resolve(this.withEngine((engine) => engine.has(toWasmU64(txId), store, key)));
1329
1608
  }
1330
1609
  async put(txId, store, key, value, options = {}) {
1331
- const defs = this.indexesForStoreInTx(txId, store);
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);
1332
1615
  if (defs.length === 0) {
1333
- const storedValue = await this.encodeStoreValueForWrite(txId, store, value);
1616
+ const preparedValue = this.prepareStoredValue(store, value, compression);
1617
+ const storedValue = preparedValue instanceof Uint8Array ? preparedValue : await preparedValue;
1334
1618
  const baselineExists = this.withEngine((engine) => engine.put(toWasmU64(txId), store, key, storedValue, toWasmPutOptions(options)));
1335
1619
  this.recordPutChange(txId, store, key, baselineExists);
1336
1620
  return;
@@ -1522,7 +1806,7 @@ var DbWorker = class {
1522
1806
  if (rows !== null) {
1523
1807
  bytes = 4;
1524
1808
  for (const row of rows) {
1525
- row.value = await this.decodeStoreValue(cursor.txId, cursor.store, row.value);
1809
+ row.value = await decodeStoreValueRecord(row.value, { strict: true });
1526
1810
  bytes += 8 + row.key.byteLength + row.value.byteLength;
1527
1811
  if (bytes > request.maxBytes) throw remoteError("ValueTooLargeError", "decoded scan page exceeds maxBytes");
1528
1812
  }
@@ -1560,12 +1844,15 @@ var DbWorker = class {
1560
1844
  return page.rows.length === 0 ? null : page.rows[0].value;
1561
1845
  }
1562
1846
  async scanByIndex(txId, store, indexName, range = {}) {
1847
+ assertValidScanRange(range);
1563
1848
  const def = this.resolveIndexDefinition(txId, store, indexName);
1564
1849
  const rows = [];
1565
1850
  let remaining = range.limit ?? Number.POSITIVE_INFINITY;
1851
+ if (remaining <= 0) return rows;
1852
+ const physical = this.physicalIndexRange(txId, def, range);
1566
1853
  let cursor = null;
1567
1854
  while (remaining > 0) {
1568
- 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);
1569
1856
  rows.push(...page.rows);
1570
1857
  remaining -= page.rows.length;
1571
1858
  if (page.cursor === null) break;
@@ -1577,7 +1864,7 @@ var DbWorker = class {
1577
1864
  if (!Number.isSafeInteger(limit) || limit <= 0) throw remoteError("InvalidRangeError", "index scan page limit must be a positive integer");
1578
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");
1579
1866
  const def = this.resolveIndexDefinition(txId, store, indexName);
1580
- return this.loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes);
1867
+ return this.loadVisibleIndexPage(txId, def, range, cursor, limit, maxBytes, this.physicalIndexRange(txId, def, range));
1581
1868
  }
1582
1869
  getIndexes(txId) {
1583
1870
  return Promise.resolve(toPublicIndexDefinitions(txId === void 0 ? this.loadCommittedIndexSchema() : this.indexSchemaForTx(txId)));
@@ -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());
@@ -2217,22 +2506,20 @@ var DbWorker = class {
2217
2506
  } : op);
2218
2507
  return storedOps;
2219
2508
  }
2220
- async encodeStoreValueForWrite(txId, store, value) {
2221
- if (isInternalStoreName(store)) return value;
2222
- return encodeStoreValueRecord(value, this.storeCompressionForTx(txId, store));
2223
- }
2224
- async decodeStoreValue(txId, store, value) {
2509
+ prepareStoredValue(store, value, compression) {
2225
2510
  if (isInternalStoreName(store)) return value;
2226
- if (this.storeCompressionForTx(txId, store) === false) return value;
2227
- return decodeStoreValueRecord(value, { strict: true });
2511
+ if (compression === false) return limitPlainStoreValue(value);
2512
+ return encodeStoreValueRecord(value, compression);
2228
2513
  }
2229
2514
  async decodeStoreValueForFeed(store, value) {
2230
2515
  if (isInternalStoreName(store)) return value;
2231
2516
  return decodeStoreValueRecord(value, { strict: (this.loadCommittedStoreCompression().get(store) ?? false) !== false });
2232
2517
  }
2233
- async readStoreValue(txId, store, key) {
2518
+ async readStoreValue(txId, store, key, knownCompression) {
2234
2519
  const value = this.readRawValue(txId, store, key);
2235
- return value === null ? null : this.decodeStoreValue(txId, store, value);
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 });
2236
2523
  }
2237
2524
  async readStoreScan(txId, store, range) {
2238
2525
  const rows = this.readRawScan(txId, store, range);
@@ -2242,7 +2529,7 @@ var DbWorker = class {
2242
2529
  for (let index = 0; index < rows.length; index += 1) rows[index].value = values[index];
2243
2530
  return rows;
2244
2531
  }
2245
- async decodeStoreValues(txId, store, values) {
2532
+ async decodeStoreValues(_txId, _store, values) {
2246
2533
  const state = {
2247
2534
  next: 0,
2248
2535
  failed: false,
@@ -2254,7 +2541,7 @@ var DbWorker = class {
2254
2541
  const value = values[index];
2255
2542
  if (value === null) continue;
2256
2543
  try {
2257
- values[index] = await this.decodeStoreValue(txId, store, value);
2544
+ values[index] = await decodeStoreValueRecord(value, { strict: true });
2258
2545
  } catch (error) {
2259
2546
  if (!state.failed) {
2260
2547
  state.failed = true;
@@ -2282,11 +2569,42 @@ var DbWorker = class {
2282
2569
  return byStore.get(store) ?? [];
2283
2570
  }
2284
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
+ }
2285
2583
  if (!this.rawStoreExistsInTx(txId, store)) throw remoteError("StoreNotFoundError", `store not found: ${store}`);
2286
2584
  const def = findIndexDefinition(this.indexesForStoreInTx(txId, store), store, indexName);
2287
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);
2288
2587
  return def;
2289
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
+ }
2290
2608
  indexSchemaForTx(txId) {
2291
2609
  const override = this.txIndexSchemas.get(txId);
2292
2610
  if (override) return override;
@@ -2466,7 +2784,10 @@ var DbWorker = class {
2466
2784
  if (physicalKey.byteLength > MAX_ENGINE_KEY_BYTES) throw remoteError("KeyTooLargeError", `index key too large for ${def.store}.${def.name}`);
2467
2785
  return physicalKey;
2468
2786
  }
2469
- async assertUniqueIndexAvailability(txId, def, logicalKey, primaryKey) {
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) {
2470
2791
  if (!def.unique) return;
2471
2792
  const physical = indexKeyExactRange(logicalKey);
2472
2793
  let cursor = null;
@@ -2491,7 +2812,7 @@ var DbWorker = class {
2491
2812
  continue;
2492
2813
  }
2493
2814
  if (state === "trusted") throw remoteError("UniqueIndexConstraintError", `unique index constraint violation on ${def.store}.${def.name}`);
2494
- const value = await this.readStoreValue(txId, def.store, decoded.primaryKey);
2815
+ const value = await this.readStoreValue(txId, def.store, decoded.primaryKey, knownCompression);
2495
2816
  if (value === null) {
2496
2817
  this.cleanupStaleIndexRow(txId, def, row.key);
2497
2818
  continue;
@@ -2512,10 +2833,11 @@ var DbWorker = class {
2512
2833
  * chunks prevent stale entries from forcing the whole range into memory.
2513
2834
  * The output limit counts only live rows.
2514
2835
  */
2515
- 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) {
2516
2837
  const { limit: _ignored, ...withoutLimit } = range;
2517
- const physical = indexRangeToPhysicalRange(withoutLimit);
2838
+ const physical = physicalRange ?? indexRangeToPhysicalRange(withoutLimit);
2518
2839
  const reverse = physical.reverse === true;
2840
+ const compression = this.storeCompressionForTx(txId, def.store);
2519
2841
  let resumeAfter = cursor;
2520
2842
  const visible = [];
2521
2843
  let outputBytes = 4;
@@ -2554,9 +2876,10 @@ var DbWorker = class {
2554
2876
  batchStart = index;
2555
2877
  batch = this.readIndexPrimaryValues(txId, def, rawRows, index, Math.min(rawRows.length, index + limit - visible.length), maxBytes - outputBytes);
2556
2878
  batchEnd = index + (batch?.entries.length ?? 1);
2879
+ if (batch && compression !== false) this.stabilizeWasmValues(batch.values);
2557
2880
  }
2558
2881
  const row = rawRows[index];
2559
- const resolved = batch ? await this.resolveVisibleIndexedValue(txId, def, row.key, batch.entries[index - batchStart], batch.values[index - batchStart], batch.trusted[index - batchStart]) : await this.resolveVisibleIndexedRow(txId, def, row.key);
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);
2560
2883
  if (batch !== null) batch.values[index - batchStart] = null;
2561
2884
  if (!resolved) {
2562
2885
  resumeAfter = row.key;
@@ -2576,7 +2899,7 @@ var DbWorker = class {
2576
2899
  resumeAfter = row.key;
2577
2900
  if (visible.length >= limit) return {
2578
2901
  rows: visible,
2579
- cursor: row.key.slice()
2902
+ cursor: row.key
2580
2903
  };
2581
2904
  }
2582
2905
  if (rawRows.length < chunkLimit) return {
@@ -2649,7 +2972,13 @@ var DbWorker = class {
2649
2972
  trusted
2650
2973
  };
2651
2974
  }
2652
- async resolveVisibleIndexedRow(txId, def, physicalKey) {
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) {
2653
2982
  const decoded = decodeIndexEntryKey(physicalKey);
2654
2983
  const state = this.inspectIndexedValue(txId, def, physicalKey, decoded.primaryKey);
2655
2984
  if (state === "missing") {
@@ -2657,14 +2986,18 @@ var DbWorker = class {
2657
2986
  return null;
2658
2987
  }
2659
2988
  const value = this.readRawValue(txId, def.store, decoded.primaryKey);
2660
- return this.resolveVisibleIndexedValue(txId, def, physicalKey, decoded, value, state === "trusted");
2989
+ return this.resolveVisibleIndexedValue(txId, def, physicalKey, decoded, value, state === "trusted", compression, true);
2661
2990
  }
2662
- async resolveVisibleIndexedValue(txId, def, physicalKey, decoded, rawValue, trusted = false) {
2991
+ resolveVisibleIndexedValue(txId, def, physicalKey, decoded, rawValue, trusted = false, compression, stable) {
2663
2992
  if (rawValue === null) {
2664
2993
  this.cleanupStaleIndexRow(txId, def, physicalKey);
2665
2994
  return null;
2666
2995
  }
2667
- const value = await this.decodeStoreValue(txId, def.store, normalizeWasmBytes(rawValue));
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) {
2668
3001
  if (!trusted) {
2669
3002
  const currentLogicalKey = extractLogicalIndexKey(def, value);
2670
3003
  if (currentLogicalKey === null || !bytesEqual(currentLogicalKey, decoded.logicalKey)) {
@@ -2718,6 +3051,8 @@ var DbWorker = class {
2718
3051
  this.txChanges.clear();
2719
3052
  this.txModes.clear();
2720
3053
  this.txIndexSchemas.clear();
3054
+ this.readonlyIndexCache.clear();
3055
+ this.indexBoundCache = null;
2721
3056
  this.txEnsuredRawStores.clear();
2722
3057
  this.txIndexSchemaChanged.clear();
2723
3058
  this.txStoreCompression.clear();