@cloudbitmaps/roaring 0.8.1 → 0.9.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/README.md +36 -2
- package/dist/bin/compact-segments.js +70 -3
- package/dist/bin/compact-segments.js.map +1 -1
- package/dist/bin/export-segments.js +1 -1
- package/dist/{chunk-DAV4F5WM.js → chunk-D6LIJSGF.js} +125 -15
- package/dist/chunk-D6LIJSGF.js.map +1 -0
- package/dist/index.cjs +122 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -3
- package/dist/index.d.ts +100 -3
- package/dist/index.js +123 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-DAV4F5WM.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, DropResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
1
|
+
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, DropResult, RetentionPolicy, SetRetentionResult, RetireExpiredOptions, RetireExpiredResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -422,7 +422,12 @@ declare class CloudRoaring {
|
|
|
422
422
|
*
|
|
423
423
|
* **Omitting the namespace addresses a different segment and is a silent no-op** — you get
|
|
424
424
|
* `{ dropped: false, reason: 'absent' }`, not a throw, so a retention loop with that mistake deletes nothing
|
|
425
|
-
* forever and quietly.
|
|
425
|
+
* forever and quietly. Branch on `dropped`, and treat `reason: 'absent'` as the alert.
|
|
426
|
+
*
|
|
427
|
+
* **Works on an accumulator too.** A segment you created by writing to it — never bulk-loaded, never compacted,
|
|
428
|
+
* so it has no registry row and no Cold objects — is retired by deleting its Warm rows alone. That reports
|
|
429
|
+
* `{ dropped: true, reason: 'warm-only' }`: there is no tombstone to write, and deliberately none is written,
|
|
430
|
+
* because a `destroyed` row per retired daily bucket would be registry litter. The data really is gone.
|
|
426
431
|
*
|
|
427
432
|
* **Inspect `generationsRemaining`.** Empty is the normal outcome; non-empty means the storage was NOT fully
|
|
428
433
|
* reclaimed and the drop should be re-run. A compaction that was already in flight when the tombstone landed
|
|
@@ -443,6 +448,98 @@ declare class CloudRoaring {
|
|
|
443
448
|
dryRun?: boolean;
|
|
444
449
|
audit?: IAuditSink;
|
|
445
450
|
}): Promise<DropResult>;
|
|
451
|
+
/**
|
|
452
|
+
* **Record when this segment becomes eligible for retirement.** One registry write; nothing is deleted here,
|
|
453
|
+
* and nothing starts running. `retireExpired` is what acts on the policy, and **you** decide when that runs —
|
|
454
|
+
* an EventBridge rule, a CronJob, a queue consumer, whatever your deployment already has. This library starts
|
|
455
|
+
* no background thread (it has to work identically in a Lambda, an edge isolate and a long-lived server).
|
|
456
|
+
*
|
|
457
|
+
* `expiresAt` is an **absolute epoch-ms you compute**, not a duration the library derives. A relative TTL would
|
|
458
|
+
* have to be anchored to something the library knows — `updatedAt`, or the current generation — and compaction
|
|
459
|
+
* rewrites both, so "expire 30 days after the last write" would keep a busy daily bucket alive forever
|
|
460
|
+
* precisely because the daemon is working.
|
|
461
|
+
*
|
|
462
|
+
* ```ts
|
|
463
|
+
* const DAY = 86_400_000;
|
|
464
|
+
* const ref = { namespace: 'active-daily', segment: '2026-08-05' };
|
|
465
|
+
* await store.setRetention(ref, { expiresAt: Date.now() + 30 * DAY });
|
|
466
|
+
* ```
|
|
467
|
+
*
|
|
468
|
+
* **Works on an accumulator**, which is the whole point: a segment you created by writing to it has no
|
|
469
|
+
* registry row, so it is invisible to `registry.list()` and therefore to every fleet-wide operation, including
|
|
470
|
+
* the sweep. This call mints the row (`createdRow: true` in the result) with **no Cold generation**, so it
|
|
471
|
+
* becomes enumerable while every read still resolves exactly as before.
|
|
472
|
+
*
|
|
473
|
+
* A value in the past is legal and means "eligible on the next sweep" — backfilling a policy onto existing
|
|
474
|
+
* buckets is normal. A value below `MIN_EXPIRES_AT_MS` (2001-09-09) is rejected: it is almost certainly epoch
|
|
475
|
+
* **seconds**, which would read as long-expired and retire the segment on the next pass. Needs a `registry`
|
|
476
|
+
* in the store config (throws {@link UnsupportedError} otherwise), and refuses a crypto-shredded segment.
|
|
477
|
+
*/
|
|
478
|
+
setRetention(ref: SegmentRef, policy: RetentionPolicy): Promise<SetRetentionResult>;
|
|
479
|
+
/**
|
|
480
|
+
* **The stored retention policy**, or `null` if the segment has none (or has no registry row, or is a
|
|
481
|
+
* tombstone). Returns the string `'invalid'` for a row whose `expiresAt` is present but unusable — a
|
|
482
|
+
* hand-edited row, or one from a restore — so a malformed policy is visible rather than silently reading as
|
|
483
|
+
* "never expires" on a segment someone believes is expiring.
|
|
484
|
+
*/
|
|
485
|
+
getRetention(ref: SegmentRef): Promise<RetentionPolicy | null | 'invalid'>;
|
|
486
|
+
/**
|
|
487
|
+
* **Cancel a segment's expiry** so no sweep retires it. Returns whether a policy was actually removed (`false`
|
|
488
|
+
* when there was none). A separate verb from `setRetention` on purpose: "never expire" as a magic value passed
|
|
489
|
+
* to the setter is how a typo becomes a deletion.
|
|
490
|
+
*/
|
|
491
|
+
clearRetention(ref: SegmentRef): Promise<boolean>;
|
|
492
|
+
/**
|
|
493
|
+
* **Run the retention sweep: retire every segment whose `expiresAt` has passed.** This is the call that acts on
|
|
494
|
+
* the policies `setRetention` records — and it is a **call, not a daemon**. Nothing schedules it; you run it from
|
|
495
|
+
* whatever heartbeat your deployment already has (an EventBridge rule, a Kubernetes CronJob, a queue consumer,
|
|
496
|
+
* your compaction worker's loop). A library that started a timer would behave differently in a Lambda, an edge
|
|
497
|
+
* isolate and a long-lived server, which is worse than not having one.
|
|
498
|
+
*
|
|
499
|
+
* Each retirement goes through `dropSegment`, so the Warm → registry → Cold ordering, the re-sweep for a
|
|
500
|
+
* generation staged by an in-flight compaction, and the `generationsRemaining` report all come from one
|
|
501
|
+
* implementation rather than two.
|
|
502
|
+
*
|
|
503
|
+
* ```ts
|
|
504
|
+
* // In your scheduled handler. Start with a preview in a new deployment.
|
|
505
|
+
* const preview = await store.retireExpired({ namespace: 'active-daily', dryRun: true });
|
|
506
|
+
* console.log(`would retire ${preview.retired} of ${preview.scanned} (limited: ${preview.limited})`);
|
|
507
|
+
*
|
|
508
|
+
* const swept = await store.retireExpired({ namespace: 'active-daily' });
|
|
509
|
+
* for (const e of swept.entries) {
|
|
510
|
+
* if (e.action === 'skipped') console.warn(`${e.segment}: ${e.reason}`); // invalid-policy / limit / failed: …
|
|
511
|
+
* if (e.action === 'retired' && e.result.generationsRemaining.length > 0) {
|
|
512
|
+
* console.warn(`${e.segment}: storage not fully reclaimed — re-run`);
|
|
513
|
+
* }
|
|
514
|
+
* }
|
|
515
|
+
* if (swept.limited) scheduleAnotherPassSoon(); // more are still eligible
|
|
516
|
+
* ```
|
|
517
|
+
*
|
|
518
|
+
* **Read the ledger.** A per-segment *fault* is an entry, not an exception — a throw from the middle of a fleet
|
|
519
|
+
* sweep would leave the caller unable to say which segments were retired, having already retired some. (A bad
|
|
520
|
+
* argument does throw, and so does a fleet larger than `maxScanSegments`.) `limited: true` means the per-cycle
|
|
521
|
+
* `limit` (default 100) cut the pass short and more are eligible. That cap is charged on **attempts**, not
|
|
522
|
+
* successes, which is what makes it a real bound: `dropSegment` deletes Warm and writes the tombstone before
|
|
523
|
+
* sweeping Cold, so a fault in the Cold phase is a segment that is already retired. Retirements are sequential
|
|
524
|
+
* (~8 round trips each), so `limit` is a wall-clock knob too, and `retired` counts deletions only — a dry run
|
|
525
|
+
* reports `wouldRetire` instead, so a dashboard summing `retired` can never show a phantom deletion.
|
|
526
|
+
*
|
|
527
|
+
* It also **deletes the tombstone rows its own past retirements left**, after `tombstoneGraceMs` (default 24 h)
|
|
528
|
+
* and only once that segment's Warm rows and Cold generations are provably gone — collecting a straggler
|
|
529
|
+
* generation itself first, since nothing else ever would for a tombstoned segment. Attribution is a **positive
|
|
530
|
+
* marker the sweep stamps on its own retirements**, not an inference from "destroyed + an expired policy": a
|
|
531
|
+
* crypto-shred leaves `retention` untouched, so setting a policy and then honouring a right-to-erasure request
|
|
532
|
+
* mid-window produces exactly that row, and deleting it would destroy the Art. 17 attestation and un-fence the
|
|
533
|
+
* name. Pass `purgeTombstones: false` to keep every tombstone.
|
|
534
|
+
*
|
|
535
|
+
* Needs the store built with a **raw cold driver + a registry** (throws {@link UnsupportedError} otherwise),
|
|
536
|
+
* because retiring a segment deletes its Cold objects. `now` defaults to the store's clock.
|
|
537
|
+
*/
|
|
538
|
+
retireExpired(options?: Omit<RetireExpiredOptions, 'now'> & {
|
|
539
|
+
now?: number;
|
|
540
|
+
}): Promise<RetireExpiredResult>;
|
|
541
|
+
/** The store's registry, or a typed error naming the operation that needs one. */
|
|
542
|
+
private requireRegistry;
|
|
446
543
|
/**
|
|
447
544
|
* **Cross-tier DR consistency check (audit gap #11).** After a restore/failover, verify every registered
|
|
448
545
|
* segment's `currentGen` actually has its `.crbm` present in Cold — catching a **torn restore** where the
|
|
@@ -632,6 +729,6 @@ declare class Segment {
|
|
|
632
729
|
}
|
|
633
730
|
|
|
634
731
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
635
|
-
declare const VERSION = "0.
|
|
732
|
+
declare const VERSION = "0.9.0";
|
|
636
733
|
|
|
637
734
|
export { type BaseCombineOptions, CloudRoaring, type CloudRoaringOptions, type CombineIntoOptions, type CombineOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IntegrityError, bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, safeMetrics, NOOP_METRICS, BoundedLru, RetryingWarmDriver, RetryingColdChunkSource, ValidationError, resolveBudget, DEFAULT_BUDGET, SegmentEngine, UnsupportedError, validateSegmentRef, resolvePerOpBudget, splitId, collectWithinBudget, mapWithConcurrency, validateCompactionOptions, gcOrphanGenerations, dropSegment, runConsistencyCheck, estimateCost, groundedReport, CrbmColdChunkSource } from '@cloudbitmaps/core';
|
|
1
|
+
import { IntegrityError, bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, safeMetrics, NOOP_METRICS, BoundedLru, RetryingWarmDriver, RetryingColdChunkSource, ValidationError, resolveBudget, DEFAULT_BUDGET, SegmentEngine, UnsupportedError, validateSegmentRef, resolvePerOpBudget, splitId, collectWithinBudget, mapWithConcurrency, validateCompactionOptions, gcOrphanGenerations, dropSegment, setSegmentRetention, getSegmentRetention, clearSegmentRetention, retireExpired, runConsistencyCheck, estimateCost, groundedReport, CrbmColdChunkSource } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
import roaring from 'roaring';
|
|
4
4
|
|
|
@@ -305,12 +305,12 @@ var CloudRoaring = class {
|
|
|
305
305
|
compactionDeps() {
|
|
306
306
|
if (this.coldDriver === void 0) {
|
|
307
307
|
throw new UnsupportedError(
|
|
308
|
-
"compact/dropSegment/eraseSubject/checkConsistency need the store built with a raw cold driver (IColdDriver), not a pre-built ColdChunkSource \u2014 or call the compactSegment/runConsistencyCheck free functions with explicit deps"
|
|
308
|
+
"compact/dropSegment/retireExpired/eraseSubject/checkConsistency need the store built with a raw cold driver (IColdDriver), not a pre-built ColdChunkSource \u2014 or call the compactSegment/runConsistencyCheck free functions with explicit deps"
|
|
309
309
|
);
|
|
310
310
|
}
|
|
311
311
|
if (this.registry === void 0) {
|
|
312
312
|
throw new UnsupportedError(
|
|
313
|
-
"compact/eraseSubject/checkConsistency need a `registry` in the store config"
|
|
313
|
+
"compact/dropSegment/retireExpired/eraseSubject/checkConsistency need a `registry` in the store config"
|
|
314
314
|
);
|
|
315
315
|
}
|
|
316
316
|
return {
|
|
@@ -343,10 +343,7 @@ var CloudRoaring = class {
|
|
|
343
343
|
* (throws {@link UnsupportedError} otherwise).
|
|
344
344
|
*/
|
|
345
345
|
async subjectReport(id, options = {}) {
|
|
346
|
-
|
|
347
|
-
throw new UnsupportedError("subjectReport needs a `registry` in the store config");
|
|
348
|
-
}
|
|
349
|
-
const registry = this.registry;
|
|
346
|
+
const registry = this.requireRegistry("subjectReport");
|
|
350
347
|
requireScope(options, "subjectReport");
|
|
351
348
|
validateConcurrency(options.concurrency);
|
|
352
349
|
const budget = resolvePerOpBudget(options.budget, this.budget);
|
|
@@ -496,7 +493,12 @@ var CloudRoaring = class {
|
|
|
496
493
|
*
|
|
497
494
|
* **Omitting the namespace addresses a different segment and is a silent no-op** — you get
|
|
498
495
|
* `{ dropped: false, reason: 'absent' }`, not a throw, so a retention loop with that mistake deletes nothing
|
|
499
|
-
* forever and quietly.
|
|
496
|
+
* forever and quietly. Branch on `dropped`, and treat `reason: 'absent'` as the alert.
|
|
497
|
+
*
|
|
498
|
+
* **Works on an accumulator too.** A segment you created by writing to it — never bulk-loaded, never compacted,
|
|
499
|
+
* so it has no registry row and no Cold objects — is retired by deleting its Warm rows alone. That reports
|
|
500
|
+
* `{ dropped: true, reason: 'warm-only' }`: there is no tombstone to write, and deliberately none is written,
|
|
501
|
+
* because a `destroyed` row per retired daily bucket would be registry litter. The data really is gone.
|
|
500
502
|
*
|
|
501
503
|
* **Inspect `generationsRemaining`.** Empty is the normal outcome; non-empty means the storage was NOT fully
|
|
502
504
|
* reclaimed and the drop should be re-run. A compaction that was already in flight when the tombstone landed
|
|
@@ -517,6 +519,116 @@ var CloudRoaring = class {
|
|
|
517
519
|
const deps = this.compactionDeps();
|
|
518
520
|
return dropSegment(ref, { registry: deps.registry, warm: deps.warm, cold: deps.cold }, options);
|
|
519
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* **Record when this segment becomes eligible for retirement.** One registry write; nothing is deleted here,
|
|
524
|
+
* and nothing starts running. `retireExpired` is what acts on the policy, and **you** decide when that runs —
|
|
525
|
+
* an EventBridge rule, a CronJob, a queue consumer, whatever your deployment already has. This library starts
|
|
526
|
+
* no background thread (it has to work identically in a Lambda, an edge isolate and a long-lived server).
|
|
527
|
+
*
|
|
528
|
+
* `expiresAt` is an **absolute epoch-ms you compute**, not a duration the library derives. A relative TTL would
|
|
529
|
+
* have to be anchored to something the library knows — `updatedAt`, or the current generation — and compaction
|
|
530
|
+
* rewrites both, so "expire 30 days after the last write" would keep a busy daily bucket alive forever
|
|
531
|
+
* precisely because the daemon is working.
|
|
532
|
+
*
|
|
533
|
+
* ```ts
|
|
534
|
+
* const DAY = 86_400_000;
|
|
535
|
+
* const ref = { namespace: 'active-daily', segment: '2026-08-05' };
|
|
536
|
+
* await store.setRetention(ref, { expiresAt: Date.now() + 30 * DAY });
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* **Works on an accumulator**, which is the whole point: a segment you created by writing to it has no
|
|
540
|
+
* registry row, so it is invisible to `registry.list()` and therefore to every fleet-wide operation, including
|
|
541
|
+
* the sweep. This call mints the row (`createdRow: true` in the result) with **no Cold generation**, so it
|
|
542
|
+
* becomes enumerable while every read still resolves exactly as before.
|
|
543
|
+
*
|
|
544
|
+
* A value in the past is legal and means "eligible on the next sweep" — backfilling a policy onto existing
|
|
545
|
+
* buckets is normal. A value below `MIN_EXPIRES_AT_MS` (2001-09-09) is rejected: it is almost certainly epoch
|
|
546
|
+
* **seconds**, which would read as long-expired and retire the segment on the next pass. Needs a `registry`
|
|
547
|
+
* in the store config (throws {@link UnsupportedError} otherwise), and refuses a crypto-shredded segment.
|
|
548
|
+
*/
|
|
549
|
+
async setRetention(ref, policy) {
|
|
550
|
+
validateSegmentRef(ref);
|
|
551
|
+
return setSegmentRetention(ref, { registry: this.requireRegistry("setRetention") }, policy);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* **The stored retention policy**, or `null` if the segment has none (or has no registry row, or is a
|
|
555
|
+
* tombstone). Returns the string `'invalid'` for a row whose `expiresAt` is present but unusable — a
|
|
556
|
+
* hand-edited row, or one from a restore — so a malformed policy is visible rather than silently reading as
|
|
557
|
+
* "never expires" on a segment someone believes is expiring.
|
|
558
|
+
*/
|
|
559
|
+
async getRetention(ref) {
|
|
560
|
+
validateSegmentRef(ref);
|
|
561
|
+
return getSegmentRetention(ref, { registry: this.requireRegistry("getRetention") });
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* **Cancel a segment's expiry** so no sweep retires it. Returns whether a policy was actually removed (`false`
|
|
565
|
+
* when there was none). A separate verb from `setRetention` on purpose: "never expire" as a magic value passed
|
|
566
|
+
* to the setter is how a typo becomes a deletion.
|
|
567
|
+
*/
|
|
568
|
+
async clearRetention(ref) {
|
|
569
|
+
validateSegmentRef(ref);
|
|
570
|
+
return clearSegmentRetention(ref, { registry: this.requireRegistry("clearRetention") });
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* **Run the retention sweep: retire every segment whose `expiresAt` has passed.** This is the call that acts on
|
|
574
|
+
* the policies `setRetention` records — and it is a **call, not a daemon**. Nothing schedules it; you run it from
|
|
575
|
+
* whatever heartbeat your deployment already has (an EventBridge rule, a Kubernetes CronJob, a queue consumer,
|
|
576
|
+
* your compaction worker's loop). A library that started a timer would behave differently in a Lambda, an edge
|
|
577
|
+
* isolate and a long-lived server, which is worse than not having one.
|
|
578
|
+
*
|
|
579
|
+
* Each retirement goes through `dropSegment`, so the Warm → registry → Cold ordering, the re-sweep for a
|
|
580
|
+
* generation staged by an in-flight compaction, and the `generationsRemaining` report all come from one
|
|
581
|
+
* implementation rather than two.
|
|
582
|
+
*
|
|
583
|
+
* ```ts
|
|
584
|
+
* // In your scheduled handler. Start with a preview in a new deployment.
|
|
585
|
+
* const preview = await store.retireExpired({ namespace: 'active-daily', dryRun: true });
|
|
586
|
+
* console.log(`would retire ${preview.retired} of ${preview.scanned} (limited: ${preview.limited})`);
|
|
587
|
+
*
|
|
588
|
+
* const swept = await store.retireExpired({ namespace: 'active-daily' });
|
|
589
|
+
* for (const e of swept.entries) {
|
|
590
|
+
* if (e.action === 'skipped') console.warn(`${e.segment}: ${e.reason}`); // invalid-policy / limit / failed: …
|
|
591
|
+
* if (e.action === 'retired' && e.result.generationsRemaining.length > 0) {
|
|
592
|
+
* console.warn(`${e.segment}: storage not fully reclaimed — re-run`);
|
|
593
|
+
* }
|
|
594
|
+
* }
|
|
595
|
+
* if (swept.limited) scheduleAnotherPassSoon(); // more are still eligible
|
|
596
|
+
* ```
|
|
597
|
+
*
|
|
598
|
+
* **Read the ledger.** A per-segment *fault* is an entry, not an exception — a throw from the middle of a fleet
|
|
599
|
+
* sweep would leave the caller unable to say which segments were retired, having already retired some. (A bad
|
|
600
|
+
* argument does throw, and so does a fleet larger than `maxScanSegments`.) `limited: true` means the per-cycle
|
|
601
|
+
* `limit` (default 100) cut the pass short and more are eligible. That cap is charged on **attempts**, not
|
|
602
|
+
* successes, which is what makes it a real bound: `dropSegment` deletes Warm and writes the tombstone before
|
|
603
|
+
* sweeping Cold, so a fault in the Cold phase is a segment that is already retired. Retirements are sequential
|
|
604
|
+
* (~8 round trips each), so `limit` is a wall-clock knob too, and `retired` counts deletions only — a dry run
|
|
605
|
+
* reports `wouldRetire` instead, so a dashboard summing `retired` can never show a phantom deletion.
|
|
606
|
+
*
|
|
607
|
+
* It also **deletes the tombstone rows its own past retirements left**, after `tombstoneGraceMs` (default 24 h)
|
|
608
|
+
* and only once that segment's Warm rows and Cold generations are provably gone — collecting a straggler
|
|
609
|
+
* generation itself first, since nothing else ever would for a tombstoned segment. Attribution is a **positive
|
|
610
|
+
* marker the sweep stamps on its own retirements**, not an inference from "destroyed + an expired policy": a
|
|
611
|
+
* crypto-shred leaves `retention` untouched, so setting a policy and then honouring a right-to-erasure request
|
|
612
|
+
* mid-window produces exactly that row, and deleting it would destroy the Art. 17 attestation and un-fence the
|
|
613
|
+
* name. Pass `purgeTombstones: false` to keep every tombstone.
|
|
614
|
+
*
|
|
615
|
+
* Needs the store built with a **raw cold driver + a registry** (throws {@link UnsupportedError} otherwise),
|
|
616
|
+
* because retiring a segment deletes its Cold objects. `now` defaults to the store's clock.
|
|
617
|
+
*/
|
|
618
|
+
async retireExpired(options = {}) {
|
|
619
|
+
const deps = this.compactionDeps();
|
|
620
|
+
return retireExpired(
|
|
621
|
+
{ registry: deps.registry, warm: deps.warm, cold: deps.cold },
|
|
622
|
+
{ ...options, now: options.now ?? this.clock.now() }
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
/** The store's registry, or a typed error naming the operation that needs one. */
|
|
626
|
+
requireRegistry(op) {
|
|
627
|
+
if (this.registry === void 0) {
|
|
628
|
+
throw new UnsupportedError(`${op} needs a \`registry\` in the store config`);
|
|
629
|
+
}
|
|
630
|
+
return this.registry;
|
|
631
|
+
}
|
|
520
632
|
/**
|
|
521
633
|
* **Cross-tier DR consistency check (audit gap #11).** After a restore/failover, verify every registered
|
|
522
634
|
* segment's `currentGen` actually has its `.crbm` present in Cold — catching a **torn restore** where the
|
|
@@ -544,10 +656,8 @@ var CloudRoaring = class {
|
|
|
544
656
|
* exists" means the run finished — check `failed`. The `export-segments` CLI wraps this with a filesystem sink.
|
|
545
657
|
*/
|
|
546
658
|
async exportSegments(sink, options = {}) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
return runExport$1(this, this.registry, sink, {
|
|
659
|
+
const registry = this.requireRegistry("exportSegments");
|
|
660
|
+
return runExport$1(this, registry, sink, {
|
|
551
661
|
...options,
|
|
552
662
|
codec: options.codec ?? roaringCodec
|
|
553
663
|
});
|
|
@@ -758,7 +868,7 @@ var Segment = class {
|
|
|
758
868
|
});
|
|
759
869
|
}
|
|
760
870
|
};
|
|
761
|
-
var VERSION = "0.
|
|
871
|
+
var VERSION = "0.9.0";
|
|
762
872
|
|
|
763
873
|
export { CloudRoaring, SafeBitmap, Segment, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
764
874
|
//# sourceMappingURL=index.js.map
|