@cloudbitmaps/core 0.23.0 → 0.25.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.
@@ -19,7 +19,7 @@ import {
19
19
  requirePositiveInteger,
20
20
  shown,
21
21
  tailBytesFit
22
- } from "./chunk-MVUD4R72.js";
22
+ } from "./chunk-2B2QF4FU.js";
23
23
 
24
24
  // src/drivers/_shared/options.ts
25
25
  function checkNow(name, now) {
@@ -638,4 +638,4 @@ export {
638
638
  namespaceKeyPart,
639
639
  namespacePathPart
640
640
  };
641
- //# sourceMappingURL=chunk-7GT632UF.js.map
641
+ //# sourceMappingURL=chunk-45ML6YU4.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  requirePositiveInteger
3
- } from "./chunk-MVUD4R72.js";
3
+ } from "./chunk-2B2QF4FU.js";
4
4
 
5
5
  // src/core/concurrency.ts
6
6
  async function mapWithConcurrency(items, limit, fn) {
@@ -70,4 +70,4 @@ export {
70
70
  mapWithConcurrency,
71
71
  boundedAll
72
72
  };
73
- //# sourceMappingURL=chunk-CGILDXGP.js.map
73
+ //# sourceMappingURL=chunk-BUDAVGBI.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ValidationError,
3
3
  encodeNameForKey
4
- } from "./chunk-MVUD4R72.js";
4
+ } from "./chunk-2B2QF4FU.js";
5
5
 
6
6
  // src/core/keys.ts
7
7
  var FIELD = " ";
@@ -118,4 +118,4 @@ export {
118
118
  BufferSink,
119
119
  BufferReader
120
120
  };
121
- //# sourceMappingURL=chunk-GKHERVVQ.js.map
121
+ //# sourceMappingURL=chunk-WPER7R2R.js.map
@@ -33,7 +33,8 @@ export declare function requireFiniteNumber(value: unknown, bounds: {
33
33
  /**
34
34
  * Refuse an options bag that is not an object, or that holds a key not in `keys`, naming each such key and the keys it
35
35
  * takes. `hint`, when given, ends the message, to point at where a likely mistake belongs (`an S3 client goes in
36
- * \`client\``). Only the bag's own enumerable string keys are read.
36
+ * \`client\``). Only the bag's own enumerable string keys are read, so a key inherited through a prototype is not
37
+ * checked.
37
38
  */
38
39
  export declare function refuseUnknownOptions(name: string, options: unknown, keys: readonly string[], hint?: string): void;
39
40
  /**
@@ -16,9 +16,9 @@
16
16
  * different codecs, or a cached bitmap from codec A could reach codec B's in-place op. The `CloudRoaring` facade
17
17
  * mints the cache per store, so this cannot arise in normal use.)
18
18
  *
19
- * **Interface surface = exactly what the engine needs**, kept general enough for the known fast-follow codecs
20
- * (positional/rank-select access + raw-bitset interop that `@cloudbitmaps/bitset` will add live on that
21
- * package's own extended value type, not here — the engine never calls them). The one codec-linked concern
19
+ * **Interface surface = exactly what the engine needs**, and nothing a codec might one day offer: an operation the
20
+ * engine never calls (positional or rank/select access, say) is not on {@link CodecBitmap}, and a second codec that
21
+ * has such operations would carry them on its own value type. The one codec-linked concern
22
22
  * this seam intentionally leaves in the format layer is the `.crbm` **payload codec id**
23
23
  * (`PAYLOAD_CODEC_ROARING_PORTABLE` / `KNOWN_PAYLOAD_CODEC_IDS` in `crbm/format.ts`): it is stamped in the footer
24
24
  * and validated on read, as membership of a registry rather than equality with one constant, so a codec-specific
@@ -54,7 +54,7 @@ export interface CodecBitmap {
54
54
  toArray(): number[];
55
55
  /**
56
56
  * The set's values as one ascending `Uint32Array` the caller owns: what a batch read converts a chunk with, in one
57
- * native step instead of one iterator step per value.
57
+ * step instead of one iterator step per value.
58
58
  *
59
59
  * **Optional.** A codec without it is read through its iterator, which is correct and slower.
60
60
  *
@@ -125,7 +125,7 @@ export interface EncodedChunk {
125
125
  }
126
126
  /**
127
127
  * A pluggable bitmap codec — the factory the codec-agnostic engine constructs {@link CodecBitmap}s through.
128
- * Implementations **must** size-cap before handing untrusted bytes to any native decoder and use a safe
128
+ * Implementations **must** size-cap before handing untrusted bytes to a decoder and use a safe
129
129
  * (never a trusting/frozen) deserializer — hard invariant 5.
130
130
  */
131
131
  export interface CodecInterface {
@@ -135,7 +135,7 @@ export interface CodecInterface {
135
135
  fromValues(values: Iterable<number>): CodecBitmap;
136
136
  /**
137
137
  * Size-cap (`bytes.length <= maxBytes`) then portable-deserialize. Throws `IntegrityError` when the input
138
- * exceeds the cap or fails to decode — the native decoder is never handed unbounded or unsafe-format input.
138
+ * exceeds the cap or fails to decode — the decoder is never handed unbounded or unsafe-format input.
139
139
  *
140
140
  * "Fails to decode" includes bytes that are the right length and the wrong shape. A bitmap this returns must
141
141
  * satisfy every invariant its own operations assume (for roaring: containers and values in order, runs
@@ -161,6 +161,16 @@ export interface CodecInterface {
161
161
  * The answer is the whole check: an `owns` that answers `true` for anything lets any object through as a bitmap.
162
162
  */
163
163
  owns?(bitmap: unknown): boolean;
164
+ /**
165
+ * Whether an intersect of three or more operands should meet each chunk's operands smallest-first (by `size`).
166
+ *
167
+ * **Optional, and off by default.** A codec whose combine cost depends on the order, because an early operand that is
168
+ * large and of one container kind makes the running result expensive, sets it; the engine then pays one `size` read per
169
+ * operand per chunk to put the sparsest first. A codec whose combines cost about the same in any order, or whose
170
+ * `size` is expensive to read, leaves it out and keeps the operand order it was given. The answer is the
171
+ * same either way.
172
+ */
173
+ readonly intersectSmallestFirst?: boolean;
164
174
  }
165
175
  /**
166
176
  * Resolve a codec that a **public core entry point** was given, failing fast when it is missing.
@@ -3,13 +3,16 @@
3
3
  *
4
4
  * Everything this library does on the hot read path is small. Bulk-load is the exception: it is the one place
5
5
  * where a single call legitimately occupies the CPU for hundreds of milliseconds, and Node has exactly one thread
6
- * to occupy. Measured on a 1M-id load of 65,536 chunks onto in-memory storage, on a loaded Apple M3 Pro: **518 ms
7
- * wall, and 519 ms during which the event loop did not turn at all** (medians of 20 fresh-process runs). Wire that to a request handler and every other in-flight request on the instance — health
6
+ * to occupy. Measured on the native codec of 0.23.0 (every figure in this header is the native codec's, to be
7
+ * re-measured on the TypeScript codec), on a 1M-id load of 65,536 chunks onto in-memory storage, on a loaded Apple M3
8
+ * Pro: **518 ms wall, and 519 ms during which the event loop did not turn at all** (medians of 20 fresh-process
9
+ * runs). Wire that to a request handler and every other in-flight request on the instance — health
8
10
  * checks included — waits out the whole load.
9
11
  *
10
- * The fix is not to make the work asynchronous. That was measured too, and it is a 7x *regression*: handing each
11
- * chunk's insert to the threadpool costs ~9 µs of dispatch against ~1.5 µs of actual work once ids are spread
12
- * across ~62,000 chunks (636 ms versus 92 ms). The work is genuinely CPU-bound and genuinely small per chunk;
12
+ * The fix is not to make the work asynchronous. That was measured too, on the native codec's threadpool insert, and
13
+ * it was a 7x *regression*: handing each chunk's insert to the threadpool cost ~9 µs of dispatch against ~1.5 µs of
14
+ * actual work once ids are spread across ~62,000 chunks (636 ms versus 92 ms). The TypeScript codec has no
15
+ * threadpool path. The work is genuinely CPU-bound and genuinely small per chunk;
13
16
  * the only thing wrong with it is that it never stops to let anyone else go. So the fix is to keep the work
14
17
  * synchronous and periodically hand the loop back.
15
18
  *
@@ -19,7 +22,7 @@
19
22
  * the microtask queue drains to empty before the event loop advances a single phase. A loop awaiting them
20
23
  * yields to other *microtasks* and to nothing else; no pending I/O callback runs. Measured: 555 ms of
21
24
  * starvation against a 568 ms unyielded baseline **on the synthetic yield-primitive benchmark below** —
22
- * a different, smaller workload than the 442 ms end-to-end load above, which is why the two baselines
25
+ * a different, smaller workload than the 518 ms end-to-end load above, which is why the two baselines
23
26
  * differ. The yield must be a **macrotask** — hence
24
27
  * `Clock.yieldNow`, backed by `setImmediate` in production wiring.
25
28
  * 2. **Yielding per unit of work costs more than the work.** The yield has to be periodic, and `everyN` has to be
@@ -179,7 +179,7 @@ export declare class CrbmReader {
179
179
  has(chunkKey: number): boolean;
180
180
  /**
181
181
  * Range-GET one chunk's payload, verifying its CRC32C before returning. Returns `null` if the chunk is
182
- * absent. Throws `IntegrityError` on a CRC mismatch (the bytes must never reach the native
182
+ * absent. Throws `IntegrityError` on a CRC mismatch (the bytes must never reach the codec's
183
183
  * deserializer). The returned bytes may be shared (a small generation's chunks are kept by the reader and handed out
184
184
  * again), so callers must not change them.
185
185
  */
@@ -401,7 +401,7 @@ export declare class SegmentEngine {
401
401
  * The AND (or OR) of one chunk key across all operands, minus the excludes that hold it, or `null` if empty.
402
402
  * Operand chunks are fetched **in parallel** (the spec's parallel byte-range reads).
403
403
  *
404
- * The accumulator is a **clone** of the first operand's chunk, and that clone is load-bearing: every other
404
+ * The accumulator is a **clone** of one operand's chunk (the first, or for a codec that asks the one with the fewest values), and that clone is load-bearing: every other
405
405
  * bitmap here is the cached, shared Storage instance and must never be mutated, or the cache is poisoned for
406
406
  * every later reader. The remaining operands and the excludes are only *read* by the in-place ops, so they are
407
407
  * used as-is — one clone per key, not one per operand.
@@ -66,7 +66,7 @@
66
66
  * its number after the winner's object was already in the bucket. Up there nothing collects it, and it was derived
67
67
  * from a generation the winner has replaced, so when the winner was another erasure it still holds the id that
68
68
  * erasure has just reported gone. That object is deleted before this call returns, once its footer matches the
69
- * rewrite's and a renewal of the row has refused every publish fenced on a row read before it, under the version the
69
+ * rewrite's (a read of it that faults is thrown instead, and the object stays) and a renewal of the row has refused every publish fenced on a row read before it, under the version the
70
70
  * footer read reported, unless {@link DISCARD_ATTEMPTS} renewals lose to other writes (a load, a rollback, a
71
71
  * retention write): then it is kept, and can hold the winner's id, until a later call takes it: any erasure that
72
72
  * rewrites the segment (this one's re-run among them, while its id is still in the current generation) numbers above it
@@ -115,10 +115,16 @@ export declare function prepareLoadInput(input: LoadInput, codec: CodecInterface
115
115
  * input of a load goes through, and what a flavor offers its users to decode bytes they hold themselves.
116
116
  *
117
117
  * The bytes must be a `Uint8Array` (a Node `Buffer` is one), at most {@link MAX_SERIALIZED_LOAD_BYTES}, one whole
118
- * bitmap and structurally sound. A view is read over the bytes it really holds, and bytes in a `SharedArrayBuffer`
119
- * are copied first. An empty or detached buffer is the empty bitmap.
118
+ * bitmap and structurally sound. A view is read over the bytes it really holds, once, and the size checks, the
119
+ * copy and the decode all use that one view. Bytes in a `SharedArrayBuffer` are copied first. An empty or detached
120
+ * buffer is the empty bitmap, unless `refuseEmpty` is set.
120
121
  *
121
122
  * @param what names the input in a refusal's message.
122
- * @throws {ValidationError} for anything that is not such bytes; nothing reaches the codec's native decoder then.
123
+ * @param options `maxBytes` lowers the size cap for this call (it cannot raise it); `refuseEmpty` refuses an empty or
124
+ * detached buffer, which is not a bitmap.
125
+ * @throws {ValidationError} for anything that is not such bytes; nothing reaches the codec's decoder then.
123
126
  */
124
- export declare function decodeSerialized(bytes: unknown, codec: CodecInterface, what?: string): CodecBitmap;
127
+ export declare function decodeSerialized(bytes: unknown, codec: CodecInterface, what?: string, options?: {
128
+ readonly maxBytes?: number;
129
+ readonly refuseEmpty?: boolean;
130
+ }): CodecBitmap;
@@ -148,8 +148,8 @@ export interface LoadOptions {
148
148
  * `ValidationError`, naming its position and never the id. The object is the same, byte for byte, as an unsorted load
149
149
  * of the same ids writes under the same write identity.
150
150
  *
151
- * For ids only (an iterable or an async iterable): with `{ serialized }`, `{ bitmap }` or a `RoaringBitmap32` of the
152
- * flavor's own `roaring` (one from another copy loads as its ids, and takes `sorted`), and on core's
151
+ * For ids only (an iterable or an async iterable): with `{ serialized }`, `{ bitmap }` or a bitmap itself (an
152
+ * iterable with a `serialize` method, which a flavor loads as `{ bitmap }`), and on core's
153
153
  * `loadSegmentChunks`, it throws `ValidationError`, as does a value that is not a boolean, before any request.
154
154
  *
155
155
  * The ids are read after the segment's row is read and judged, and after its number and key are settled, so a load
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  mapWithConcurrency
3
- } from "./chunk-CGILDXGP.js";
3
+ } from "./chunk-BUDAVGBI.js";
4
4
  import {
5
5
  DEFAULT_NAMESPACE,
6
6
  applyRegistryPatch,
@@ -18,7 +18,7 @@ import {
18
18
  validateNewRegistryRecord,
19
19
  validateRegistryPatch,
20
20
  webCryptoEntropy
21
- } from "./chunk-7GT632UF.js";
21
+ } from "./chunk-45ML6YU4.js";
22
22
  import {
23
23
  CloudRoaringError,
24
24
  IntegrityError,
@@ -45,7 +45,7 @@ import {
45
45
  requireNonEmptyString,
46
46
  unsettledConflict,
47
47
  validateSegmentRef
48
- } from "./chunk-MVUD4R72.js";
48
+ } from "./chunk-2B2QF4FU.js";
49
49
 
50
50
  // src/drivers/_shared/object-registry-keys.ts
51
51
  var REGISTRY_SUFFIX = ".reg";
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export { BoundedLru } from './core/lru.js';
24
24
  export { safeMetrics } from './core/metrics.js';
25
25
  export { runExport } from './export/index.js';
26
26
  export type { SegmentReader } from './export/index.js';
27
- export { splitId } from './core/bit-route.js';
27
+ export { splitId, refusalOfId } from './core/bit-route.js';
28
28
  export { mapWithConcurrency } from './core/concurrency.js';
29
29
  export { resolveBudget, resolvePerOpBudget, checkBudget, collectWithinBudget } from './core/budget.js';
30
30
  export { segmentExists, listSegments } from './core/discover.js';
@@ -43,7 +43,7 @@ export { compileCombineMany, rebindCombineMany, runCombineMany } from './core/co
43
43
  export type { CombineManyDeps, CombineManyFeed, CombineManyOperand, CombineManyOutcome, CombineManyOutput, CombineManyRequest, CombineManyRun, CombineManyWrite, CompiledCombineMany, Expr, MaterializeManyFeedRecord, MaterializeManyOperandStats, MaterializeManyOutputStats, MaterializeManyStats, } from './core/combine-many.js';
44
44
  export type { LoadDeps, LoadOptions, LoadGuard, GuardRefusal, LoadJudgement, LoadResult, LoadRefusal, } from './core/load.js';
45
45
  export type { LoadInput, PortableBitmap } from './core/load-input.js';
46
- export { decodeSerialized } from './core/load-input.js';
46
+ export { decodeSerialized, MAX_SERIALIZED_LOAD_BYTES } from './core/load-input.js';
47
47
  export { listGenerations, rollbackSegment } from './core/rollback.js';
48
48
  export type { GenerationListDeps, GenerationEntry, RollbackResult } from './core/rollback.js';
49
49
  export { eraseIdFromSegment, findIdUnderTombstone } from './core/erase-id.js';