sliftutils 1.4.66 → 1.4.67

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/index.d.ts CHANGED
@@ -853,6 +853,13 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
853
853
  isFieldLoadedSync<Column extends keyof T>(key: string, column: Column): boolean;
854
854
  /** Reactive: whether a whole column is loaded yet (see isFieldLoadedSync). */
855
855
  isColumnLoadedSync<Column extends keyof T>(column: Column): boolean;
856
+ /**
857
+ * Reactive: true while a merge is rewriting this collection's files (background `maybeMerge` or
858
+ * an explicit `compact`/`merge`/`tryMergeNow`). Becomes false as soon as the new index is swapped
859
+ * in — the deferred-delete cleanup window is NOT counted. Use this in a UI to show a per-database
860
+ * "compacting…" indicator.
861
+ */
862
+ isCompactingSync(): boolean;
856
863
  /**
857
864
  * Whether a row (key) is currently being watched by some reactive observer (getSingleFieldObjSync /
858
865
  * getSingleFieldSync). Lets callers skip per-row work when nothing's watching. Non-reactive query;
@@ -1030,6 +1037,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1030
1037
  private fileLogicalSize;
1031
1038
  private handleUnreadableFile;
1032
1039
  private mergeFileSet;
1040
+ private mergeFileSetInner;
1033
1041
  private canDeleteStream;
1034
1042
  private mergeSpacingDelay;
1035
1043
  private testMerge;
@@ -1059,6 +1067,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1059
1067
  }[] | undefined;
1060
1068
  isFieldLoadedSync<C extends keyof T>(key: string, column: C): boolean;
1061
1069
  isColumnLoadedSync<C extends keyof T>(column: C): boolean;
1070
+ isCompactingSync(): boolean;
1062
1071
  getColumnInfo(): Promise<{
1063
1072
  column: string;
1064
1073
  byteSize: number;
@@ -1279,6 +1288,10 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseReader" {
1279
1288
  isKeyWatched(key: string): boolean;
1280
1289
  isLiveNow(key: string): boolean;
1281
1290
  localTime(key: string): number;
1291
+ private compactingCount;
1292
+ beginCompaction(): void;
1293
+ endCompaction(): void;
1294
+ isCompactingSync(): boolean;
1282
1295
  private notifyOverlayMutation;
1283
1296
  getKeys(): Promise<string[]>;
1284
1297
  getColumn<C extends keyof T>(column: C): Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.66",
3
+ "version": "1.4.67",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -88,6 +88,13 @@ export interface IBulkDatabase2<T extends {
88
88
  isFieldLoadedSync<Column extends keyof T>(key: string, column: Column): boolean;
89
89
  /** Reactive: whether a whole column is loaded yet (see isFieldLoadedSync). */
90
90
  isColumnLoadedSync<Column extends keyof T>(column: Column): boolean;
91
+ /**
92
+ * Reactive: true while a merge is rewriting this collection's files (background `maybeMerge` or
93
+ * an explicit `compact`/`merge`/`tryMergeNow`). Becomes false as soon as the new index is swapped
94
+ * in — the deferred-delete cleanup window is NOT counted. Use this in a UI to show a per-database
95
+ * "compacting…" indicator.
96
+ */
97
+ isCompactingSync(): boolean;
91
98
  /**
92
99
  * Whether a row (key) is currently being watched by some reactive observer (getSingleFieldObjSync /
93
100
  * getSingleFieldSync). Lets callers skip per-row work when nothing's watching. Non-reactive query;
@@ -74,6 +74,14 @@ export interface IBulkDatabase2<T extends { key: string }> {
74
74
  /** Reactive: whether a whole column is loaded yet (see isFieldLoadedSync). */
75
75
  isColumnLoadedSync<Column extends keyof T>(column: Column): boolean;
76
76
 
77
+ /**
78
+ * Reactive: true while a merge is rewriting this collection's files (background `maybeMerge` or
79
+ * an explicit `compact`/`merge`/`tryMergeNow`). Becomes false as soon as the new index is swapped
80
+ * in — the deferred-delete cleanup window is NOT counted. Use this in a UI to show a per-database
81
+ * "compacting…" indicator.
82
+ */
83
+ isCompactingSync(): boolean;
84
+
77
85
  /**
78
86
  * Whether a row (key) is currently being watched by some reactive observer (getSingleFieldObjSync /
79
87
  * getSingleFieldSync). Lets callers skip per-row work when nothing's watching. Non-reactive query;
@@ -106,6 +106,7 @@ export declare class BulkDatabaseBase<T extends {
106
106
  private fileLogicalSize;
107
107
  private handleUnreadableFile;
108
108
  private mergeFileSet;
109
+ private mergeFileSetInner;
109
110
  private canDeleteStream;
110
111
  private mergeSpacingDelay;
111
112
  private testMerge;
@@ -135,6 +136,7 @@ export declare class BulkDatabaseBase<T extends {
135
136
  }[] | undefined;
136
137
  isFieldLoadedSync<C extends keyof T>(key: string, column: C): boolean;
137
138
  isColumnLoadedSync<C extends keyof T>(column: C): boolean;
139
+ isCompactingSync(): boolean;
138
140
  getColumnInfo(): Promise<{
139
141
  column: string;
140
142
  byteSize: number;
@@ -632,6 +632,15 @@ export class BulkDatabaseBase<T extends { key: string }> {
632
632
  // we trigger an index rebuild + atomic swap; once swap completes, the consumed files' block-cache
633
633
  // entries are evicted (no consumer can ask for them now).
634
634
  private async mergeFileSet(bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[], includesOldest = false, forceDeleteStreams = false): Promise<boolean> {
635
+ this.reader.beginCompaction();
636
+ try {
637
+ return await this.mergeFileSetInner(bulkFiles, streamFiles, includesOldest, forceDeleteStreams);
638
+ } finally {
639
+ this.reader.endCompaction();
640
+ }
641
+ }
642
+
643
+ private async mergeFileSetInner(bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[], includesOldest: boolean, forceDeleteStreams: boolean): Promise<boolean> {
635
644
  const storage = await this.storage();
636
645
  const timestamp = nextFileTime();
637
646
 
@@ -932,6 +941,13 @@ export class BulkDatabaseBase<T extends { key: string }> {
932
941
  return this.reader.isColumnLoadedSync(column);
933
942
  }
934
943
 
944
+ // Reactive: true while a merge is rewriting this collection's files. Use this in UI to show a
945
+ // "compacting…" indicator. Counts both background merges (maybeMerge) and explicit compact/merge
946
+ // calls. Becomes false once the new index is swapped in (the deferred-delete window is NOT counted).
947
+ public isCompactingSync(): boolean {
948
+ return this.reader.isCompactingSync();
949
+ }
950
+
935
951
  public async getColumnInfo() {
936
952
  const index = await this.ensureIndex();
937
953
  return index.reader.columns;
@@ -31,6 +31,10 @@ export declare class BulkDatabaseReader<T extends {
31
31
  isKeyWatched(key: string): boolean;
32
32
  isLiveNow(key: string): boolean;
33
33
  localTime(key: string): number;
34
+ private compactingCount;
35
+ beginCompaction(): void;
36
+ endCompaction(): void;
37
+ isCompactingSync(): boolean;
34
38
  private notifyOverlayMutation;
35
39
  getKeys(): Promise<string[]>;
36
40
  getColumn<C extends keyof T>(column: C): Promise<{
@@ -7,6 +7,7 @@ import { blue, red } from "socket-function/src/formatting/logColors";
7
7
  const NULL = String.fromCharCode(0);
8
8
  const LOAD_SIGNAL = NULL + "load";
9
9
  const OVERLAY_SIGNAL = NULL + "overlay";
10
+ const COMPACTING_SIGNAL = NULL + "compacting";
10
11
  const TRIGGER_THROTTLE_FIRST_STEP_MS = 16;
11
12
 
12
13
  function nullJoin(a: string, b: string): string {
@@ -88,6 +89,22 @@ export class BulkDatabaseReader<T extends { key: string }> {
88
89
  return -Infinity;
89
90
  }
90
91
 
92
+ // Counter (so concurrent / nested merges in the same instance compose correctly). Signal fires
93
+ // immediately, not through the trigger throttle — UI spinners should show right away.
94
+ private compactingCount = 0;
95
+ beginCompaction(): void {
96
+ this.compactingCount++;
97
+ if (this.compactingCount === 1) this.cfg.deps.invalidate(COMPACTING_SIGNAL);
98
+ }
99
+ endCompaction(): void {
100
+ this.compactingCount--;
101
+ if (this.compactingCount === 0) this.cfg.deps.invalidate(COMPACTING_SIGNAL);
102
+ }
103
+ isCompactingSync(): boolean {
104
+ this.cfg.deps.observe(COMPACTING_SIGNAL);
105
+ return this.compactingCount > 0;
106
+ }
107
+
91
108
  private notifyOverlayMutation(key: string, columns: Iterable<string> | "all"): void {
92
109
  this.dataGen++;
93
110
  if (columns === "all") this.columnCache.clear();