@cloudbitmaps/core 0.4.1 → 0.6.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.cjs CHANGED
@@ -2184,6 +2184,7 @@ function writeCrbmGeneration(driver, key, chunks, options = {}) {
2184
2184
  const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
2185
2185
  for (const { chunkKey, bitmap } of sorted) {
2186
2186
  if (bitmap.isEmpty) continue;
2187
+ bitmap.optimize?.();
2187
2188
  await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
2188
2189
  const pause = tick();
2189
2190
  if (pause !== null) await pause;
@@ -2199,6 +2200,7 @@ async function writeCrbmGenerationStream(driver, key, chunks, options = {}) {
2199
2200
  const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
2200
2201
  for await (const { chunkKey, bitmap } of chunks) {
2201
2202
  if (bitmap.isEmpty) continue;
2203
+ bitmap.optimize?.();
2202
2204
  await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
2203
2205
  chunkKeys.push(chunkKey);
2204
2206
  cardinality += bitmap.size;
@@ -2218,6 +2220,10 @@ async function publishGeneration(registry, key, options = {}) {
2218
2220
  currentGen: key.generation,
2219
2221
  wrappedDeks: options.wrappedDeks
2220
2222
  });
2223
+ } else if (record.status === "destroyed") {
2224
+ throw new ValidationError(
2225
+ `segment "${key.segment}" was destroyed (crypto-shredded) while generation ${key.generation} was being written \u2014 refusing to publish it; the written object is unreadable. Use a new segment.`
2226
+ );
2221
2227
  } else if (record.currentGen > key.generation) {
2222
2228
  return false;
2223
2229
  } else if (record.currentGen === key.generation) {
@@ -3983,7 +3989,22 @@ async function eraseNamespace(namespace, deps, options) {
3983
3989
  const destroyed = [];
3984
3990
  let segmentsShredded = 0;
3985
3991
  for (const ref of refs) {
3986
- const result = await shredSegment(ref, deps, options.allowCleartext ?? false);
3992
+ let result;
3993
+ try {
3994
+ result = await shredSegment(ref, deps, options.allowCleartext ?? false);
3995
+ } catch (err) {
3996
+ result = {
3997
+ segment: ref.segment,
3998
+ namespace: ref.namespace,
3999
+ destroyed: false,
4000
+ cryptoShredded: false,
4001
+ reason: isWriteConflictError(err) ? "contended" : `failed: ${err instanceof Error ? err.message : String(err)}`,
4002
+ // Deliberately 0, and not a partial count. `eraseWarm` may have deleted rows before it threw, but the
4003
+ // erase did not complete as a unit, so a partial figure on a failed entry would invite reading it as
4004
+ // progress toward a destruction that did not happen. The attestation that matters is `destroyed`.
4005
+ warmRowsDeleted: 0
4006
+ };
4007
+ }
3987
4008
  destroyed.push(result);
3988
4009
  if (result.cryptoShredded) {
3989
4010
  segmentsShredded += 1;
@@ -4046,12 +4067,13 @@ async function shredSegment(ref, deps, allowCleartext) {
4046
4067
  }
4047
4068
  async function eraseWarm(warm, ref) {
4048
4069
  let deleted = 0;
4070
+ let conflicts = 0;
4049
4071
  for (let pass = 0; pass < MAX_WARM_PASSES; pass++) {
4072
+ conflicts = 0;
4050
4073
  const rows = [];
4051
4074
  for await (const row of warm.listChunks(ref))
4052
4075
  rows.push({ chunkKey: row.chunkKey, token: row.token });
4053
4076
  if (rows.length === 0) break;
4054
- let conflicts = 0;
4055
4077
  for (const { chunkKey, token } of rows) {
4056
4078
  const chunkRef = { namespace: ref.namespace, segment: ref.segment, chunkKey };
4057
4079
  try {
@@ -4064,6 +4086,11 @@ async function eraseWarm(warm, ref) {
4064
4086
  }
4065
4087
  if (conflicts === 0) break;
4066
4088
  }
4089
+ if (conflicts > 0) {
4090
+ throw new WriteConflictError(
4091
+ `destroySegment: ${conflicts} warm row(s) of "${ref.segment}" were rewritten during every one of ${MAX_WARM_PASSES} erase passes \u2014 the segment was NOT destroyed and its cleartext warm deltas are still readable. Stop writes to this segment and retry.`
4092
+ );
4093
+ }
4067
4094
  return deleted;
4068
4095
  }
4069
4096
 
@@ -4194,11 +4221,15 @@ var RetryingWarmDriver = class {
4194
4221
  this.deps
4195
4222
  );
4196
4223
  if (started.first.done === true) return;
4197
- yield started.first.value;
4198
- for (; ; ) {
4199
- const next = await started.iterator.next();
4200
- if (next.done === true) return;
4201
- yield next.value;
4224
+ try {
4225
+ yield started.first.value;
4226
+ for (; ; ) {
4227
+ const next = await started.iterator.next();
4228
+ if (next.done === true) return;
4229
+ yield next.value;
4230
+ }
4231
+ } finally {
4232
+ await started.iterator.return?.();
4202
4233
  }
4203
4234
  }
4204
4235
  };