sliftutils 1.4.64 → 1.4.66

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.
@@ -109,5 +109,5 @@ async function main() {
109
109
  let duration = Date.now() - time;
110
110
  console.log(`Electron build completed in ${formatTime(duration)}`);
111
111
  }
112
- main().catch(console.error).finally(() => process.exit());
112
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
113
113
 
@@ -141,4 +141,4 @@ async function main() {
141
141
  let duration = Date.now() - time;
142
142
  console.log(`Extension build completed in ${formatTime(duration)}`);
143
143
  }
144
- main().catch(console.error).finally(() => process.exit());
144
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
@@ -26,4 +26,4 @@ async function main() {
26
26
  let duration = Date.now() - time;
27
27
  console.log(`NodeJS build completed in ${formatTime(duration)}`);
28
28
  }
29
- main().catch(console.error).finally(() => process.exit());
29
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
@@ -92,5 +92,5 @@ async function main() {
92
92
  console.log(`Web build completed in ${formatTime(duration)}`);
93
93
  console.log(`file://${path.resolve(indexHtmlOutput).replaceAll("\\", "/")}`);
94
94
  }
95
- main().catch(console.error).finally(() => process.exit());
95
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
96
96
 
@@ -42,4 +42,4 @@ async function main() {
42
42
  } catch { }
43
43
  }
44
44
  }
45
- main().catch(console.error).finally(() => process.exit());
45
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
package/index.d.ts CHANGED
@@ -936,6 +936,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
936
936
  streamFoldHardLimitBytes: number;
937
937
  writeFlushMaxDelayMs: number;
938
938
  fileSetPollIntervalMs: number;
939
+ deleteDeferMs: number;
939
940
  memoryFlushHeapBytes: number;
940
941
  memoryFlushMinCollectionBytes: number;
941
942
  memoryFlushThrottleMs: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.64",
3
+ "version": "1.4.66",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -12,6 +12,7 @@ export declare const bulkDatabase2Timing: {
12
12
  streamFoldHardLimitBytes: number;
13
13
  writeFlushMaxDelayMs: number;
14
14
  fileSetPollIntervalMs: number;
15
+ deleteDeferMs: number;
15
16
  memoryFlushHeapBytes: number;
16
17
  memoryFlushMinCollectionBytes: number;
17
18
  memoryFlushThrottleMs: number;
@@ -57,6 +57,12 @@ export const bulkDatabase2Timing = {
57
57
  // the whole stream file per write.
58
58
  writeFlushMaxDelayMs: isNode() ? 0 : 15 * 1000,
59
59
  fileSetPollIntervalMs: 30 * 60 * 1000,
60
+ // Defer deletion of a merge's consumed inputs by this long. The OS may not durably flush the new
61
+ // outputs immediately, so if we deleted the inputs and crashed before the writes settled we'd lose
62
+ // data. Within the window the inputs sit as duplicates of the outputs; if we never crash, the timer
63
+ // fires and removes them. If we DO crash before the timer, the duplicates stay until the next
64
+ // key-stratify pass (high duplicate-key fraction → it merges them again, dedup completes).
65
+ deleteDeferMs: 15 * 60 * 1000,
60
66
  memoryFlushHeapBytes: 1500 * 1024 * 1024,
61
67
  memoryFlushMinCollectionBytes: 100 * 1024 * 1024,
62
68
  memoryFlushThrottleMs: 15 * 60 * 1000,
@@ -628,7 +634,6 @@ export class BulkDatabaseBase<T extends { key: string }> {
628
634
  private async mergeFileSet(bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[], includesOldest = false, forceDeleteStreams = false): Promise<boolean> {
629
635
  const storage = await this.storage();
630
636
  const timestamp = nextFileTime();
631
- const now = Date.now();
632
637
 
633
638
  const consumedBulk: BulkFileInfo[] = [];
634
639
  const bulkReaders: BaseBulkDatabaseReader[] = [];
@@ -702,15 +707,30 @@ export class BulkDatabaseBase<T extends { key: string }> {
702
707
  for (const line of steps) console.log(line);
703
708
  console.groupEnd();
704
709
 
705
- const remove = async (name: string) => { try { await storage.remove(name); } catch { /* already gone */ } };
706
- for (const f of consumedBulk) await remove(f.fileName);
707
- for (const f of streamFiles) {
708
- if (await this.canDeleteStream(f, now, streamData.sizes, forceDeleteStreams)) await remove(f.fileName);
709
- }
710
-
711
- // File set changed — rebuild + swap. After the swap, consumed files' block-cache entries are
712
- // evicted (no reader will request them now).
710
+ // Rebuild + swap NOW so the index sees the new outputs; the old inputs are still on disk until
711
+ // the deferred-delete timer fires (the index briefly serves both, resolved by time so reads are
712
+ // correct). Block-cache eviction for inputs happens on the SECOND rebuild after deletion.
713
713
  await this.triggerRebuild();
714
+
715
+ // Deferred delete: give the FS time to durably flush new writes before removing the inputs.
716
+ // If we crash before the timer fires, the inputs survive as duplicates and a later key-stratify
717
+ // pass dedupes them. We DON'T await this — the merge returns "done" as soon as the new outputs
718
+ // are written and the index is swapped in.
719
+ const remove = async (name: string) => { try { await storage.remove(name); } catch { /* already gone */ } };
720
+ const timer = setTimeout(() => {
721
+ void (async () => {
722
+ try {
723
+ for (const f of consumedBulk) await remove(f.fileName);
724
+ for (const f of streamFiles) {
725
+ if (await this.canDeleteStream(f, Date.now(), streamData.sizes, forceDeleteStreams)) await remove(f.fileName);
726
+ }
727
+ await this.triggerRebuild();
728
+ } catch (e) {
729
+ console.warn(`${this.name}: deferred delete of consumed merge inputs failed: ${(e as Error).message}`);
730
+ }
731
+ })();
732
+ }, bulkDatabase2Timing.deleteDeferMs);
733
+ (timer as { unref?: () => void }).unref?.();
714
734
  return newNames.length > 0 || carriedDeletes > 0;
715
735
  }
716
736
 
package/test.ts CHANGED
@@ -7,4 +7,4 @@ async function main() {
7
7
  });
8
8
  console.log(test);
9
9
  }
10
- main().catch(console.error).finally(() => process.exit(0));
10
+ main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());