@cloudbitmaps/core 0.1.3 → 0.3.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/dist/index.d.cts CHANGED
@@ -122,6 +122,29 @@ declare function resolvePerOpBudget(opt: BudgetOption | undefined, storeBudget:
122
122
  * (disabled).
123
123
  */
124
124
  declare function checkBudget(budget: Budget | null, projectedRequests: number, op: string): void;
125
+ /**
126
+ * Drain an async iterable into an array, refusing **as soon as** the budget is exceeded.
127
+ *
128
+ * This exists because the obvious spelling is wrong in a way that is easy to miss:
129
+ *
130
+ * ```ts
131
+ * for await (const rec of registry.list(ns)) recs.push(rec);
132
+ * checkBudget(budget, recs.length, op); // ← too late; the memory is already spent
133
+ * ```
134
+ *
135
+ * That pattern bounds the **fan-out** but not the **enumeration**, so a tight budget refuses the work only
136
+ * after paying for the list — which defeats the purpose. It was measured buffering 20,000 registry records
137
+ * under `budget: { maxRequests: 2 }` before throwing. Draining incrementally bounds resident memory to
138
+ * `O(budget)` instead of `O(fleet)`.
139
+ *
140
+ * **The error message is deliberately vaguer than `checkBudget`'s.** It reports "more than N" rather than an
141
+ * exact total, because knowing the exact total requires finishing the scan — which is precisely the cost being
142
+ * refused. An honest approximation beats an expensive exact number.
143
+ *
144
+ * A `null` budget (`budget: false`) means the caller has explicitly opted out of fan-out limits, so this
145
+ * degrades to a plain drain.
146
+ */
147
+ declare function collectWithinBudget<T>(source: AsyncIterable<T>, budget: Budget | null, op: string): Promise<T[]>;
125
148
 
126
149
  /**
127
150
  * The determinism seam.
@@ -430,6 +453,15 @@ declare function isTransient(err: unknown): boolean;
430
453
  */
431
454
  declare function withRetry<T>(op: () => Promise<T>, policy: RetryPolicy, deps: RetryDeps): Promise<T>;
432
455
 
456
+ /**
457
+ * Default ceiling on resident warm-delta bytes for a single segment scan: 64 MiB.
458
+ *
459
+ * Chosen to be generous enough that no realistic segment trips it — a maximally-wide segment of 65,536 chunks
460
+ * would need ~1 KiB of warm delta per chunk to reach it — while still being small enough to keep a modest
461
+ * container (a 512 MiB Lambda, say) alive rather than OOM-killed when a pathological segment or a hostile
462
+ * caller shows up.
463
+ */
464
+ declare const DEFAULT_MAX_WARM_SCAN_BYTES: number;
433
465
  interface EngineDeps {
434
466
  readonly warm: IWarmDriver;
435
467
  readonly cold: ColdChunkSource;
@@ -463,6 +495,19 @@ interface EngineDeps {
463
495
  * row, so bounded concurrency is safe (`addMany` is already non-atomic on partial failure).
464
496
  */
465
497
  readonly writeConcurrency?: number;
498
+ /**
499
+ * Hard ceiling on the bytes one warm scan may hold resident, in bytes. Default {@link DEFAULT_MAX_WARM_SCAN_BYTES}.
500
+ *
501
+ * **Separate from `budget`, and enforced even when `budget: false`.** The budget bounds *cost* — billable
502
+ * requests — which is a different axis from memory, and conflating the two is what let a segment with
503
+ * thousands of warm chunks materialise ~12 MB before a `maxRequests: 2` budget could refuse it. It is also
504
+ * why `intersect` cannot use the budget as a memory bound: its budget is `common keys × operands`, a product
505
+ * that one wide operand can legitimately exceed in row count while staying entirely legal.
506
+ *
507
+ * Always on, because a ceiling that `budget: false` switches off is missing at exactly the moment it is
508
+ * needed. Configurable, because a ceiling you cannot raise is a landmine for a legitimately large segment.
509
+ */
510
+ readonly maxWarmScanBytes?: number;
466
511
  /**
467
512
  * Per-op denial-of-wallet budget (07 Decision #3 / T3), already resolved by the facade: a {@link Budget} to
468
513
  * enforce, or `null` to disable. Undefined ⇒ {@link DEFAULT_BUDGET} (a direct engine construction still gets
@@ -485,6 +530,7 @@ declare class SegmentEngine {
485
530
  /** Consistency for READ-path Warm fetches (precomputed; OCC RMW passes strong explicitly, see `warmGet`). */
486
531
  private readonly readOpts;
487
532
  private readonly writeConcurrency;
533
+ private readonly maxWarmScanBytes;
488
534
  /** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. See {@link checkBudget}. */
489
535
  private readonly budget;
490
536
  constructor(deps: EngineDeps);
@@ -1791,6 +1837,20 @@ declare function eraseNamespace(namespace: string, deps: EraseDeps, options: {
1791
1837
  * to confirm a reported tear.
1792
1838
  */
1793
1839
 
1840
+ /** Default in-flight fan-out for the consistency scan — bounded, no thundering herd. */
1841
+ /**
1842
+ * Default ceiling on how many registry records one consistency check may hold resident: 250,000.
1843
+ *
1844
+ * **Named `maxScanSegments`, not `maxSegments`, on purpose.** Compaction already has a `maxSegments`
1845
+ * (`CR_COMPACT_MAX_SEGMENTS`) that caps how much work one cycle does and then *continues normally*. This one
1846
+ * caps how much a scan may hold and *refuses* past it. Same-sounding names with opposite behaviour at the
1847
+ * limit is a trap; pairing this with `maxWarmScanBytes` instead makes both read as what they are — ceilings on
1848
+ * a scan that fail closed.
1849
+ *
1850
+ * Generous — the compaction docs target 100K+ segment fleets — while still bounding a DR drill's memory to
1851
+ * something a modest operator box survives. Raisable, because a ceiling you cannot lift is a landmine.
1852
+ */
1853
+ declare const DEFAULT_MAX_SCAN_SEGMENTS = 250000;
1794
1854
  interface ConsistencyIssue {
1795
1855
  readonly segment: string;
1796
1856
  readonly namespace?: string;
@@ -1830,6 +1890,7 @@ declare function runConsistencyCheck(deps: {
1830
1890
  }, options?: {
1831
1891
  namespace?: string;
1832
1892
  concurrency?: number;
1893
+ maxScanSegments?: number;
1833
1894
  }): Promise<ConsistencyReport>;
1834
1895
 
1835
1896
  /**
@@ -1963,4 +2024,4 @@ declare class CrbmWriter {
1963
2024
  private buildFooter;
1964
2025
  }
1965
2026
 
1966
- export { AWS_US_EAST_1_ONDEMAND, Aead, AeadSealed, type AuditEvent, type AuditEventKind, BlobReader, BlobSink, BoundedLru, type Budget, BudgetExceededError, type BudgetOption, type BulkLoadResult, CapabilityError, ChunkRef, type Clock, CloudRoaringError, type CodecBitmap, type CodecInterface, ColdCaps, ColdChunkSource, type CompactionCandidate, type CompactionCycleResult, type CompactionDeps, type CompactionOptions, type CompactionResult, type ConsistencyErrorEntry, type ConsistencyIssue, type ConsistencyReport, type CostAdvisory, type CostReport, CountingMetricsSink, CrbmColdChunkSource, type CrbmColdChunkSourceOptions, CrbmCrypto, CrbmReader, type CrbmReaderOptions, CrbmWriter, type CrbmWriterOptions, DEFAULT_BUDGET, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, type DestroyResult, type DiscoveryOptions, type EngineDeps, type EraseDeps, type EstimateInput, type ExportFailure, type ExportFormat, type ExportManifest, type ExportOptions, type ExportSink, type ExportWriter, type ExportedSegment, GenKey, type IAuditSink, IColdDriver, IKeystore, type IMetricsSink, IRegistryDriver, IWarmDriver, InProcessKeystore, type InProcessKeystoreOptions, IntegrityError, KeyUnavailableError, LocalFsColdDriver, LocalFsRegistryDriver, type LocalFsRegistryDriverOptions, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, type MemoryRegistryDriverOptions, MemoryWarmDriver, type MetricEvent, type MetricOpName, type MetricsSnapshot, NOOP_AUDIT, NOOP_METRICS, NewRegistryRecord, NoRow, NodeAead, NotFoundError, type PricingProfile, RecordingAuditSink, RegCaps, RegistryPatch, RegistryRecord, type RetryDeps, type RetryPolicy, RetryingColdChunkSource, RetryingColdDriver, type RetryingOptions, RetryingRegistryDriver, RetryingWarmDriver, type Rng, SegmentEngine, SegmentRef, SegmentSize, type SegmentSizing, TimeoutError, Token, type Topology, TransientError, UnsupportedError, ValidationError, WarmReadOptions, WarmRow, type Workload, WrappedDek, WriteConflictError, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isCloudRoaringError, isIntegrityError, isNotFoundError, isTransient, isTransientError, isValidationError, isWriteConflictError, joinId, mapWithConcurrency, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, validateSegmentRef, withRetry, writeCrbmGeneration };
2027
+ export { AWS_US_EAST_1_ONDEMAND, Aead, AeadSealed, type AuditEvent, type AuditEventKind, BlobReader, BlobSink, BoundedLru, type Budget, BudgetExceededError, type BudgetOption, type BulkLoadResult, CapabilityError, ChunkRef, type Clock, CloudRoaringError, type CodecBitmap, type CodecInterface, ColdCaps, ColdChunkSource, type CompactionCandidate, type CompactionCycleResult, type CompactionDeps, type CompactionOptions, type CompactionResult, type ConsistencyErrorEntry, type ConsistencyIssue, type ConsistencyReport, type CostAdvisory, type CostReport, CountingMetricsSink, CrbmColdChunkSource, type CrbmColdChunkSourceOptions, CrbmCrypto, CrbmReader, type CrbmReaderOptions, CrbmWriter, type CrbmWriterOptions, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, type DestroyResult, type DiscoveryOptions, type EngineDeps, type EraseDeps, type EstimateInput, type ExportFailure, type ExportFormat, type ExportManifest, type ExportOptions, type ExportSink, type ExportWriter, type ExportedSegment, GenKey, type IAuditSink, IColdDriver, IKeystore, type IMetricsSink, IRegistryDriver, IWarmDriver, InProcessKeystore, type InProcessKeystoreOptions, IntegrityError, KeyUnavailableError, LocalFsColdDriver, LocalFsRegistryDriver, type LocalFsRegistryDriverOptions, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, type MemoryRegistryDriverOptions, MemoryWarmDriver, type MetricEvent, type MetricOpName, type MetricsSnapshot, NOOP_AUDIT, NOOP_METRICS, NewRegistryRecord, NoRow, NodeAead, NotFoundError, type PricingProfile, RecordingAuditSink, RegCaps, RegistryPatch, RegistryRecord, type RetryDeps, type RetryPolicy, RetryingColdChunkSource, RetryingColdDriver, type RetryingOptions, RetryingRegistryDriver, RetryingWarmDriver, type Rng, SegmentEngine, SegmentRef, SegmentSize, type SegmentSizing, TimeoutError, Token, type Topology, TransientError, UnsupportedError, ValidationError, WarmReadOptions, WarmRow, type Workload, WrappedDek, WriteConflictError, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isCloudRoaringError, isIntegrityError, isNotFoundError, isTransient, isTransientError, isValidationError, isWriteConflictError, joinId, mapWithConcurrency, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, validateSegmentRef, withRetry, writeCrbmGeneration };
package/dist/index.d.ts CHANGED
@@ -122,6 +122,29 @@ declare function resolvePerOpBudget(opt: BudgetOption | undefined, storeBudget:
122
122
  * (disabled).
123
123
  */
124
124
  declare function checkBudget(budget: Budget | null, projectedRequests: number, op: string): void;
125
+ /**
126
+ * Drain an async iterable into an array, refusing **as soon as** the budget is exceeded.
127
+ *
128
+ * This exists because the obvious spelling is wrong in a way that is easy to miss:
129
+ *
130
+ * ```ts
131
+ * for await (const rec of registry.list(ns)) recs.push(rec);
132
+ * checkBudget(budget, recs.length, op); // ← too late; the memory is already spent
133
+ * ```
134
+ *
135
+ * That pattern bounds the **fan-out** but not the **enumeration**, so a tight budget refuses the work only
136
+ * after paying for the list — which defeats the purpose. It was measured buffering 20,000 registry records
137
+ * under `budget: { maxRequests: 2 }` before throwing. Draining incrementally bounds resident memory to
138
+ * `O(budget)` instead of `O(fleet)`.
139
+ *
140
+ * **The error message is deliberately vaguer than `checkBudget`'s.** It reports "more than N" rather than an
141
+ * exact total, because knowing the exact total requires finishing the scan — which is precisely the cost being
142
+ * refused. An honest approximation beats an expensive exact number.
143
+ *
144
+ * A `null` budget (`budget: false`) means the caller has explicitly opted out of fan-out limits, so this
145
+ * degrades to a plain drain.
146
+ */
147
+ declare function collectWithinBudget<T>(source: AsyncIterable<T>, budget: Budget | null, op: string): Promise<T[]>;
125
148
 
126
149
  /**
127
150
  * The determinism seam.
@@ -430,6 +453,15 @@ declare function isTransient(err: unknown): boolean;
430
453
  */
431
454
  declare function withRetry<T>(op: () => Promise<T>, policy: RetryPolicy, deps: RetryDeps): Promise<T>;
432
455
 
456
+ /**
457
+ * Default ceiling on resident warm-delta bytes for a single segment scan: 64 MiB.
458
+ *
459
+ * Chosen to be generous enough that no realistic segment trips it — a maximally-wide segment of 65,536 chunks
460
+ * would need ~1 KiB of warm delta per chunk to reach it — while still being small enough to keep a modest
461
+ * container (a 512 MiB Lambda, say) alive rather than OOM-killed when a pathological segment or a hostile
462
+ * caller shows up.
463
+ */
464
+ declare const DEFAULT_MAX_WARM_SCAN_BYTES: number;
433
465
  interface EngineDeps {
434
466
  readonly warm: IWarmDriver;
435
467
  readonly cold: ColdChunkSource;
@@ -463,6 +495,19 @@ interface EngineDeps {
463
495
  * row, so bounded concurrency is safe (`addMany` is already non-atomic on partial failure).
464
496
  */
465
497
  readonly writeConcurrency?: number;
498
+ /**
499
+ * Hard ceiling on the bytes one warm scan may hold resident, in bytes. Default {@link DEFAULT_MAX_WARM_SCAN_BYTES}.
500
+ *
501
+ * **Separate from `budget`, and enforced even when `budget: false`.** The budget bounds *cost* — billable
502
+ * requests — which is a different axis from memory, and conflating the two is what let a segment with
503
+ * thousands of warm chunks materialise ~12 MB before a `maxRequests: 2` budget could refuse it. It is also
504
+ * why `intersect` cannot use the budget as a memory bound: its budget is `common keys × operands`, a product
505
+ * that one wide operand can legitimately exceed in row count while staying entirely legal.
506
+ *
507
+ * Always on, because a ceiling that `budget: false` switches off is missing at exactly the moment it is
508
+ * needed. Configurable, because a ceiling you cannot raise is a landmine for a legitimately large segment.
509
+ */
510
+ readonly maxWarmScanBytes?: number;
466
511
  /**
467
512
  * Per-op denial-of-wallet budget (07 Decision #3 / T3), already resolved by the facade: a {@link Budget} to
468
513
  * enforce, or `null` to disable. Undefined ⇒ {@link DEFAULT_BUDGET} (a direct engine construction still gets
@@ -485,6 +530,7 @@ declare class SegmentEngine {
485
530
  /** Consistency for READ-path Warm fetches (precomputed; OCC RMW passes strong explicitly, see `warmGet`). */
486
531
  private readonly readOpts;
487
532
  private readonly writeConcurrency;
533
+ private readonly maxWarmScanBytes;
488
534
  /** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. See {@link checkBudget}. */
489
535
  private readonly budget;
490
536
  constructor(deps: EngineDeps);
@@ -1791,6 +1837,20 @@ declare function eraseNamespace(namespace: string, deps: EraseDeps, options: {
1791
1837
  * to confirm a reported tear.
1792
1838
  */
1793
1839
 
1840
+ /** Default in-flight fan-out for the consistency scan — bounded, no thundering herd. */
1841
+ /**
1842
+ * Default ceiling on how many registry records one consistency check may hold resident: 250,000.
1843
+ *
1844
+ * **Named `maxScanSegments`, not `maxSegments`, on purpose.** Compaction already has a `maxSegments`
1845
+ * (`CR_COMPACT_MAX_SEGMENTS`) that caps how much work one cycle does and then *continues normally*. This one
1846
+ * caps how much a scan may hold and *refuses* past it. Same-sounding names with opposite behaviour at the
1847
+ * limit is a trap; pairing this with `maxWarmScanBytes` instead makes both read as what they are — ceilings on
1848
+ * a scan that fail closed.
1849
+ *
1850
+ * Generous — the compaction docs target 100K+ segment fleets — while still bounding a DR drill's memory to
1851
+ * something a modest operator box survives. Raisable, because a ceiling you cannot lift is a landmine.
1852
+ */
1853
+ declare const DEFAULT_MAX_SCAN_SEGMENTS = 250000;
1794
1854
  interface ConsistencyIssue {
1795
1855
  readonly segment: string;
1796
1856
  readonly namespace?: string;
@@ -1830,6 +1890,7 @@ declare function runConsistencyCheck(deps: {
1830
1890
  }, options?: {
1831
1891
  namespace?: string;
1832
1892
  concurrency?: number;
1893
+ maxScanSegments?: number;
1833
1894
  }): Promise<ConsistencyReport>;
1834
1895
 
1835
1896
  /**
@@ -1963,4 +2024,4 @@ declare class CrbmWriter {
1963
2024
  private buildFooter;
1964
2025
  }
1965
2026
 
1966
- export { AWS_US_EAST_1_ONDEMAND, Aead, AeadSealed, type AuditEvent, type AuditEventKind, BlobReader, BlobSink, BoundedLru, type Budget, BudgetExceededError, type BudgetOption, type BulkLoadResult, CapabilityError, ChunkRef, type Clock, CloudRoaringError, type CodecBitmap, type CodecInterface, ColdCaps, ColdChunkSource, type CompactionCandidate, type CompactionCycleResult, type CompactionDeps, type CompactionOptions, type CompactionResult, type ConsistencyErrorEntry, type ConsistencyIssue, type ConsistencyReport, type CostAdvisory, type CostReport, CountingMetricsSink, CrbmColdChunkSource, type CrbmColdChunkSourceOptions, CrbmCrypto, CrbmReader, type CrbmReaderOptions, CrbmWriter, type CrbmWriterOptions, DEFAULT_BUDGET, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, type DestroyResult, type DiscoveryOptions, type EngineDeps, type EraseDeps, type EstimateInput, type ExportFailure, type ExportFormat, type ExportManifest, type ExportOptions, type ExportSink, type ExportWriter, type ExportedSegment, GenKey, type IAuditSink, IColdDriver, IKeystore, type IMetricsSink, IRegistryDriver, IWarmDriver, InProcessKeystore, type InProcessKeystoreOptions, IntegrityError, KeyUnavailableError, LocalFsColdDriver, LocalFsRegistryDriver, type LocalFsRegistryDriverOptions, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, type MemoryRegistryDriverOptions, MemoryWarmDriver, type MetricEvent, type MetricOpName, type MetricsSnapshot, NOOP_AUDIT, NOOP_METRICS, NewRegistryRecord, NoRow, NodeAead, NotFoundError, type PricingProfile, RecordingAuditSink, RegCaps, RegistryPatch, RegistryRecord, type RetryDeps, type RetryPolicy, RetryingColdChunkSource, RetryingColdDriver, type RetryingOptions, RetryingRegistryDriver, RetryingWarmDriver, type Rng, SegmentEngine, SegmentRef, SegmentSize, type SegmentSizing, TimeoutError, Token, type Topology, TransientError, UnsupportedError, ValidationError, WarmReadOptions, WarmRow, type Workload, WrappedDek, WriteConflictError, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isCloudRoaringError, isIntegrityError, isNotFoundError, isTransient, isTransientError, isValidationError, isWriteConflictError, joinId, mapWithConcurrency, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, validateSegmentRef, withRetry, writeCrbmGeneration };
2027
+ export { AWS_US_EAST_1_ONDEMAND, Aead, AeadSealed, type AuditEvent, type AuditEventKind, BlobReader, BlobSink, BoundedLru, type Budget, BudgetExceededError, type BudgetOption, type BulkLoadResult, CapabilityError, ChunkRef, type Clock, CloudRoaringError, type CodecBitmap, type CodecInterface, ColdCaps, ColdChunkSource, type CompactionCandidate, type CompactionCycleResult, type CompactionDeps, type CompactionOptions, type CompactionResult, type ConsistencyErrorEntry, type ConsistencyIssue, type ConsistencyReport, type CostAdvisory, type CostReport, CountingMetricsSink, CrbmColdChunkSource, type CrbmColdChunkSourceOptions, CrbmCrypto, CrbmReader, type CrbmReaderOptions, CrbmWriter, type CrbmWriterOptions, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, type DestroyResult, type DiscoveryOptions, type EngineDeps, type EraseDeps, type EstimateInput, type ExportFailure, type ExportFormat, type ExportManifest, type ExportOptions, type ExportSink, type ExportWriter, type ExportedSegment, GenKey, type IAuditSink, IColdDriver, IKeystore, type IMetricsSink, IRegistryDriver, IWarmDriver, InProcessKeystore, type InProcessKeystoreOptions, IntegrityError, KeyUnavailableError, LocalFsColdDriver, LocalFsRegistryDriver, type LocalFsRegistryDriverOptions, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, type MemoryRegistryDriverOptions, MemoryWarmDriver, type MetricEvent, type MetricOpName, type MetricsSnapshot, NOOP_AUDIT, NOOP_METRICS, NewRegistryRecord, NoRow, NodeAead, NotFoundError, type PricingProfile, RecordingAuditSink, RegCaps, RegistryPatch, RegistryRecord, type RetryDeps, type RetryPolicy, RetryingColdChunkSource, RetryingColdDriver, type RetryingOptions, RetryingRegistryDriver, RetryingWarmDriver, type Rng, SegmentEngine, SegmentRef, SegmentSize, type SegmentSizing, TimeoutError, Token, type Topology, TransientError, UnsupportedError, ValidationError, WarmReadOptions, WarmRow, type Workload, WrappedDek, WriteConflictError, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isCloudRoaringError, isIntegrityError, isNotFoundError, isTransient, isTransientError, isValidationError, isWriteConflictError, joinId, mapWithConcurrency, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, validateSegmentRef, withRetry, writeCrbmGeneration };
package/dist/index.js CHANGED
@@ -117,6 +117,18 @@ function checkBudget(budget, projectedRequests, op) {
117
117
  );
118
118
  }
119
119
  }
120
+ async function collectWithinBudget(source, budget, op) {
121
+ const out = [];
122
+ for await (const item of source) {
123
+ out.push(item);
124
+ if (budget !== null && out.length > budget.maxRequests) {
125
+ throw new BudgetExceededError(
126
+ `${op} would fan out to more than ${budget.maxRequests} units, over the per-op budget \u2014 the scan was abandoned at that point rather than completed, so no exact total is available. Raise \`budget.maxRequests\`, override it per-op, or set \`budget: false\``
127
+ );
128
+ }
129
+ }
130
+ return out;
131
+ }
120
132
 
121
133
  // src/core/keys.ts
122
134
  var FIELD = " ";
@@ -310,6 +322,7 @@ var ZeroRng = class {
310
322
  return 0;
311
323
  }
312
324
  };
325
+ var DEFAULT_MAX_WARM_SCAN_BYTES = 64 * 1024 * 1024;
313
326
  var SegmentEngine = class {
314
327
  warm;
315
328
  cold;
@@ -325,6 +338,7 @@ var SegmentEngine = class {
325
338
  /** Consistency for READ-path Warm fetches (precomputed; OCC RMW passes strong explicitly, see `warmGet`). */
326
339
  readOpts;
327
340
  writeConcurrency;
341
+ maxWarmScanBytes;
328
342
  /** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. See {@link checkBudget}. */
329
343
  budget;
330
344
  constructor(deps) {
@@ -339,6 +353,12 @@ var SegmentEngine = class {
339
353
  this.occBackoff = deps.occBackoff ?? DEFAULT_OCC_BACKOFF;
340
354
  this.readOpts = { consistent: (deps.warmReadConsistency ?? "strong") !== "eventual" };
341
355
  this.writeConcurrency = deps.writeConcurrency ?? 1;
356
+ this.maxWarmScanBytes = deps.maxWarmScanBytes ?? DEFAULT_MAX_WARM_SCAN_BYTES;
357
+ if (!Number.isFinite(this.maxWarmScanBytes) || this.maxWarmScanBytes < 1) {
358
+ throw new ValidationError(
359
+ `maxWarmScanBytes must be a finite number >= 1 (got ${String(deps.maxWarmScanBytes)})`
360
+ );
361
+ }
342
362
  this.budget = deps.budget === void 0 ? DEFAULT_BUDGET : deps.budget;
343
363
  this.metrics = safeMetrics(deps.metrics ?? NOOP_METRICS);
344
364
  this.metricsOn = this.metrics !== NOOP_METRICS;
@@ -370,7 +390,7 @@ var SegmentEngine = class {
370
390
  return cold ? cold.has(remainder) : false;
371
391
  }
372
392
  async count(seg) {
373
- const warmRows = await this.collectWarm(seg);
393
+ const warmRows = await this.collectWarm(seg, "count", this.budget?.maxRequests ?? null);
374
394
  const cardinalities = this.cold.cardinalities ? await this.cold.cardinalities(seg) : null;
375
395
  if (!cardinalities) {
376
396
  const chunkKeys = await this.chunkKeys(seg, warmRows);
@@ -402,7 +422,7 @@ var SegmentEngine = class {
402
422
  return this.cold.sizeOf ? this.cold.sizeOf(seg) : Promise.resolve(null);
403
423
  }
404
424
  async *iterate(seg) {
405
- const warmRows = await this.collectWarm(seg);
425
+ const warmRows = await this.collectWarm(seg, "iterate", this.budget?.maxRequests ?? null);
406
426
  const chunkKeys = await this.chunkKeys(seg, warmRows);
407
427
  checkBudget(this.budget, chunkKeys.length, "iterate");
408
428
  const gen = await this.currentGen(seg);
@@ -449,7 +469,7 @@ var SegmentEngine = class {
449
469
  const budget = resolvePerOpBudget(options?.budget, this.budget);
450
470
  const operands = await Promise.all(
451
471
  segs.map(async (seg) => {
452
- const warmRows = await this.collectWarm(seg);
472
+ const warmRows = await this.collectWarm(seg, "intersect", null);
453
473
  const keys = await this.chunkKeys(seg, warmRows);
454
474
  const gen = await this.currentGen(seg);
455
475
  return { seg, warmRows, keys: new Set(keys), gen };
@@ -590,12 +610,22 @@ var SegmentEngine = class {
590
610
  }
591
611
  return row;
592
612
  }
593
- async collectWarm(seg) {
613
+ async collectWarm(seg, op, rowCeiling) {
594
614
  const rows = /* @__PURE__ */ new Map();
595
615
  let bytes = 0;
596
616
  for await (const row of this.warm.listChunks(seg, this.readOpts)) {
597
617
  rows.set(row.chunkKey, row.bytes);
598
618
  bytes += row.bytes.length;
619
+ if (bytes > this.maxWarmScanBytes) {
620
+ throw new BudgetExceededError(
621
+ `${op} would hold more than ${this.maxWarmScanBytes} bytes of warm deltas resident for one segment \u2014 the scan was abandoned there rather than completed. Raise \`maxWarmScanBytes\`, narrow the segment, or compact it so fewer chunks carry warm deltas. (This ceiling is independent of \`budget\` and stays in force when \`budget: false\`.)`
622
+ );
623
+ }
624
+ if (rowCeiling !== null && rows.size > rowCeiling) {
625
+ throw new BudgetExceededError(
626
+ `${op} would read more than ${rowCeiling} warm chunk rows, over the per-op budget \u2014 the scan was abandoned at that point rather than completed, so no exact total is available. Raise \`budget.maxRequests\`, override it per-op, or set \`budget: false\``
627
+ );
628
+ }
599
629
  }
600
630
  if (this.metricsOn) {
601
631
  this.metrics.onEvent({
@@ -1083,22 +1113,44 @@ function aadFor(ref, generation, scope) {
1083
1113
  }
1084
1114
 
1085
1115
  // src/core/crbm/crc32c.ts
1086
- var TABLE = (() => {
1116
+ var TABLES = (() => {
1087
1117
  const POLY = 2197175160;
1088
- const table = new Uint32Array(256);
1118
+ const t0 = new Uint32Array(256);
1089
1119
  for (let n = 0; n < 256; n++) {
1090
1120
  let c = n;
1091
- for (let k = 0; k < 8; k++) {
1092
- c = c & 1 ? POLY ^ c >>> 1 : c >>> 1;
1121
+ for (let k = 0; k < 8; k++) c = c & 1 ? POLY ^ c >>> 1 : c >>> 1;
1122
+ t0[n] = c >>> 0;
1123
+ }
1124
+ const tables = [t0];
1125
+ for (let k = 1; k < 8; k++) {
1126
+ const prev = tables[k - 1];
1127
+ const next = new Uint32Array(256);
1128
+ for (let n = 0; n < 256; n++) {
1129
+ const v = prev[n];
1130
+ next[n] = (v >>> 8 ^ t0[v & 255]) >>> 0;
1093
1131
  }
1094
- table[n] = c >>> 0;
1132
+ tables.push(next);
1095
1133
  }
1096
- return table;
1134
+ return tables;
1097
1135
  })();
1136
+ var T0 = TABLES[0];
1137
+ var T1 = TABLES[1];
1138
+ var T2 = TABLES[2];
1139
+ var T3 = TABLES[3];
1140
+ var T4 = TABLES[4];
1141
+ var T5 = TABLES[5];
1142
+ var T6 = TABLES[6];
1143
+ var T7 = TABLES[7];
1098
1144
  function crc32c(bytes, seed = 0) {
1099
1145
  let crc = (seed ^ 4294967295) >>> 0;
1100
- for (let i = 0; i < bytes.length; i++) {
1101
- crc = (TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8) >>> 0;
1146
+ let i = 0;
1147
+ const n8 = bytes.length - bytes.length % 8;
1148
+ for (; i < n8; i += 8) {
1149
+ crc ^= bytes[i] | bytes[i + 1] << 8 | bytes[i + 2] << 16 | bytes[i + 3] << 24;
1150
+ crc = (T7[crc & 255] ^ T6[crc >>> 8 & 255] ^ T5[crc >>> 16 & 255] ^ T4[crc >>> 24 & 255] ^ T3[bytes[i + 4]] ^ T2[bytes[i + 5]] ^ T1[bytes[i + 6]] ^ T0[bytes[i + 7]]) >>> 0;
1151
+ }
1152
+ for (; i < bytes.length; i++) {
1153
+ crc = (T0[(crc ^ bytes[i]) & 255] ^ crc >>> 8) >>> 0;
1102
1154
  }
1103
1155
  return (crc ^ 4294967295) >>> 0;
1104
1156
  }
@@ -1609,6 +1661,7 @@ function coldBlobReader(driver, key) {
1609
1661
  };
1610
1662
  }
1611
1663
  var DEFAULT_CURRENT_GEN_TTL_MS = 2e3;
1664
+ var BULK_FLUSH_IDS = 1 << 20;
1612
1665
  var DEFAULT_MAX_OPEN_SEGMENTS = 1024;
1613
1666
  var DEFAULT_MAX_OPEN_INDEX_BYTES = 64 * 1024 * 1024;
1614
1667
  var CrbmColdChunkSource = class {
@@ -1886,15 +1939,29 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
1886
1939
  }
1887
1940
  const codec = requireCodec(options.codec, "bulkLoadCrbmGeneration");
1888
1941
  const byChunk = /* @__PURE__ */ new Map();
1942
+ const pendingByChunk = /* @__PURE__ */ new Map();
1943
+ let pending = 0;
1944
+ const flushPending = () => {
1945
+ for (const [chunkKey, rems] of pendingByChunk) {
1946
+ if (rems.length === 0) continue;
1947
+ const existingBitmap = byChunk.get(chunkKey);
1948
+ if (existingBitmap === void 0) byChunk.set(chunkKey, codec.fromValues(rems));
1949
+ else existingBitmap.addMany(rems);
1950
+ rems.length = 0;
1951
+ }
1952
+ pending = 0;
1953
+ };
1889
1954
  for await (const id of toAsyncIterable(ids)) {
1890
1955
  const { chunkKey, remainder } = splitId(id);
1891
- let bitmap = byChunk.get(chunkKey);
1892
- if (bitmap === void 0) {
1893
- bitmap = codec.empty();
1894
- byChunk.set(chunkKey, bitmap);
1956
+ let bucket = pendingByChunk.get(chunkKey);
1957
+ if (bucket === void 0) {
1958
+ bucket = [];
1959
+ pendingByChunk.set(chunkKey, bucket);
1895
1960
  }
1896
- bitmap.add(remainder);
1961
+ bucket.push(remainder);
1962
+ if (++pending >= BULK_FLUSH_IDS) flushPending();
1897
1963
  }
1964
+ flushPending();
1898
1965
  let cardinality = 0;
1899
1966
  const chunks = [];
1900
1967
  for (const [chunkKey, bitmap] of byChunk) {
@@ -3472,6 +3539,7 @@ async function eraseWarm(warm, ref) {
3472
3539
  }
3473
3540
 
3474
3541
  // src/core/consistency.ts
3542
+ var DEFAULT_MAX_SCAN_SEGMENTS = 25e4;
3475
3543
  var DEFAULT_CHECK_CONCURRENCY = 8;
3476
3544
  async function listGenerations(cold, ref) {
3477
3545
  const present = /* @__PURE__ */ new Set();
@@ -3483,8 +3551,21 @@ async function runConsistencyCheck(deps, options = {}) {
3483
3551
  if (!Number.isInteger(concurrency) || concurrency < 1) {
3484
3552
  throw new ValidationError(`concurrency must be a positive integer; got ${concurrency}`);
3485
3553
  }
3554
+ const maxScanSegments = options.maxScanSegments ?? DEFAULT_MAX_SCAN_SEGMENTS;
3555
+ if (!Number.isFinite(maxScanSegments) || maxScanSegments < 1) {
3556
+ throw new ValidationError(
3557
+ `maxScanSegments must be a finite number >= 1; got ${String(options.maxScanSegments)}`
3558
+ );
3559
+ }
3486
3560
  const recs = [];
3487
- for await (const rec of deps.registry.list(options.namespace)) recs.push(rec);
3561
+ for await (const rec of deps.registry.list(options.namespace)) {
3562
+ recs.push(rec);
3563
+ if (recs.length > maxScanSegments) {
3564
+ throw new BudgetExceededError(
3565
+ `checkConsistency would enumerate more than ${maxScanSegments} segments \u2014 the scan was abandoned there rather than completed. Narrow it with \`namespace\`, or raise \`maxScanSegments\` if the fleet really is that large and the memory is available.`
3566
+ );
3567
+ }
3568
+ }
3488
3569
  const results = await mapWithConcurrency(recs, concurrency, async (rec) => {
3489
3570
  if (rec.status === "destroyed") return { kind: "ok" };
3490
3571
  const ref = { segment: rec.segment, namespace: rec.namespace };
@@ -3676,6 +3757,6 @@ var RetryingRegistryDriver = class {
3676
3757
  }
3677
3758
  };
3678
3759
 
3679
- export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
3760
+ export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
3680
3761
  //# sourceMappingURL=index.js.map
3681
3762
  //# sourceMappingURL=index.js.map