sliftutils 1.7.120 → 1.7.122

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
@@ -921,9 +921,9 @@ declare module "sliftutils/storage/ArchivesDisk" {
921
921
  }
922
922
 
923
923
  declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
924
- import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult } from "./BulkDatabaseBase";
924
+ import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult, CompactionPlan } from "./BulkDatabaseBase";
925
925
  export { BulkDatabaseBase, noopReactiveDeps, bulkDatabase2Timing } from "./BulkDatabaseBase";
926
- export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason } from "./BulkDatabaseBase";
926
+ export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason, CompactionPlan, CompactionStep, CompactionStepKind, CompactionTrigger } from "./BulkDatabaseBase";
927
927
  /** Per-column on-disk size info, as reported by getColumnInfo/getReaderInfo. */
928
928
  export type BulkColumnInfo = {
929
929
  column: string;
@@ -1039,6 +1039,18 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
1039
1039
  * size/fragmentation and deciding whether to call tryMergeNow()/compact().
1040
1040
  */
1041
1041
  getFileInfo(): Promise<BulkFileInfoListing>;
1042
+ /**
1043
+ * Every compaction the files on disk currently call for, without performing any of them. This is the
1044
+ * same plan the background merge pass builds and then executes, so it says exactly what the database
1045
+ * is about to do, to which files, and (via `startTime`) when.
1046
+ *
1047
+ * A step that isn't `ready` is still listed with its `triggers`, so you can see how close it is: each
1048
+ * trigger carries the current `value`, the `threshold` that sets the step off, and their `fraction`.
1049
+ *
1050
+ * Not free: working out the dedup steps walks the key list of every combined file, so this is O(total
1051
+ * keys). Fine to call when showing collection status, not something to poll on a short timer.
1052
+ */
1053
+ planCompaction(): Promise<CompactionPlan>;
1042
1054
  /**
1043
1055
  * Consolidate on-disk files. Optional to call; the database also does this in the background.
1044
1056
  * Returns whether anything was merged, or (via skipReason) why the pass never ran — another merge
@@ -1090,16 +1102,17 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
1090
1102
 
1091
1103
  declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1092
1104
  import type { FileStorage } from "../FileFolderAPI";
1105
+ import { BulkFileInfo, StreamFileInfo } from "./LoadedIndex";
1093
1106
  export declare const BULK_ROOT_FOLDER = "bulkDatabases2";
1094
1107
  export declare const bulkDatabase2Timing: {
1095
1108
  streamSealAgeMs: number;
1096
1109
  visibleMergeIntervalMs: number;
1097
1110
  mergeSpacingMs: number;
1098
- firstMergeTriggerFiles: number;
1099
- firstMergeTriggerRangeMs: number;
1100
- streamFoldTriggerRows: number;
1111
+ looseBulkTriggerBytes: number;
1112
+ looseBulkTriggerFiles: number;
1101
1113
  streamFoldTriggerBytes: number;
1102
1114
  streamFileMaxBytes: number;
1115
+ liveWriterProbeMs: number;
1103
1116
  streamFoldHardLimitBytes: number;
1104
1117
  writeFlushMaxDelayMs: number;
1105
1118
  fileSetPollIntervalMs: number;
@@ -1125,6 +1138,46 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1125
1138
  lockHolderId?: string;
1126
1139
  lockExpiresInMs?: number;
1127
1140
  };
1141
+ /** One threshold a compaction step is measured against. `value` is where the collection stands now and `threshold` is what sets the step off, so `fraction` (value/threshold) reads as how close it is - 1 or more means met. Deliberately not clamped, so an overdue step reads as how far past due it is. */
1142
+ export type CompactionTrigger = {
1143
+ name: string;
1144
+ value: number;
1145
+ threshold: number;
1146
+ fraction: number;
1147
+ met: boolean;
1148
+ /** How to render value/threshold. "fraction" values are 0..1. */
1149
+ unit: "bytes" | "count" | "fraction";
1150
+ };
1151
+ /** streamHardLimit and streamFold are phase 1 (stream -> bulk), looseCombine is phase 2 (loose bulk -> combined bulk), dedupAll and dedupKeyGroup are phase 3. */
1152
+ export type CompactionStepKind = "streamHardLimit" | "streamFold" | "looseCombine" | "dedupAll" | "dedupKeyGroup";
1153
+ export type CompactionStep = {
1154
+ phase: 1 | 2 | 3;
1155
+ kind: CompactionStepKind;
1156
+ /** Whether this step runs on the next pass. Authoritative: on top of `requires` it accounts for inputs the step needs beyond its thresholds (e.g. two files to combine), so it can be false even with every trigger met. */
1157
+ ready: boolean;
1158
+ /** Whether every trigger has to be met for this step, or just one of them. */
1159
+ requires: "any" | "all";
1160
+ triggers: CompactionTrigger[];
1161
+ /** When this step's merge starts, given merges are spaced mergeSpacingMs apart. Only set when ready. */
1162
+ startTime?: number;
1163
+ /** The files this step consumes, as of when the plan was made. */
1164
+ bulkFiles: BulkFileInfo[];
1165
+ streamFiles: StreamFileInfo[];
1166
+ /** Total size of those inputs. */
1167
+ bytes: number;
1168
+ /** dedupKeyGroup only - the key range the step rewrites. */
1169
+ keyRange?: {
1170
+ lo: string;
1171
+ hi: string;
1172
+ };
1173
+ };
1174
+ /** Every compaction the current file set calls for, in the order a merge pass runs them, plus how close each not-yet-ready one is to its thresholds. */
1175
+ export type CompactionPlan = {
1176
+ collection: string;
1177
+ /** When the plan was computed; every startTime is measured from here. */
1178
+ time: number;
1179
+ steps: CompactionStep[];
1180
+ };
1128
1181
  export declare class BulkDatabaseBase<T extends {
1129
1182
  key: string;
1130
1183
  }> {
@@ -1149,7 +1202,6 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1149
1202
  private currentStreamFileBytes;
1150
1203
  private mergeInFlight;
1151
1204
  private lastMergeSkipLogMs;
1152
- private streamRowsOnDisk;
1153
1205
  private streamBytesOnDisk;
1154
1206
  private fileSetPollTimer;
1155
1207
  private rebuildPromise;
@@ -1169,6 +1221,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1169
1221
  };
1170
1222
  isRemote(): Promise<boolean>;
1171
1223
  private streamNeedsFold;
1224
+ private findAbandonedStreams;
1172
1225
  private automaticCompactionAllowed;
1173
1226
  isKeyWatched(key: string): boolean;
1174
1227
  private ensureIndex;
@@ -1212,8 +1265,19 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
1212
1265
  private mergeFileSetInner;
1213
1266
  private canDeleteStream;
1214
1267
  private mergeSpacingDelay;
1268
+ private splitBulkTier;
1269
+ private analyzeDuplicates;
1270
+ private filesForKeyRange;
1271
+ /**
1272
+ * Every compaction the files on disk currently call for, without performing any of them. A merge pass
1273
+ * builds exactly this and then runs the steps whose `ready` is true, so the plan is precisely what the
1274
+ * database is about to do — and a step that isn't ready still reports its `triggers`, so a caller can
1275
+ * see how close it is (50MB of stream data out of the 64MB that would fold it, and so on).
1276
+ *
1277
+ * O(total keys): the phase 3 steps need every combined file's key list walked.
1278
+ */
1279
+ planCompaction(): Promise<CompactionPlan>;
1215
1280
  private testMergeINTERNAL_DO_NOT_CALL;
1216
- private findDuplicateGroups;
1217
1281
  getSingleField<C extends keyof T>(key: string, column: C): Promise<T[C] | undefined>;
1218
1282
  getSingleFieldObj<C extends keyof T>(key: string, column: C): Promise<{
1219
1283
  key: string;
@@ -1521,6 +1585,7 @@ declare module "sliftutils/storage/BulkDatabase2/LoadedIndex" {
1521
1585
  export type StreamFileInfo = {
1522
1586
  fileName: string;
1523
1587
  timestamp: number;
1588
+ ownerId?: string;
1524
1589
  };
1525
1590
  export type StreamReaderCacheEntry = {
1526
1591
  readSize: number;
@@ -1772,9 +1837,11 @@ declare module "sliftutils/storage/BulkDatabase2/syncClient" {
1772
1837
  deleted?: boolean;
1773
1838
  value?: unknown;
1774
1839
  };
1840
+ export declare function registerWriterId(id: string): void;
1775
1841
  export declare function isSyncSupported(): boolean;
1776
1842
  export declare function connect(collection: string, onWrite: (write: RemoteWrite) => void, onSeal?: () => void): Promise<RemoteWrite[]>;
1777
1843
  export declare function broadcast(collection: string, write: RemoteWrite): void;
1844
+ export declare function queryLiveWriters(collection: string, timeoutMs: number): Promise<Set<string> | undefined>;
1778
1845
  export declare function broadcastSeal(collection: string): void;
1779
1846
 
1780
1847
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.7.120",
3
+ "version": "1.7.122",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -57,7 +57,7 @@
57
57
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
58
58
  "preact-old-types": "^10.28.1",
59
59
  "shell-quote": "^1.8.3",
60
- "socket-function": "^1.2.33",
60
+ "socket-function": "^1.2.34",
61
61
  "typenode": "^6.6.1",
62
62
  "typesafecss": "*",
63
63
  "ws": "^8.18.3",
@@ -1,6 +1,6 @@
1
- import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult } from "./BulkDatabaseBase";
1
+ import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult, CompactionPlan } from "./BulkDatabaseBase";
2
2
  export { BulkDatabaseBase, noopReactiveDeps, bulkDatabase2Timing } from "./BulkDatabaseBase";
3
- export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason } from "./BulkDatabaseBase";
3
+ export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason, CompactionPlan, CompactionStep, CompactionStepKind, CompactionTrigger } from "./BulkDatabaseBase";
4
4
  /** Per-column on-disk size info, as reported by getColumnInfo/getReaderInfo. */
5
5
  export type BulkColumnInfo = {
6
6
  column: string;
@@ -116,6 +116,18 @@ export interface IBulkDatabase2<T extends {
116
116
  * size/fragmentation and deciding whether to call tryMergeNow()/compact().
117
117
  */
118
118
  getFileInfo(): Promise<BulkFileInfoListing>;
119
+ /**
120
+ * Every compaction the files on disk currently call for, without performing any of them. This is the
121
+ * same plan the background merge pass builds and then executes, so it says exactly what the database
122
+ * is about to do, to which files, and (via `startTime`) when.
123
+ *
124
+ * A step that isn't `ready` is still listed with its `triggers`, so you can see how close it is: each
125
+ * trigger carries the current `value`, the `threshold` that sets the step off, and their `fraction`.
126
+ *
127
+ * Not free: working out the dedup steps walks the key list of every combined file, so this is O(total
128
+ * keys). Fine to call when showing collection status, not something to poll on a short timer.
129
+ */
130
+ planCompaction(): Promise<CompactionPlan>;
119
131
  /**
120
132
  * Consolidate on-disk files. Optional to call; the database also does this in the background.
121
133
  * Returns whether anything was merged, or (via skipReason) why the pass never ran — another merge
@@ -1,9 +1,9 @@
1
1
  import { getFileStorageNested2 } from "../FileFolderAPI";
2
2
  import { observable, runInAction, onBecomeObserved, onBecomeUnobserved } from "../../render-utils/mobxTyped";
3
- import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult } from "./BulkDatabaseBase";
3
+ import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult, CompactionPlan } from "./BulkDatabaseBase";
4
4
 
5
5
  export { BulkDatabaseBase, noopReactiveDeps, bulkDatabase2Timing } from "./BulkDatabaseBase";
6
- export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason } from "./BulkDatabaseBase";
6
+ export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason, CompactionPlan, CompactionStep, CompactionStepKind, CompactionTrigger } from "./BulkDatabaseBase";
7
7
 
8
8
  /** Per-column on-disk size info, as reported by getColumnInfo/getReaderInfo. */
9
9
  export type BulkColumnInfo = { column: string; byteSize: number };
@@ -106,6 +106,19 @@ export interface IBulkDatabase2<T extends { key: string }> {
106
106
  */
107
107
  getFileInfo(): Promise<BulkFileInfoListing>;
108
108
 
109
+ /**
110
+ * Every compaction the files on disk currently call for, without performing any of them. This is the
111
+ * same plan the background merge pass builds and then executes, so it says exactly what the database
112
+ * is about to do, to which files, and (via `startTime`) when.
113
+ *
114
+ * A step that isn't `ready` is still listed with its `triggers`, so you can see how close it is: each
115
+ * trigger carries the current `value`, the `threshold` that sets the step off, and their `fraction`.
116
+ *
117
+ * Not free: working out the dedup steps walks the key list of every combined file, so this is O(total
118
+ * keys). Fine to call when showing collection status, not something to poll on a short timer.
119
+ */
120
+ planCompaction(): Promise<CompactionPlan>;
121
+
109
122
  /**
110
123
  * Consolidate on-disk files. Optional to call; the database also does this in the background.
111
124
  * Returns whether anything was merged, or (via skipReason) why the pass never ran — another merge
@@ -1,14 +1,15 @@
1
1
  import type { FileStorage } from "../FileFolderAPI";
2
+ import { BulkFileInfo, StreamFileInfo } from "./LoadedIndex";
2
3
  export declare const BULK_ROOT_FOLDER = "bulkDatabases2";
3
4
  export declare const bulkDatabase2Timing: {
4
5
  streamSealAgeMs: number;
5
6
  visibleMergeIntervalMs: number;
6
7
  mergeSpacingMs: number;
7
- firstMergeTriggerFiles: number;
8
- firstMergeTriggerRangeMs: number;
9
- streamFoldTriggerRows: number;
8
+ looseBulkTriggerBytes: number;
9
+ looseBulkTriggerFiles: number;
10
10
  streamFoldTriggerBytes: number;
11
11
  streamFileMaxBytes: number;
12
+ liveWriterProbeMs: number;
12
13
  streamFoldHardLimitBytes: number;
13
14
  writeFlushMaxDelayMs: number;
14
15
  fileSetPollIntervalMs: number;
@@ -34,6 +35,46 @@ export type MergeAttemptResult = {
34
35
  lockHolderId?: string;
35
36
  lockExpiresInMs?: number;
36
37
  };
38
+ /** One threshold a compaction step is measured against. `value` is where the collection stands now and `threshold` is what sets the step off, so `fraction` (value/threshold) reads as how close it is - 1 or more means met. Deliberately not clamped, so an overdue step reads as how far past due it is. */
39
+ export type CompactionTrigger = {
40
+ name: string;
41
+ value: number;
42
+ threshold: number;
43
+ fraction: number;
44
+ met: boolean;
45
+ /** How to render value/threshold. "fraction" values are 0..1. */
46
+ unit: "bytes" | "count" | "fraction";
47
+ };
48
+ /** streamHardLimit and streamFold are phase 1 (stream -> bulk), looseCombine is phase 2 (loose bulk -> combined bulk), dedupAll and dedupKeyGroup are phase 3. */
49
+ export type CompactionStepKind = "streamHardLimit" | "streamFold" | "looseCombine" | "dedupAll" | "dedupKeyGroup";
50
+ export type CompactionStep = {
51
+ phase: 1 | 2 | 3;
52
+ kind: CompactionStepKind;
53
+ /** Whether this step runs on the next pass. Authoritative: on top of `requires` it accounts for inputs the step needs beyond its thresholds (e.g. two files to combine), so it can be false even with every trigger met. */
54
+ ready: boolean;
55
+ /** Whether every trigger has to be met for this step, or just one of them. */
56
+ requires: "any" | "all";
57
+ triggers: CompactionTrigger[];
58
+ /** When this step's merge starts, given merges are spaced mergeSpacingMs apart. Only set when ready. */
59
+ startTime?: number;
60
+ /** The files this step consumes, as of when the plan was made. */
61
+ bulkFiles: BulkFileInfo[];
62
+ streamFiles: StreamFileInfo[];
63
+ /** Total size of those inputs. */
64
+ bytes: number;
65
+ /** dedupKeyGroup only - the key range the step rewrites. */
66
+ keyRange?: {
67
+ lo: string;
68
+ hi: string;
69
+ };
70
+ };
71
+ /** Every compaction the current file set calls for, in the order a merge pass runs them, plus how close each not-yet-ready one is to its thresholds. */
72
+ export type CompactionPlan = {
73
+ collection: string;
74
+ /** When the plan was computed; every startTime is measured from here. */
75
+ time: number;
76
+ steps: CompactionStep[];
77
+ };
37
78
  export declare class BulkDatabaseBase<T extends {
38
79
  key: string;
39
80
  }> {
@@ -58,7 +99,6 @@ export declare class BulkDatabaseBase<T extends {
58
99
  private currentStreamFileBytes;
59
100
  private mergeInFlight;
60
101
  private lastMergeSkipLogMs;
61
- private streamRowsOnDisk;
62
102
  private streamBytesOnDisk;
63
103
  private fileSetPollTimer;
64
104
  private rebuildPromise;
@@ -78,6 +118,7 @@ export declare class BulkDatabaseBase<T extends {
78
118
  };
79
119
  isRemote(): Promise<boolean>;
80
120
  private streamNeedsFold;
121
+ private findAbandonedStreams;
81
122
  private automaticCompactionAllowed;
82
123
  isKeyWatched(key: string): boolean;
83
124
  private ensureIndex;
@@ -121,8 +162,19 @@ export declare class BulkDatabaseBase<T extends {
121
162
  private mergeFileSetInner;
122
163
  private canDeleteStream;
123
164
  private mergeSpacingDelay;
165
+ private splitBulkTier;
166
+ private analyzeDuplicates;
167
+ private filesForKeyRange;
168
+ /**
169
+ * Every compaction the files on disk currently call for, without performing any of them. A merge pass
170
+ * builds exactly this and then runs the steps whose `ready` is true, so the plan is precisely what the
171
+ * database is about to do — and a step that isn't ready still reports its `triggers`, so a caller can
172
+ * see how close it is (50MB of stream data out of the 64MB that would fold it, and so on).
173
+ *
174
+ * O(total keys): the phase 3 steps need every combined file's key list walked.
175
+ */
176
+ planCompaction(): Promise<CompactionPlan>;
124
177
  private testMergeINTERNAL_DO_NOT_CALL;
125
- private findDuplicateGroups;
126
178
  getSingleField<C extends keyof T>(key: string, column: C): Promise<T[C] | undefined>;
127
179
  getSingleFieldObj<C extends keyof T>(key: string, column: C): Promise<{
128
180
  key: string;