@cloudbitmaps/core 0.18.1 → 0.18.3

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/dist/index.js CHANGED
@@ -70,7 +70,7 @@ import {
70
70
  validateUserNamespace,
71
71
  validateUserRef,
72
72
  webCryptoEntropy
73
- } from "./chunk-K4EXZDMU.js";
73
+ } from "./chunk-ML6AT53O.js";
74
74
 
75
75
  // src/core/bit-route.ts
76
76
  var U32_MAX = 4294967295;
@@ -78,7 +78,7 @@ var CHUNK_COUNT = 65536;
78
78
  var LOW_16 = 65535;
79
79
  function splitId(id) {
80
80
  if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
81
- throw new ValidationError(`id must be an integer in 0..${U32_MAX}; got ${id}`);
81
+ throw new ValidationError(`an id must be an integer from 0 to ${U32_MAX}`);
82
82
  }
83
83
  return { chunkKey: id >>> 16, remainder: id & LOW_16 };
84
84
  }
@@ -428,8 +428,7 @@ function windowOf(range) {
428
428
  ["through", through]
429
429
  ]) {
430
430
  if (bound !== void 0 && (!Number.isInteger(bound) || bound < 0 || bound > U32_MAX)) {
431
- const got = typeof bound === "number" ? String(bound) : `a ${typeof bound}`;
432
- throw new ValidationError(`${name} must be an integer in 0..${U32_MAX}; got ${got}`);
431
+ throw new ValidationError(`${name} must be an integer from 0 to ${U32_MAX}`);
433
432
  }
434
433
  }
435
434
  const lo = after === void 0 ? 0 : after + 1;
@@ -1531,10 +1530,16 @@ async function aboveEverything(ref, deps, record) {
1531
1530
  for await (const key of deps.storage.list(ref)) {
1532
1531
  if (key.generation > highest) highest = key.generation;
1533
1532
  }
1533
+ return numberAbove(highest);
1534
+ }
1535
+ function numberAbove(highest) {
1536
+ if (highest >= Number.MAX_SAFE_INTEGER) {
1537
+ throw new IntegrityError("the segment has no generation number left above its highest");
1538
+ }
1534
1539
  return highest + 1;
1535
1540
  }
1536
1541
  async function nextLoadGeneration(ref, deps, row) {
1537
- const generation = (row?.currentGen ?? -1) + 1;
1542
+ const generation = numberAbove(row?.currentGen ?? -1);
1538
1543
  try {
1539
1544
  await deps.storage.getTail({ namespace: ref.namespace, segment: ref.segment, generation }, 0);
1540
1545
  } catch (err) {
@@ -2289,7 +2294,7 @@ async function* listSegments(registry, options = {}) {
2289
2294
  }
2290
2295
 
2291
2296
  // src/drivers/backends.ts
2292
- import { join as join3 } from "node:path";
2297
+ import { join as join4 } from "node:path";
2293
2298
 
2294
2299
  // src/drivers/memory.ts
2295
2300
  import { createHash } from "node:crypto";
@@ -2467,11 +2472,30 @@ function genObjectKey(key) {
2467
2472
  // src/drivers/localfs/storage.ts
2468
2473
  import { constants as FS2 } from "node:fs";
2469
2474
  import { createHash as createHash2, randomUUID } from "node:crypto";
2470
- import { link, mkdir, open as open2, readdir, unlink } from "node:fs/promises";
2471
- import { dirname } from "node:path";
2475
+ import { link, mkdir, open as open2, readdir as readdir2, unlink } from "node:fs/promises";
2476
+ import { dirname as dirname2 } from "node:path";
2472
2477
 
2473
2478
  // src/drivers/localfs/paths.ts
2474
2479
  import { join } from "node:path";
2480
+ var MAX_COMPONENT_BYTES = 255;
2481
+ var TEMP_SUFFIX_BYTES = 1 + 36 + 4;
2482
+ function assertFits(component, extraBytes) {
2483
+ if (component.length + extraBytes > MAX_COMPONENT_BYTES) {
2484
+ throw new ValidationError(
2485
+ `a name is too long for a file name on this backend: the namespace, or the segment with its generation and the temp file's suffix, must fit in ${MAX_COMPONENT_BYTES} bytes once encoded`
2486
+ );
2487
+ }
2488
+ }
2489
+ function assertStorageNamesFit(key) {
2490
+ validateSegmentRef(key);
2491
+ assertFits(namespacePathPart(key.namespace), 0);
2492
+ assertFits(storageObjectFilename(key.segment, key.generation), TEMP_SUFFIX_BYTES);
2493
+ }
2494
+ function assertRegistryNamesFit(ref) {
2495
+ validateSegmentRef(ref);
2496
+ assertFits(namespacePathPart(ref.namespace), 0);
2497
+ assertFits(`${encodeNameForPath(ref.segment)}${REGISTRY_SUFFIX}`, TEMP_SUFFIX_BYTES);
2498
+ }
2475
2499
  function segmentsDir(root, ref) {
2476
2500
  validateSegmentRef(ref);
2477
2501
  return join(root, namespacePathPart(ref.namespace), "segments");
@@ -2531,10 +2555,15 @@ function parseNamespaceDir(entry) {
2531
2555
  return { namespace };
2532
2556
  }
2533
2557
 
2558
+ // src/drivers/localfs/exact-case.ts
2559
+ import { readdir, realpath, stat } from "node:fs/promises";
2560
+ import { dirname, join as join2, parse, relative, resolve, sep } from "node:path";
2561
+
2534
2562
  // src/drivers/localfs/fs-util.ts
2535
2563
  import { constants as FS } from "node:fs";
2536
2564
  import { open } from "node:fs/promises";
2537
2565
  var isCode = (err, code) => err instanceof Error && err.code === code;
2566
+ var isAbsent = (err) => isCode(err, "ENOENT") || isCode(err, "ENOTDIR") || isCode(err, "ENAMETOOLONG");
2538
2567
  var TRANSIENT_FS_CODES = /* @__PURE__ */ new Set(["EBUSY", "EAGAIN", "EMFILE", "ENFILE", "ETIMEDOUT"]);
2539
2568
  var isFsTransient = (err) => err instanceof Error && TRANSIENT_FS_CODES.has(err.code ?? "");
2540
2569
  var mapFsError = (err) => isFsTransient(err) ? new TransientError(`transient filesystem fault: ${err.code}`, { cause: err }) : err;
@@ -2551,18 +2580,138 @@ async function fsyncDir(dir) {
2551
2580
  }
2552
2581
  }
2553
2582
 
2583
+ // src/drivers/localfs/exact-case.ts
2584
+ function firstCaseDifference(expected, actual) {
2585
+ const shared = Math.min(expected.length, actual.length);
2586
+ for (let i = 0; i < shared; i++) {
2587
+ const want = expected[i];
2588
+ const have = actual[i];
2589
+ if (want !== have) return want.toLowerCase() === have.toLowerCase() ? i : -1;
2590
+ }
2591
+ return -1;
2592
+ }
2593
+ var swapCase = (s) => [...s].map((c) => c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase()).join("");
2594
+ var components = (path) => path.split(sep).filter((c) => c !== "");
2595
+ function caseVariantError() {
2596
+ return new ValidationError(
2597
+ "names differ only by case on a case-insensitive filesystem: a name that differs only by case from one already stored is the same file here, so it is refused rather than written over"
2598
+ );
2599
+ }
2600
+ var ExactCase = class {
2601
+ constructor(root) {
2602
+ this.root = root;
2603
+ }
2604
+ root;
2605
+ /** `undefined` until the root holds an entry to try; then fixed, as a filesystem does not change its mind. */
2606
+ insensitive;
2607
+ /**
2608
+ * Whether the root's filesystem folds case, learned from an entry already in the root: the entry's name with its
2609
+ * case swapped is the same file or it is not. An empty or absent root holds nothing to alias, so it is not
2610
+ * insensitive yet, and is looked at again on the next call.
2611
+ */
2612
+ async foldsCase() {
2613
+ if (this.insensitive !== void 0) return this.insensitive;
2614
+ let names;
2615
+ try {
2616
+ names = await readdir(this.root);
2617
+ } catch (err) {
2618
+ if (isAbsent(err)) return this.foldsAtAncestor();
2619
+ throw err;
2620
+ }
2621
+ for (const name of names) {
2622
+ const swapped = swapCase(name);
2623
+ if (swapped === name) continue;
2624
+ const own = await stat(join2(this.root, name), { bigint: true }).catch(() => void 0);
2625
+ if (own === void 0) continue;
2626
+ const other = await stat(join2(this.root, swapped), { bigint: true }).catch(() => void 0);
2627
+ this.insensitive = other !== void 0 && other.ino === own.ino && other.dev === own.dev;
2628
+ return this.insensitive;
2629
+ }
2630
+ return this.foldsAtAncestor();
2631
+ }
2632
+ /**
2633
+ * The same question asked of the root's own name, or the nearest existing directory above it, when the root holds
2634
+ * nothing to try: a root with no entries has none to alias yet, but two names created at once still would. Not
2635
+ * remembered, since it is the volume above the root that answers.
2636
+ */
2637
+ async foldsAtAncestor() {
2638
+ let dir = resolve(this.root);
2639
+ for (; ; ) {
2640
+ const top = parse(dir).root;
2641
+ const parts = components(relative(top, dir));
2642
+ let at = parts.length - 1;
2643
+ while (at >= 0 && swapCase(parts[at]) === parts[at]) at--;
2644
+ if (at === -1) return false;
2645
+ const swapped = [...parts];
2646
+ swapped[at] = swapCase(parts[at]);
2647
+ let own;
2648
+ try {
2649
+ own = await stat(dir, { bigint: true });
2650
+ } catch (err) {
2651
+ if (!isAbsent(err)) return false;
2652
+ const parent = dirname(dir);
2653
+ if (parent === dir) return false;
2654
+ dir = parent;
2655
+ continue;
2656
+ }
2657
+ const other = await stat(join2(top, ...swapped), { bigint: true }).catch(() => void 0);
2658
+ return other !== void 0 && other.ino === own.ino && other.dev === own.dev;
2659
+ }
2660
+ }
2661
+ /** Whether the root's filesystem folds case: names that differ only by case are one file there. */
2662
+ folds() {
2663
+ return this.foldsCase();
2664
+ }
2665
+ /**
2666
+ * `true` when `path` (inside the root) is, on disk, a path whose name differs only by case from the one given:
2667
+ * the deepest part of it that exists is resolved to its real name and compared exactly. A path that does not exist
2668
+ * yet, or that nothing differs from, is `false`.
2669
+ */
2670
+ async differs(path) {
2671
+ if (!await this.foldsCase()) return false;
2672
+ const rootAbs = resolve(this.root);
2673
+ let probe = resolve(path);
2674
+ let real;
2675
+ for (; ; ) {
2676
+ if (probe === rootAbs || !relative(rootAbs, probe) || relative(rootAbs, probe).startsWith("..")) {
2677
+ return false;
2678
+ }
2679
+ try {
2680
+ real = await realpath(probe);
2681
+ break;
2682
+ } catch (err) {
2683
+ if (!isAbsent(err)) throw err;
2684
+ probe = dirname(probe);
2685
+ }
2686
+ }
2687
+ const realRoot = await realpath(rootAbs).catch(() => void 0);
2688
+ if (realRoot === void 0) return false;
2689
+ const actual = relative(realRoot, real);
2690
+ if (actual.startsWith("..")) return false;
2691
+ return firstCaseDifference(components(relative(rootAbs, probe)), components(actual)) !== -1;
2692
+ }
2693
+ /** Refuse a write to `path` when its real name differs from it only by case. */
2694
+ async refuseVariant(path) {
2695
+ if (await this.differs(path)) throw caseVariantError();
2696
+ }
2697
+ };
2698
+
2554
2699
  // src/drivers/localfs/storage.ts
2555
2700
  var LocalFsStorageDriver = class {
2556
2701
  constructor(root) {
2557
2702
  this.root = root;
2703
+ this.exactCase = new ExactCase(root);
2558
2704
  }
2559
2705
  root;
2706
+ exactCase;
2560
2707
  capabilities() {
2561
2708
  return { rangeRead: true, maxObjectBytes: Number.MAX_SAFE_INTEGER, conditionalPut: false };
2562
2709
  }
2563
2710
  async putImmutable(key, write) {
2564
2711
  const finalPath = storageObjectPath(this.root, key);
2565
- await mkdir(dirname(finalPath), { recursive: true });
2712
+ assertStorageNamesFit(key);
2713
+ await this.exactCase.refuseVariant(finalPath);
2714
+ await mkdir(dirname2(finalPath), { recursive: true });
2566
2715
  const tmpPath = `${finalPath}.${randomUUID()}.tmp`;
2567
2716
  const handle = await open2(tmpPath, FS2.O_CREAT | FS2.O_EXCL | FS2.O_WRONLY | O_NOFOLLOW);
2568
2717
  const hash = createHash2("sha256");
@@ -2599,7 +2748,7 @@ var LocalFsStorageDriver = class {
2599
2748
  }
2600
2749
  await unlink(tmpPath).catch(() => {
2601
2750
  });
2602
- await fsyncDir(dirname(finalPath));
2751
+ await fsyncDir(dirname2(finalPath));
2603
2752
  return { size, sha256: hash.digest("hex") };
2604
2753
  }
2605
2754
  async getRange(key, offset, length) {
@@ -2634,17 +2783,20 @@ var LocalFsStorageDriver = class {
2634
2783
  }
2635
2784
  }
2636
2785
  async delete(key) {
2637
- await unlink(storageObjectPath(this.root, key)).catch((err) => {
2638
- if (!isCode(err, "ENOENT")) throw mapFsError(err);
2786
+ const path = storageObjectPath(this.root, key);
2787
+ if (await this.exactCase.differs(path)) return;
2788
+ await unlink(path).catch((err) => {
2789
+ if (!isAbsent(err)) throw mapFsError(err);
2639
2790
  });
2640
2791
  }
2641
2792
  async *list(ref) {
2642
2793
  const dir = segmentsDir(this.root, ref);
2794
+ if (await this.exactCase.differs(dir)) return;
2643
2795
  let names;
2644
2796
  try {
2645
- names = await readdir(dir);
2797
+ names = await readdir2(dir);
2646
2798
  } catch (err) {
2647
- if (isCode(err, "ENOENT")) return;
2799
+ if (isAbsent(err)) return;
2648
2800
  throw mapFsError(err);
2649
2801
  }
2650
2802
  for (const name of names) {
@@ -2655,10 +2807,14 @@ var LocalFsStorageDriver = class {
2655
2807
  }
2656
2808
  }
2657
2809
  async openRead(key) {
2810
+ const path = storageObjectPath(this.root, key);
2811
+ if (await this.exactCase.differs(path)) {
2812
+ throw new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);
2813
+ }
2658
2814
  try {
2659
- return await open2(storageObjectPath(this.root, key), FS2.O_RDONLY | O_NOFOLLOW);
2815
+ return await open2(path, FS2.O_RDONLY | O_NOFOLLOW);
2660
2816
  } catch (err) {
2661
- if (isCode(err, "ENOENT")) {
2817
+ if (isAbsent(err)) {
2662
2818
  throw new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);
2663
2819
  }
2664
2820
  if (isCode(err, "ELOOP")) {
@@ -2674,20 +2830,20 @@ var LocalFsStorageDriver = class {
2674
2830
  // src/drivers/localfs/registry.ts
2675
2831
  import { constants as FS3 } from "node:fs";
2676
2832
  import { randomUUID as randomUUID2 } from "node:crypto";
2677
- import { mkdir as mkdir2, open as open3, readdir as readdir2, realpath, rename, unlink as unlink2 } from "node:fs/promises";
2678
- import { basename, dirname as dirname2, join as join2, resolve } from "node:path";
2833
+ import { mkdir as mkdir2, open as open3, readdir as readdir3, realpath as realpath2, rename, unlink as unlink2 } from "node:fs/promises";
2834
+ import { basename, dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
2679
2835
  var DEFAULT_MAX_ROW_BYTES = 1 * 1024 * 1024;
2680
2836
  var rowChains = /* @__PURE__ */ new Map();
2681
2837
  async function rowLockKey(path) {
2682
- const abs = resolve(path);
2838
+ const abs = resolve2(path);
2683
2839
  const tail = [basename(abs)];
2684
- let dir = dirname2(abs);
2840
+ let dir = dirname3(abs);
2685
2841
  for (; ; ) {
2686
2842
  try {
2687
- return join2(await realpath(dir), ...tail);
2843
+ return join3(await realpath2(dir), ...tail);
2688
2844
  } catch (err) {
2689
- const parent = dirname2(dir);
2690
- if (!isCode(err, "ENOENT") || parent === dir) throw mapFsError(err);
2845
+ const parent = dirname3(dir);
2846
+ if (!isAbsent(err) || parent === dir) throw mapFsError(err);
2691
2847
  tail.unshift(basename(dir));
2692
2848
  dir = parent;
2693
2849
  }
@@ -2696,12 +2852,14 @@ async function rowLockKey(path) {
2696
2852
  var LocalFsRegistryDriver = class {
2697
2853
  constructor(root, options = {}) {
2698
2854
  this.root = root;
2855
+ this.exactCase = new ExactCase(root);
2699
2856
  this.now = options.now ?? (() => Date.now());
2700
2857
  this.entropy = options.entropy ?? webCryptoEntropy;
2701
2858
  }
2702
2859
  root;
2703
2860
  now;
2704
2861
  entropy;
2862
+ exactCase;
2705
2863
  /**
2706
2864
  * `conditionalDelete`: a delete reads the row, checks it, and unlinks it under the row's lock, which every writer on
2707
2865
  * the root in this process takes, so no write can land between the check and the unlink.
@@ -2716,6 +2874,7 @@ var LocalFsRegistryDriver = class {
2716
2874
  async create(ref, record) {
2717
2875
  const checked = validateNewRegistryRecord(record);
2718
2876
  const path = registryRowPath(this.root, ref);
2877
+ assertRegistryNamesFit(ref);
2719
2878
  return this.withRowLock(path, async () => {
2720
2879
  const current = await this.readRow(path);
2721
2880
  if (current !== null && !current.deleted) {
@@ -2733,6 +2892,7 @@ var LocalFsRegistryDriver = class {
2733
2892
  async compareAndSwap(ref, expected, patch) {
2734
2893
  const checked = validateRegistryPatch(patch);
2735
2894
  const path = registryRowPath(this.root, ref);
2895
+ assertRegistryNamesFit(ref);
2736
2896
  return this.withRowLock(path, async () => {
2737
2897
  const current = await this.readRow(path);
2738
2898
  if (current === null || current.deleted || current.record.token !== expected) {
@@ -2750,11 +2910,12 @@ var LocalFsRegistryDriver = class {
2750
2910
  async *list(namespace) {
2751
2911
  for (const ns of await this.namespaceDirs(namespace)) {
2752
2912
  const dir = registryDir(this.root, ns);
2913
+ if (await this.exactCase.differs(dir)) continue;
2753
2914
  let names;
2754
2915
  try {
2755
- names = await readdir2(dir);
2916
+ names = await readdir3(dir);
2756
2917
  } catch (err) {
2757
- if (isCode(err, "ENOENT")) continue;
2918
+ if (isAbsent(err)) continue;
2758
2919
  throw mapFsError(err);
2759
2920
  }
2760
2921
  for (const name of names) {
@@ -2791,14 +2952,14 @@ var LocalFsRegistryDriver = class {
2791
2952
  } catch (err) {
2792
2953
  if (!isCode(err, "ENOENT")) throw mapFsError(err);
2793
2954
  }
2794
- await fsyncDir(dirname2(path));
2955
+ await fsyncDir(dirname3(path));
2795
2956
  }
2796
2957
  /** Namespaces to scan: just the one requested, or every namespace dir under the root. */
2797
2958
  async namespaceDirs(namespace) {
2798
2959
  if (namespace !== void 0) return [namespace];
2799
2960
  let entries;
2800
2961
  try {
2801
- entries = await readdir2(this.root);
2962
+ entries = await readdir3(this.root);
2802
2963
  } catch (err) {
2803
2964
  if (isCode(err, "ENOENT")) return [];
2804
2965
  throw mapFsError(err);
@@ -2813,9 +2974,10 @@ var LocalFsRegistryDriver = class {
2813
2974
  async readRow(path) {
2814
2975
  let handle;
2815
2976
  try {
2977
+ if (await this.exactCase.differs(path)) return null;
2816
2978
  handle = await open3(path, FS3.O_RDONLY | O_NOFOLLOW);
2817
2979
  } catch (err) {
2818
- if (isCode(err, "ENOENT") || isCode(err, "ELOOP")) return null;
2980
+ if (isAbsent(err) || isCode(err, "ELOOP")) return null;
2819
2981
  throw mapFsError(err);
2820
2982
  }
2821
2983
  try {
@@ -2830,7 +2992,8 @@ var LocalFsRegistryDriver = class {
2830
2992
  }
2831
2993
  }
2832
2994
  async writeRow(path, deleted, record) {
2833
- await mkdir2(dirname2(path), { recursive: true });
2995
+ await this.exactCase.refuseVariant(path);
2996
+ await mkdir2(dirname3(path), { recursive: true });
2834
2997
  const out = Buffer.from(serializeRegistryEnvelope({ deleted, record }), "utf8");
2835
2998
  if (out.length > DEFAULT_MAX_ROW_BYTES) {
2836
2999
  throw new ValidationError(
@@ -2855,11 +3018,12 @@ var LocalFsRegistryDriver = class {
2855
3018
  });
2856
3019
  throw mapFsError(err);
2857
3020
  });
2858
- await fsyncDir(dirname2(path));
3021
+ await fsyncDir(dirname3(path));
2859
3022
  }
2860
3023
  /** Serialize callbacks for a row so read-modify-write is atomic across every instance in the process. */
2861
3024
  async withRowLock(path, fn) {
2862
- const key = await rowLockKey(path);
3025
+ const resolved = await rowLockKey(path);
3026
+ const key = await this.exactCase.folds() ? resolved.toLowerCase() : resolved;
2863
3027
  const prev = rowChains.get(key) ?? Promise.resolve();
2864
3028
  const result = prev.then(fn, fn);
2865
3029
  const tail = result.then(
@@ -2887,9 +3051,9 @@ var MemoryStorage = class {
2887
3051
  var LocalFsStorage = class {
2888
3052
  constructor(root, options = {}) {
2889
3053
  this.root = root;
2890
- this.storage = new LocalFsStorageDriver(join3(root, "storage"));
3054
+ this.storage = new LocalFsStorageDriver(join4(root, "storage"));
2891
3055
  this.registry = new LocalFsRegistryDriver(
2892
- join3(root, "registry"),
3056
+ join4(root, "registry"),
2893
3057
  options.now === void 0 ? {} : { now: options.now }
2894
3058
  );
2895
3059
  brandAsBackend(this);
@@ -4028,9 +4192,7 @@ function overCap(what, length) {
4028
4192
  }
4029
4193
  function prepareLoadInput(input, codec, subject = "a load") {
4030
4194
  if (typeof input !== "object" || input === null) {
4031
- throw new ValidationError(
4032
- `${subject} takes ids, { serialized } or { bitmap }; got ${String(input)}`
4033
- );
4195
+ throw new ValidationError(`${subject} takes ids, { serialized } or { bitmap }`);
4034
4196
  }
4035
4197
  const tag = tagOf(input);
4036
4198
  if (tag === "Uint8Array" || tag === "Uint8ClampedArray") {
@@ -5736,7 +5898,7 @@ function* rangeCheckedAtWrite(chunks) {
5736
5898
  const max = chunk.bitmap.maximum?.();
5737
5899
  if (max !== void 0 && max > MAX_REMAINDER) {
5738
5900
  throw new ValidationError(
5739
- `chunk ${chunk.chunkKey} holds ${max}, outside the 16-bit range [0, ${MAX_REMAINDER}]`
5901
+ `a chunk holds a value outside the 16-bit range [0, ${MAX_REMAINDER}]`
5740
5902
  );
5741
5903
  }
5742
5904
  yield chunk;
@@ -5748,18 +5910,14 @@ async function collectChunks(input, codec, clock) {
5748
5910
  let last = -1;
5749
5911
  for await (const chunk of input.chunks) {
5750
5912
  if (typeof chunk !== "object" || chunk === null) {
5751
- throw new ValidationError(
5752
- `a chunk must be an object with a chunkKey and a bitmap; got ${String(chunk)}`
5753
- );
5913
+ throw new ValidationError("a chunk must be an object with a chunkKey and a bitmap");
5754
5914
  }
5755
5915
  const { chunkKey, bitmap } = chunk;
5756
5916
  if (codec.owns?.(bitmap) !== true) {
5757
- throw new ValidationError(
5758
- `chunk ${String(chunkKey)}: its bitmap is not one this load's codec made`
5759
- );
5917
+ throw new ValidationError(`a chunk's bitmap is not one this load's codec made`);
5760
5918
  }
5761
5919
  if (!Number.isInteger(chunkKey) || chunkKey < 0 || chunkKey > MAX_REMAINDER) {
5762
- throw new ValidationError(`a chunk's key must be an integer in [0, 65535]; got ${chunkKey}`);
5920
+ throw new ValidationError("a chunk's key must be an integer in [0, 65535]");
5763
5921
  }
5764
5922
  if (chunkKey <= last) {
5765
5923
  throw new ValidationError(`chunk ${chunkKey} does not ascend: the last was ${last}`);
@@ -5768,7 +5926,7 @@ async function collectChunks(input, codec, clock) {
5768
5926
  const max = bitmap.maximum?.();
5769
5927
  if (max !== void 0 && max > MAX_REMAINDER) {
5770
5928
  throw new ValidationError(
5771
- `chunk ${chunkKey} holds ${max}, outside the 16-bit range [0, ${MAX_REMAINDER}]`
5929
+ `a chunk holds a value outside the 16-bit range [0, ${MAX_REMAINDER}]`
5772
5930
  );
5773
5931
  }
5774
5932
  chunks.push({ chunkKey, bitmap });
@@ -6792,11 +6950,7 @@ var FeedCursor = class {
6792
6950
  const key = record.key;
6793
6951
  const operands = record.operands;
6794
6952
  if (typeof key !== "number" || !Number.isInteger(key) || key < 0 || key >= KEY_IDS) {
6795
- this.refuse(
6796
- typeof key === "number" ? key : "?",
6797
- void 0,
6798
- "the key must be an integer from 0 to 65535"
6799
- );
6953
+ this.refuse("?", void 0, "the key must be an integer from 0 to 65535");
6800
6954
  }
6801
6955
  if (key < this.prevKey) {
6802
6956
  this.refuse(
@@ -7031,7 +7185,9 @@ function compileCombineMany(request) {
7031
7185
  ["after", request.after],
7032
7186
  ["through", request.through]
7033
7187
  ]) {
7034
- if (bound !== void 0) integerIn(bound, 0, U32_MAX, name);
7188
+ if (bound !== void 0 && (!Number.isInteger(bound) || bound < 0 || bound > U32_MAX)) {
7189
+ throw new ValidationError(`${name} must be an integer from 0 to ${U32_MAX}`);
7190
+ }
7035
7191
  }
7036
7192
  const index = /* @__PURE__ */ new Map();
7037
7193
  operands.forEach((op, i) => index.set(op.name, i));
@@ -7170,7 +7326,7 @@ var Gate = class {
7170
7326
  return this.peak;
7171
7327
  }
7172
7328
  async run(task) {
7173
- if (this.active >= this.limit) await new Promise((resolve2) => this.waiting.push(resolve2));
7329
+ if (this.active >= this.limit) await new Promise((resolve3) => this.waiting.push(resolve3));
7174
7330
  else this.active++;
7175
7331
  if (this.active > this.peak) this.peak = this.active;
7176
7332
  try {
@@ -8097,7 +8253,7 @@ var Run = class {
8097
8253
  const wake = () => {
8098
8254
  const now = waiters;
8099
8255
  waiters = [];
8100
- for (const resolve2 of now) resolve2();
8256
+ for (const resolve3 of now) resolve3();
8101
8257
  };
8102
8258
  const worker = async () => {
8103
8259
  for (; ; ) {
@@ -8116,7 +8272,7 @@ var Run = class {
8116
8272
  const transient = o.serialized;
8117
8273
  let admitted = this.ledger.tryCharge(transient);
8118
8274
  while (!admitted && inFlight > 0) {
8119
- await new Promise((resolve2) => waiters.push(resolve2));
8275
+ await new Promise((resolve3) => waiters.push(resolve3));
8120
8276
  admitted = this.ledger.tryCharge(transient);
8121
8277
  }
8122
8278
  if (!admitted) {
@@ -8414,7 +8570,7 @@ var HeldChunks = class {
8414
8570
  const { chunkKey, payload, cardinality } = chunk;
8415
8571
  if (!Number.isInteger(chunkKey) || chunkKey < 0 || chunkKey > MAX_REMAINDER || chunkKey <= last) {
8416
8572
  throw new ValidationError(
8417
- `a held chunk's key must be an integer in [0, 65535] above the last; got ${String(chunkKey)}`
8573
+ `a held chunk's key must be an integer in [0, 65535] above the last`
8418
8574
  );
8419
8575
  }
8420
8576
  assertChunkCardinalityInRange(cardinality);
@@ -9008,11 +9164,11 @@ var InProcessKeystore = class {
9008
9164
  if (k.length !== KEY_BYTES)
9009
9165
  throw new ValidationError(`KEK "${id}" must be ${KEY_BYTES} bytes, got ${k.length}`);
9010
9166
  }
9011
- if (!(options.activeKeyId in options.keys))
9012
- throw new ValidationError(`activeKeyId "${options.activeKeyId}" is not in keys`);
9013
- if (options.recoveryKeyId !== void 0 && !(options.recoveryKeyId in options.keys))
9014
- throw new ValidationError(`recoveryKeyId "${options.recoveryKeyId}" is not in keys`);
9015
9167
  this.keys = new Map(entries.map(([id, k]) => [id, Buffer.from(k)]));
9168
+ if (!this.keys.has(options.activeKeyId))
9169
+ throw new ValidationError(`activeKeyId "${String(options.activeKeyId)}" is not in keys`);
9170
+ if (options.recoveryKeyId !== void 0 && !this.keys.has(options.recoveryKeyId))
9171
+ throw new ValidationError(`recoveryKeyId "${String(options.recoveryKeyId)}" is not in keys`);
9016
9172
  this.activeKeyId = options.activeKeyId;
9017
9173
  this.recoveryKeyId = options.recoveryKeyId === options.activeKeyId ? void 0 : options.recoveryKeyId;
9018
9174
  }