sliftutils 1.4.67 → 1.4.69
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 +11 -11
- package/package.json +1 -1
- package/storage/BulkDatabase2/BulkDatabase2.d.ts +1 -4
- package/storage/BulkDatabase2/BulkDatabase2.ts +1 -1
- package/storage/BulkDatabase2/BulkDatabaseBase.d.ts +5 -7
- package/storage/BulkDatabase2/BulkDatabaseBase.ts +146 -19
- package/storage/BulkDatabase2/BulkDatabaseMerge.d.ts +1 -0
- package/storage/BulkDatabase2/BulkDatabaseMerge.ts +36 -18
- package/storage/BulkDatabase2/mergeLock.d.ts +4 -0
- package/storage/BulkDatabase2/mergeLock.ts +62 -0
- package/storage/remoteFileServer.ts +172 -173
package/index.d.ts
CHANGED
|
@@ -903,10 +903,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
|
|
|
903
903
|
* whether it bailed because another tab/process holds the merge lock — so a scheduler can call this
|
|
904
904
|
* (e.g. every 30 minutes) and tell "nothing to do" from "someone else is already merging".
|
|
905
905
|
*/
|
|
906
|
-
tryMergeNow(): Promise<
|
|
907
|
-
merged: boolean;
|
|
908
|
-
lockFailed: boolean;
|
|
909
|
-
}>;
|
|
906
|
+
tryMergeNow(): Promise<void>;
|
|
910
907
|
/** Rewrite everything written in [timeLo, timeHi] into fresh key-sorted bulk file(s). Low-level;
|
|
911
908
|
* most callers want compact() or tryMergeNow(). */
|
|
912
909
|
merge(timeLo: number, timeHi: number): Promise<void>;
|
|
@@ -933,7 +930,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
933
930
|
export declare const BULK_ROOT_FOLDER = "bulkDatabases2";
|
|
934
931
|
export declare const bulkDatabase2Timing: {
|
|
935
932
|
streamSealAgeMs: number;
|
|
936
|
-
|
|
933
|
+
visibleMergeIntervalMs: number;
|
|
937
934
|
mergeSpacingMs: number;
|
|
938
935
|
firstMergeTriggerFiles: number;
|
|
939
936
|
firstMergeTriggerRangeMs: number;
|
|
@@ -967,6 +964,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
967
964
|
private storageFactory;
|
|
968
965
|
private config;
|
|
969
966
|
constructor(name: string, deps: ReactiveDeps, storageFactory: StorageFactory, config?: BulkDatabase2Config);
|
|
967
|
+
private setupVisibilityMergeCheck;
|
|
970
968
|
private reader;
|
|
971
969
|
private subCaches;
|
|
972
970
|
private pendingAppends;
|
|
@@ -977,7 +975,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
977
975
|
private streamFileName;
|
|
978
976
|
private currentStreamFileName;
|
|
979
977
|
private currentStreamFileBytes;
|
|
980
|
-
private
|
|
978
|
+
private mergeInFlight;
|
|
981
979
|
private streamRowsOnDisk;
|
|
982
980
|
private streamBytesOnDisk;
|
|
983
981
|
private fileSetPollTimer;
|
|
@@ -1027,10 +1025,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
1027
1025
|
private listFiles;
|
|
1028
1026
|
private writeBulkFile;
|
|
1029
1027
|
private maybeMerge;
|
|
1030
|
-
tryMergeNow()
|
|
1031
|
-
merged: boolean;
|
|
1032
|
-
lockFailed: boolean;
|
|
1033
|
-
}>;
|
|
1028
|
+
tryMergeNow: () => Promise<void>;
|
|
1034
1029
|
compact(): Promise<void>;
|
|
1035
1030
|
merge(timeLo: number, timeHi: number): Promise<void>;
|
|
1036
1031
|
private readBulkHeader;
|
|
@@ -1040,7 +1035,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
1040
1035
|
private mergeFileSetInner;
|
|
1041
1036
|
private canDeleteStream;
|
|
1042
1037
|
private mergeSpacingDelay;
|
|
1043
|
-
private
|
|
1038
|
+
private testMergeINTERNAL_DO_NOT_CALL;
|
|
1044
1039
|
private findDuplicateGroups;
|
|
1045
1040
|
getSingleField<C extends keyof T>(key: string, column: C): Promise<T[C] | undefined>;
|
|
1046
1041
|
getSingleFieldObj<C extends keyof T>(key: string, column: C): Promise<{
|
|
@@ -1249,6 +1244,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseMerge" {
|
|
|
1249
1244
|
}): Promise<{
|
|
1250
1245
|
outputs: PlannedMergeOutput[];
|
|
1251
1246
|
carriedDeletes: Map<string, number>;
|
|
1247
|
+
usedSourceNames: Set<string>;
|
|
1252
1248
|
}>;
|
|
1253
1249
|
export {};
|
|
1254
1250
|
|
|
@@ -1527,8 +1523,12 @@ declare module "sliftutils/storage/BulkDatabase2/blockCache" {
|
|
|
1527
1523
|
}
|
|
1528
1524
|
|
|
1529
1525
|
declare module "sliftutils/storage/BulkDatabase2/mergeLock" {
|
|
1526
|
+
import type { FileStorage } from "../FileFolderAPI";
|
|
1530
1527
|
export declare function tryAcquireMergeLock(collection: string, holderId: string): boolean;
|
|
1531
1528
|
export declare function releaseMergeLock(collection: string, holderId: string): void;
|
|
1529
|
+
export declare function tryAcquireMergeFileLock(storage: FileStorage, holderId: string): Promise<boolean>;
|
|
1530
|
+
export declare function startMergeFileLockHeartbeat(storage: FileStorage, holderId: string): () => void;
|
|
1531
|
+
export declare function releaseMergeFileLock(storage: FileStorage, holderId: string): Promise<void>;
|
|
1532
1532
|
|
|
1533
1533
|
}
|
|
1534
1534
|
|
package/package.json
CHANGED
|
@@ -138,10 +138,7 @@ export interface IBulkDatabase2<T extends {
|
|
|
138
138
|
* whether it bailed because another tab/process holds the merge lock — so a scheduler can call this
|
|
139
139
|
* (e.g. every 30 minutes) and tell "nothing to do" from "someone else is already merging".
|
|
140
140
|
*/
|
|
141
|
-
tryMergeNow(): Promise<
|
|
142
|
-
merged: boolean;
|
|
143
|
-
lockFailed: boolean;
|
|
144
|
-
}>;
|
|
141
|
+
tryMergeNow(): Promise<void>;
|
|
145
142
|
/** Rewrite everything written in [timeLo, timeHi] into fresh key-sorted bulk file(s). Low-level;
|
|
146
143
|
* most callers want compact() or tryMergeNow(). */
|
|
147
144
|
merge(timeLo: number, timeHi: number): Promise<void>;
|
|
@@ -131,7 +131,7 @@ export interface IBulkDatabase2<T extends { key: string }> {
|
|
|
131
131
|
* whether it bailed because another tab/process holds the merge lock — so a scheduler can call this
|
|
132
132
|
* (e.g. every 30 minutes) and tell "nothing to do" from "someone else is already merging".
|
|
133
133
|
*/
|
|
134
|
-
tryMergeNow(): Promise<
|
|
134
|
+
tryMergeNow(): Promise<void>;
|
|
135
135
|
|
|
136
136
|
/** Rewrite everything written in [timeLo, timeHi] into fresh key-sorted bulk file(s). Low-level;
|
|
137
137
|
* most callers want compact() or tryMergeNow(). */
|
|
@@ -2,7 +2,7 @@ import type { FileStorage } from "../FileFolderAPI";
|
|
|
2
2
|
export declare const BULK_ROOT_FOLDER = "bulkDatabases2";
|
|
3
3
|
export declare const bulkDatabase2Timing: {
|
|
4
4
|
streamSealAgeMs: number;
|
|
5
|
-
|
|
5
|
+
visibleMergeIntervalMs: number;
|
|
6
6
|
mergeSpacingMs: number;
|
|
7
7
|
firstMergeTriggerFiles: number;
|
|
8
8
|
firstMergeTriggerRangeMs: number;
|
|
@@ -36,6 +36,7 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
36
36
|
private storageFactory;
|
|
37
37
|
private config;
|
|
38
38
|
constructor(name: string, deps: ReactiveDeps, storageFactory: StorageFactory, config?: BulkDatabase2Config);
|
|
39
|
+
private setupVisibilityMergeCheck;
|
|
39
40
|
private reader;
|
|
40
41
|
private subCaches;
|
|
41
42
|
private pendingAppends;
|
|
@@ -46,7 +47,7 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
46
47
|
private streamFileName;
|
|
47
48
|
private currentStreamFileName;
|
|
48
49
|
private currentStreamFileBytes;
|
|
49
|
-
private
|
|
50
|
+
private mergeInFlight;
|
|
50
51
|
private streamRowsOnDisk;
|
|
51
52
|
private streamBytesOnDisk;
|
|
52
53
|
private fileSetPollTimer;
|
|
@@ -96,10 +97,7 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
96
97
|
private listFiles;
|
|
97
98
|
private writeBulkFile;
|
|
98
99
|
private maybeMerge;
|
|
99
|
-
tryMergeNow()
|
|
100
|
-
merged: boolean;
|
|
101
|
-
lockFailed: boolean;
|
|
102
|
-
}>;
|
|
100
|
+
tryMergeNow: () => Promise<void>;
|
|
103
101
|
compact(): Promise<void>;
|
|
104
102
|
merge(timeLo: number, timeHi: number): Promise<void>;
|
|
105
103
|
private readBulkHeader;
|
|
@@ -109,7 +107,7 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
109
107
|
private mergeFileSetInner;
|
|
110
108
|
private canDeleteStream;
|
|
111
109
|
private mergeSpacingDelay;
|
|
112
|
-
private
|
|
110
|
+
private testMergeINTERNAL_DO_NOT_CALL;
|
|
113
111
|
private findDuplicateGroups;
|
|
114
112
|
getSingleField<C extends keyof T>(key: string, column: C): Promise<T[C] | undefined>;
|
|
115
113
|
getSingleFieldObj<C extends keyof T>(key: string, column: C): Promise<{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { sort } from "socket-function/src/misc";
|
|
1
|
+
import { sort, throttleFunction } from "socket-function/src/misc";
|
|
2
|
+
import { runInSerial } from "socket-function/src/batching";
|
|
2
3
|
import { getTimeUnique } from "socket-function/src/bits";
|
|
3
4
|
import { lazy } from "socket-function/src/caching";
|
|
4
5
|
import { isNode } from "typesafecss";
|
|
@@ -17,7 +18,7 @@ import { blue, magenta } from "socket-function/src/formatting/logColors";
|
|
|
17
18
|
import { STREAM_EXTENSION, frameDeletes, frameRows, streamReaderFromEntries } from "./streamLog";
|
|
18
19
|
import { broadcast as syncBroadcast, broadcastSeal as syncBroadcastSeal, connect as syncConnect, isSyncSupported, RemoteWrite } from "./syncClient";
|
|
19
20
|
import { DELETED } from "./WriteOverlay";
|
|
20
|
-
import { releaseMergeLock, tryAcquireMergeLock } from "./mergeLock";
|
|
21
|
+
import { releaseMergeFileLock, releaseMergeLock, startMergeFileLockHeartbeat, tryAcquireMergeFileLock, tryAcquireMergeLock } from "./mergeLock";
|
|
21
22
|
import {
|
|
22
23
|
BulkFileInfo,
|
|
23
24
|
LoadedIndex,
|
|
@@ -41,11 +42,19 @@ const MAX_INDEX_RELOAD_ATTEMPTS = 3;
|
|
|
41
42
|
const FIRST_MERGE_BYTES = TARGET_FILE_BYTES / 2;
|
|
42
43
|
const KEY_GROUP_BYTES = 800 * 1024 * 1024;
|
|
43
44
|
const DUP_THRESHOLD = 0.4;
|
|
45
|
+
// Emergency Pass 2 short-circuit: once Pass 1 has had a chance to fold streams, if the bulk tier is
|
|
46
|
+
// big enough AND the overall key duplication fraction is high, do ONE merge across every bulk file
|
|
47
|
+
// instead of the per-key-group walk (which spaces merges 5 min apart — 16 hours for 200 groups).
|
|
48
|
+
const EMERGENCY_DEDUP_BYTES = 512 * 1024 * 1024;
|
|
49
|
+
const EMERGENCY_DEDUP_FRACTION = 0.5;
|
|
44
50
|
const WRITE_FLUSH_FIRST_STEP_MS = 250;
|
|
45
51
|
|
|
46
52
|
export const bulkDatabase2Timing = {
|
|
47
53
|
streamSealAgeMs: 10 * 60 * 60 * 1000,
|
|
48
|
-
|
|
54
|
+
// Wait this long after the tab becomes visible before the first merge check, then check this often
|
|
55
|
+
// afterwards. Tab being hidden cancels the timers (no merges while in background). Browser-only;
|
|
56
|
+
// Node compactors (e.g. remoteFileServer) drive their own polling.
|
|
57
|
+
visibleMergeIntervalMs: 5 * 60 * 1000,
|
|
49
58
|
mergeSpacingMs: 5 * 60 * 1000,
|
|
50
59
|
firstMergeTriggerFiles: 20,
|
|
51
60
|
firstMergeTriggerRangeMs: 3 * 24 * 60 * 60 * 1000,
|
|
@@ -164,10 +173,43 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
164
173
|
}
|
|
165
174
|
this.fileSetPollTimer = setInterval(() => void this.pollFileSet(), bulkDatabase2Timing.fileSetPollIntervalMs);
|
|
166
175
|
(this.fileSetPollTimer as { unref?: () => void }).unref?.();
|
|
176
|
+
this.setupVisibilityMergeCheck();
|
|
167
177
|
BulkDatabaseBase.liveInstances.add(this);
|
|
168
178
|
BulkDatabaseBase.startMemoryWatchdog();
|
|
169
179
|
}
|
|
170
180
|
|
|
181
|
+
// Every `visibleMergeIntervalMs` of being-visible time, run a merge check. First check fires
|
|
182
|
+
// `visibleMergeIntervalMs` after the tab becomes visible (not on initial load — gives the user a
|
|
183
|
+
// moment of session commitment before we start churning the FS), then every interval after. Tab
|
|
184
|
+
// hiding cancels the timers; visible-again restarts. Node has no document — there we consider
|
|
185
|
+
// ourselves always-visible and just install the interval directly.
|
|
186
|
+
private setupVisibilityMergeCheck(): void {
|
|
187
|
+
let firstTimer: ReturnType<typeof setTimeout> | undefined;
|
|
188
|
+
let intervalTimer: ReturnType<typeof setInterval> | undefined;
|
|
189
|
+
const stop = () => {
|
|
190
|
+
if (firstTimer) { clearTimeout(firstTimer); firstTimer = undefined; }
|
|
191
|
+
if (intervalTimer) { clearInterval(intervalTimer); intervalTimer = undefined; }
|
|
192
|
+
};
|
|
193
|
+
const start = () => {
|
|
194
|
+
if (firstTimer || intervalTimer) return;
|
|
195
|
+
firstTimer = setTimeout(() => {
|
|
196
|
+
firstTimer = undefined;
|
|
197
|
+
void this.maybeMerge();
|
|
198
|
+
intervalTimer = setInterval(() => void this.maybeMerge(), bulkDatabase2Timing.visibleMergeIntervalMs);
|
|
199
|
+
(intervalTimer as { unref?: () => void }).unref?.();
|
|
200
|
+
}, bulkDatabase2Timing.visibleMergeIntervalMs);
|
|
201
|
+
(firstTimer as { unref?: () => void }).unref?.();
|
|
202
|
+
};
|
|
203
|
+
if (typeof document === "undefined") { start(); return; }
|
|
204
|
+
try {
|
|
205
|
+
document.addEventListener("visibilitychange", () => {
|
|
206
|
+
if (document.visibilityState === "visible") start();
|
|
207
|
+
else stop();
|
|
208
|
+
});
|
|
209
|
+
if (document.visibilityState === "visible") start();
|
|
210
|
+
} catch { /* not in a DOM context */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
171
213
|
private reader: BulkDatabaseReader<T>;
|
|
172
214
|
private subCaches: SubReaderCaches = { bulk: new Map(), stream: new Map() };
|
|
173
215
|
|
|
@@ -180,7 +222,10 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
180
222
|
private streamFileName: string | undefined;
|
|
181
223
|
private currentStreamFileName: string | undefined;
|
|
182
224
|
private currentStreamFileBytes = 0;
|
|
183
|
-
|
|
225
|
+
// In-process re-entry guard: if a merge is running, additional triggers (visibility timer, writes,
|
|
226
|
+
// tryMergeNow calls) return immediately instead of queueing. Keeps the visibility-timer firings
|
|
227
|
+
// from stacking behind a long merge.
|
|
228
|
+
private mergeInFlight = false;
|
|
184
229
|
// Running counters of stream-tier rows + bytes on disk. Seeded from each LoadedIndex build, then
|
|
185
230
|
// incremented per flush so the fold-trigger checks current data without an extra directory listing.
|
|
186
231
|
private streamRowsOnDisk = 0;
|
|
@@ -380,7 +425,7 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
380
425
|
});
|
|
381
426
|
for (const { time, row } of stamped) syncBroadcast(this.name, { key: row.key as string, time, value: row });
|
|
382
427
|
await this.streamAppend(framed, stamped.length);
|
|
383
|
-
void this.maybeMerge();
|
|
428
|
+
void this.maybeMerge({ onlyIfStreamHeavy: true });
|
|
384
429
|
}
|
|
385
430
|
|
|
386
431
|
public async delete(key: string): Promise<void> {
|
|
@@ -396,7 +441,7 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
396
441
|
});
|
|
397
442
|
for (const { time, key } of stamped) syncBroadcast(this.name, { key, time, deleted: true });
|
|
398
443
|
await this.streamAppend(frameDeletes(stamped), stamped.length);
|
|
399
|
-
void this.maybeMerge();
|
|
444
|
+
void this.maybeMerge({ onlyIfStreamHeavy: true });
|
|
400
445
|
}
|
|
401
446
|
|
|
402
447
|
// Coalesce stream appends on a ramping per-collection schedule (the browser rewrites the whole
|
|
@@ -533,11 +578,12 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
533
578
|
}
|
|
534
579
|
|
|
535
580
|
// ── merge policy ─────────────────────────────────────────────────────────────────────────────────
|
|
536
|
-
|
|
581
|
+
// Called both periodically (visibility timer) and on writes (with the streamNeedsFold gate so a
|
|
582
|
+
// small write doesn't drag the merge through). The mergeInFlight check in tryMergeNow skips any
|
|
583
|
+
// call that arrives while a previous one is still running — so 5-min timer ticks don't pile up.
|
|
584
|
+
private async maybeMerge(opts: { onlyIfStreamHeavy?: boolean } = {}): Promise<void> {
|
|
537
585
|
if (!await this.automaticCompactionAllowed()) return;
|
|
538
|
-
|
|
539
|
-
if (!this.streamNeedsFold() && now - this.lastMergeCheck < bulkDatabase2Timing.mergeCheckIntervalMs) return;
|
|
540
|
-
this.lastMergeCheck = now;
|
|
586
|
+
if (opts.onlyIfStreamHeavy && !this.streamNeedsFold()) return;
|
|
541
587
|
try {
|
|
542
588
|
await this.tryMergeNow();
|
|
543
589
|
} catch (e) {
|
|
@@ -545,17 +591,40 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
545
591
|
}
|
|
546
592
|
}
|
|
547
593
|
|
|
548
|
-
|
|
549
|
-
|
|
594
|
+
// Returns immediately with { lockFailed: true } if another call is already in flight (same
|
|
595
|
+
// instance), if the localStorage lock is held by another tab, or if the cross-process file lock
|
|
596
|
+
// says another process owns it. Otherwise: acquires both locks, runs testMerge, releases.
|
|
597
|
+
public tryMergeNow = throttleFunction(1000, async () => {
|
|
598
|
+
if (this.mergeInFlight) return;
|
|
599
|
+
if (!tryAcquireMergeLock(this.name, writerId)) return;
|
|
600
|
+
const storage = await this.storage();
|
|
601
|
+
const haveFileLock = await tryAcquireMergeFileLock(storage, writerId);
|
|
602
|
+
if (!haveFileLock) {
|
|
603
|
+
releaseMergeLock(this.name, writerId);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this.mergeInFlight = true;
|
|
607
|
+
const stopHeartbeat = startMergeFileLockHeartbeat(storage, writerId);
|
|
550
608
|
try {
|
|
551
|
-
|
|
609
|
+
let t = Date.now();
|
|
610
|
+
await this.testMergeINTERNAL_DO_NOT_CALL();
|
|
611
|
+
console.log(` [autocompact] ${this.name}: merged (${Date.now() - t}ms)`);
|
|
552
612
|
} finally {
|
|
613
|
+
stopHeartbeat();
|
|
614
|
+
await releaseMergeFileLock(storage, writerId);
|
|
553
615
|
releaseMergeLock(this.name, writerId);
|
|
616
|
+
this.mergeInFlight = false;
|
|
554
617
|
}
|
|
555
|
-
}
|
|
618
|
+
});
|
|
556
619
|
|
|
557
620
|
public async compact(): Promise<void> {
|
|
621
|
+
if (this.mergeInFlight) return;
|
|
558
622
|
if (!tryAcquireMergeLock(this.name, writerId)) return;
|
|
623
|
+
const storage = await this.storage();
|
|
624
|
+
const haveFileLock = await tryAcquireMergeFileLock(storage, writerId);
|
|
625
|
+
if (!haveFileLock) { releaseMergeLock(this.name, writerId); return; }
|
|
626
|
+
this.mergeInFlight = true;
|
|
627
|
+
const stopHeartbeat = startMergeFileLockHeartbeat(storage, writerId);
|
|
559
628
|
try {
|
|
560
629
|
await this.flushPending();
|
|
561
630
|
syncBroadcastSeal(this.name);
|
|
@@ -565,7 +634,10 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
565
634
|
// can be dropped (nothing left to suppress).
|
|
566
635
|
if (bulkFiles.length + streamFiles.length >= 1) await this.mergeFileSet(bulkFiles, streamFiles, true);
|
|
567
636
|
} finally {
|
|
637
|
+
stopHeartbeat();
|
|
638
|
+
await releaseMergeFileLock(storage, writerId);
|
|
568
639
|
releaseMergeLock(this.name, writerId);
|
|
640
|
+
this.mergeInFlight = false;
|
|
569
641
|
}
|
|
570
642
|
}
|
|
571
643
|
|
|
@@ -631,19 +703,40 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
631
703
|
// leaves duplicates (next merge dedupes) rather than a gap. After the file set changes on disk,
|
|
632
704
|
// we trigger an index rebuild + atomic swap; once swap completes, the consumed files' block-cache
|
|
633
705
|
// entries are evicted (no consumer can ask for them now).
|
|
634
|
-
|
|
706
|
+
//
|
|
707
|
+
// Serialized: foldOwnStream + merge(timeLo, timeHi) + testMerge's runMerge all hit this; the lock
|
|
708
|
+
// covers tryMergeNow/compact but those two paths bypass it. runInSerial queues so they never
|
|
709
|
+
// collide on the file set.
|
|
710
|
+
private mergeFileSet = runInSerial(async (bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[], includesOldest = false, forceDeleteStreams = false): Promise<boolean> => {
|
|
635
711
|
this.reader.beginCompaction();
|
|
636
712
|
try {
|
|
637
713
|
return await this.mergeFileSetInner(bulkFiles, streamFiles, includesOldest, forceDeleteStreams);
|
|
638
714
|
} finally {
|
|
639
715
|
this.reader.endCompaction();
|
|
640
716
|
}
|
|
641
|
-
}
|
|
717
|
+
});
|
|
642
718
|
|
|
643
719
|
private async mergeFileSetInner(bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[], includesOldest: boolean, forceDeleteStreams: boolean): Promise<boolean> {
|
|
644
720
|
const storage = await this.storage();
|
|
645
721
|
const timestamp = nextFileTime();
|
|
646
722
|
|
|
723
|
+
// The caller's bulkFiles came from a `listFiles()` snapshot. Between then and now any file
|
|
724
|
+
// could be gone (another tab's deferred delete, manual cleanup). The subCaches reader for a
|
|
725
|
+
// deleted file is silently stale — its in-memory metadata still resolves, but the very next
|
|
726
|
+
// getRange against disk throws. Re-verify existence up-front and drop the cache entry for
|
|
727
|
+
// anything that vanished, so we don't even try to plan around a ghost file.
|
|
728
|
+
const verifiedBulkFiles = (await Promise.all(bulkFiles.map(async f => {
|
|
729
|
+
try {
|
|
730
|
+
const info = await storage.getInfo(f.fileName);
|
|
731
|
+
if (!info) { this.subCaches.bulk.delete(f.fileName); return undefined; }
|
|
732
|
+
return f;
|
|
733
|
+
} catch {
|
|
734
|
+
this.subCaches.bulk.delete(f.fileName);
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
}))).filter((f): f is BulkFileInfo => f !== undefined);
|
|
738
|
+
bulkFiles = verifiedBulkFiles;
|
|
739
|
+
|
|
647
740
|
const consumedBulk: BulkFileInfo[] = [];
|
|
648
741
|
const bulkReaders: BaseBulkDatabaseReader[] = [];
|
|
649
742
|
await Promise.all(bulkFiles.map(async f => {
|
|
@@ -721,6 +814,12 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
721
814
|
// correct). Block-cache eviction for inputs happens on the SECOND rebuild after deletion.
|
|
722
815
|
await this.triggerRebuild();
|
|
723
816
|
|
|
817
|
+
// Only the sources runPlannedMerge actually used are safe to delete — a source it dropped
|
|
818
|
+
// mid-plan (file gone, corruption) still holds data we couldn't merge in, so leave it on disk
|
|
819
|
+
// and let a future merge re-attempt it.
|
|
820
|
+
const usedConsumedBulk = consumedBulk.filter(f => mergeResult.usedSourceNames.has(f.fileName));
|
|
821
|
+
const usedStreamFiles = streamFiles.filter(f => mergeResult.usedSourceNames.has(f.fileName) || mergeResult.usedSourceNames.has("(streams)"));
|
|
822
|
+
|
|
724
823
|
// Deferred delete: give the FS time to durably flush new writes before removing the inputs.
|
|
725
824
|
// If we crash before the timer fires, the inputs survive as duplicates and a later key-stratify
|
|
726
825
|
// pass dedupes them. We DON'T await this — the merge returns "done" as soon as the new outputs
|
|
@@ -729,8 +828,8 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
729
828
|
const timer = setTimeout(() => {
|
|
730
829
|
void (async () => {
|
|
731
830
|
try {
|
|
732
|
-
for (const f of
|
|
733
|
-
for (const f of
|
|
831
|
+
for (const f of usedConsumedBulk) await remove(f.fileName);
|
|
832
|
+
for (const f of usedStreamFiles) {
|
|
734
833
|
if (await this.canDeleteStream(f, Date.now(), streamData.sizes, forceDeleteStreams)) await remove(f.fileName);
|
|
735
834
|
}
|
|
736
835
|
await this.triggerRebuild();
|
|
@@ -775,7 +874,8 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
775
874
|
// into one when they fragment or span too wide a time range.
|
|
776
875
|
// 2) Key-stratify: walk all keys in ~KEY_GROUP_BYTES groups; rewrite groups whose duplicate-key
|
|
777
876
|
// fraction passes DUP_THRESHOLD, highest first.
|
|
778
|
-
|
|
877
|
+
// Use tryMerge instead
|
|
878
|
+
private async testMergeINTERNAL_DO_NOT_CALL(): Promise<boolean> {
|
|
779
879
|
let merged = false;
|
|
780
880
|
await this.flushPending();
|
|
781
881
|
const runMerge = async (bulk: BulkFileInfo[], stream: StreamFileInfo[]): Promise<boolean> => {
|
|
@@ -841,6 +941,33 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
841
941
|
}
|
|
842
942
|
}
|
|
843
943
|
|
|
944
|
+
// Pass 1.5 (emergency dedup): Pass 1 has had its chance. If the bulk tier is fat AND mostly
|
|
945
|
+
// duplicates, the per-group walk below (5 min spacing per group) takes hours for an extreme
|
|
946
|
+
// state (1000+ files, 99% dup) — fold every bulk file in one merge and skip the walk.
|
|
947
|
+
{
|
|
948
|
+
const { bulkFiles } = await this.listFiles();
|
|
949
|
+
if (bulkFiles.length >= 2) {
|
|
950
|
+
const storage = await this.storage();
|
|
951
|
+
let totalBytes = 0;
|
|
952
|
+
let totalSlots = 0;
|
|
953
|
+
const uniqueKeys = new Set<string>();
|
|
954
|
+
for (const f of bulkFiles) {
|
|
955
|
+
try {
|
|
956
|
+
const reader = await loadFileReader(this.name, storage, f, this.subCaches.bulk);
|
|
957
|
+
totalBytes += reader.totalBytes;
|
|
958
|
+
totalSlots += reader.keys.length;
|
|
959
|
+
for (const k of reader.keys) uniqueKeys.add(k);
|
|
960
|
+
} catch { /* skip unreadable */ }
|
|
961
|
+
}
|
|
962
|
+
const dupFraction = totalSlots ? (totalSlots - uniqueKeys.size) / totalSlots : 0;
|
|
963
|
+
if (totalBytes > EMERGENCY_DEDUP_BYTES && dupFraction > EMERGENCY_DEDUP_FRACTION) {
|
|
964
|
+
console.log(`${blue(this.name)} ${magenta("emergency-dedup")}: ${fmtBytes(totalBytes)} across ${bulkFiles.length} bulk file(s), ${Math.round(dupFraction * 100)}% duplicate key-slots — folding all at once`);
|
|
965
|
+
if (!await runMerge(bulkFiles, [])) return merged;
|
|
966
|
+
return merged;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
844
971
|
// Pass 2: key-stratified deduplication. Disjoint key ranges → one group's merge doesn't
|
|
845
972
|
// change another's duplication; re-select each group's files at merge time (set shifts).
|
|
846
973
|
const groups = await this.findDuplicateGroups();
|
|
@@ -92,16 +92,42 @@ export async function runPlannedMerge(config: {
|
|
|
92
92
|
// console.log so standalone callers still get identifiable output.
|
|
93
93
|
log?: (line: string) => void;
|
|
94
94
|
writeFile: (data: Buffer) => Promise<{ name: string; size: number }>;
|
|
95
|
-
}): Promise<{ outputs: PlannedMergeOutput[]; carriedDeletes: Map<string, number> }> {
|
|
95
|
+
}): Promise<{ outputs: PlannedMergeOutput[]; carriedDeletes: Map<string, number>; usedSourceNames: Set<string> }> {
|
|
96
96
|
const targetFileBytes = config.targetFileBytes ?? TARGET_FILE_BYTES;
|
|
97
97
|
const targetBatchBytes = config.targetBatchBytes ?? DEFAULT_OUTPUT_BATCH_BYTES;
|
|
98
98
|
const log = config.log ?? (line => console.log(`${blue(config.collectionName)} ${line}`));
|
|
99
99
|
|
|
100
100
|
// ─────────────────────────────────────────── Phase 1: plan ───────────────────────────────────────────
|
|
101
|
-
//
|
|
101
|
+
// Load per-source column indexes FIRST so a file that's gone (deleted between caller's listFiles and
|
|
102
|
+
// now) gets caught here and excluded from the merge entirely. Each source's load is wrapped in a
|
|
103
|
+
// try/catch; on failure we mark the source bad, log a "skipped" line, and the rest of planning +
|
|
104
|
+
// execute pretends the source doesn't exist. The caller only deletes inputs that were actually used.
|
|
105
|
+
const sourceOk = new Array<boolean>(config.sources.length).fill(true);
|
|
106
|
+
const indexesPerSource: Map<string, ColumnIndex>[] = await Promise.all(config.sources.map(async (src, si) => {
|
|
107
|
+
const map = new Map<string, ColumnIndex>();
|
|
108
|
+
try {
|
|
109
|
+
await Promise.all(src.columns
|
|
110
|
+
.filter(c => c.column !== KEY_COLUMN)
|
|
111
|
+
.map(async c => { map.set(c.column, await src.getColumnIndex(c.column)); }));
|
|
112
|
+
} catch (e) {
|
|
113
|
+
sourceOk[si] = false;
|
|
114
|
+
log(`${magenta("skipped")} ${config.sourceNames[si]}: ${(e as Error).message}`);
|
|
115
|
+
}
|
|
116
|
+
return map;
|
|
117
|
+
}));
|
|
118
|
+
const usedSourceNames = new Set<string>();
|
|
119
|
+
for (let si = 0; si < config.sources.length; si++) {
|
|
120
|
+
if (sourceOk[si]) usedSourceNames.add(config.sourceNames[si]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Aggregate keyTimes + deleteTimes across GOOD sources only (max per key). A bad source's cached
|
|
124
|
+
// metadata might claim a key as newest, but we can't actually read its cells — letting it win the
|
|
125
|
+
// time resolution would just drop that key out of the output.
|
|
102
126
|
const deleteTime = new Map<string, number>();
|
|
103
127
|
const keyTime = new Map<string, number>();
|
|
104
|
-
for (
|
|
128
|
+
for (let si = 0; si < config.sources.length; si++) {
|
|
129
|
+
if (!sourceOk[si]) continue;
|
|
130
|
+
const src = config.sources[si];
|
|
105
131
|
for (const [k, t] of src.keyTimes) {
|
|
106
132
|
const prev = keyTime.get(k);
|
|
107
133
|
if (prev === undefined || t > prev) keyTime.set(k, t);
|
|
@@ -127,28 +153,19 @@ export async function runPlannedMerge(config: {
|
|
|
127
153
|
if (dT >= sT) carriedDeletes.set(k, dT);
|
|
128
154
|
}
|
|
129
155
|
|
|
130
|
-
// All distinct value columns (KEY_COLUMN excluded — it's added at file-assembly
|
|
131
|
-
//
|
|
156
|
+
// All distinct value columns from GOOD sources (KEY_COLUMN excluded — it's added at file-assembly
|
|
157
|
+
// time). Order: first-seen, matching the existing builders.
|
|
132
158
|
const allColumns: string[] = [];
|
|
133
159
|
const seenCols = new Set<string>();
|
|
134
|
-
for (
|
|
135
|
-
|
|
160
|
+
for (let si = 0; si < config.sources.length; si++) {
|
|
161
|
+
if (!sourceOk[si]) continue;
|
|
162
|
+
for (const c of config.sources[si].columns) {
|
|
136
163
|
if (c.column === KEY_COLUMN || seenCols.has(c.column)) continue;
|
|
137
164
|
seenCols.add(c.column);
|
|
138
165
|
allColumns.push(c.column);
|
|
139
166
|
}
|
|
140
167
|
}
|
|
141
168
|
|
|
142
|
-
// Load every (source, column) index in parallel — small data (offsets+types), no values pulled. Each
|
|
143
|
-
// index is kept for the executor too, so it can read contiguous row-ranges from the right source.
|
|
144
|
-
const indexesPerSource: Map<string, ColumnIndex>[] = await Promise.all(config.sources.map(async src => {
|
|
145
|
-
const map = new Map<string, ColumnIndex>();
|
|
146
|
-
await Promise.all(allColumns.map(async col => {
|
|
147
|
-
map.set(col, await src.getColumnIndex(col));
|
|
148
|
-
}));
|
|
149
|
-
return map;
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
169
|
// For each live key, for each column, find the winning source: among sources that have this key, the
|
|
153
170
|
// one whose keyTime is largest AND whose column-index reports non-ABSENT. Record source + sourceRow +
|
|
154
171
|
// byteLen + type so the planner can size offsets and the executor can copy the bytes.
|
|
@@ -161,6 +178,7 @@ export async function runPlannedMerge(config: {
|
|
|
161
178
|
let bestTime = -Infinity;
|
|
162
179
|
let best: CellChoice | undefined;
|
|
163
180
|
for (let si = 0; si < config.sources.length; si++) {
|
|
181
|
+
if (!sourceOk[si]) continue;
|
|
164
182
|
const src = config.sources[si];
|
|
165
183
|
const t = src.keyTimes.get(key);
|
|
166
184
|
if (t === undefined) continue;
|
|
@@ -251,7 +269,7 @@ export async function runPlannedMerge(config: {
|
|
|
251
269
|
const writtenBytes = outputs.reduce((a, o) => a + o.size, 0);
|
|
252
270
|
log(`${magenta("execute")}: ${outputs.length} file(s), ${fmtBytes(writtenBytes)} written`);
|
|
253
271
|
|
|
254
|
-
return { outputs, carriedDeletes };
|
|
272
|
+
return { outputs, carriedDeletes, usedSourceNames };
|
|
255
273
|
}
|
|
256
274
|
|
|
257
275
|
function buildOutputPlan(
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import type { FileStorage } from "../FileFolderAPI";
|
|
1
2
|
export declare function tryAcquireMergeLock(collection: string, holderId: string): boolean;
|
|
2
3
|
export declare function releaseMergeLock(collection: string, holderId: string): void;
|
|
4
|
+
export declare function tryAcquireMergeFileLock(storage: FileStorage, holderId: string): Promise<boolean>;
|
|
5
|
+
export declare function startMergeFileLockHeartbeat(storage: FileStorage, holderId: string): () => void;
|
|
6
|
+
export declare function releaseMergeFileLock(storage: FileStorage, holderId: string): Promise<void>;
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
// localStorage is unavailable (Node) — there concurrent merges are harmless, which the Node stress
|
|
7
7
|
// tests exercise. localStorage has no atomic compare-and-swap, so we write-then-reread to shrink the
|
|
8
8
|
// race window, and a TTL frees a lock left behind by a tab that crashed or closed mid-merge.
|
|
9
|
+
//
|
|
10
|
+
// File-lock layer (see acquireMergeFileLock at the bottom): for cross-PROCESS safety (different
|
|
11
|
+
// browsers, different machines pointing at the same remote storage), we ALSO write a `.merge-lock`
|
|
12
|
+
// file inside the collection's storage with our ID. The protocol is check-write-wait-recheck — last
|
|
13
|
+
// writer wins after the 15s settle window. The localStorage lock above covers same-origin tabs; this
|
|
14
|
+
// covers everything else (separate processes, remote shared storage).
|
|
15
|
+
|
|
16
|
+
import type { FileStorage } from "../FileFolderAPI";
|
|
9
17
|
|
|
10
18
|
const LOCK_TTL_MS = 30 * 1000;
|
|
11
19
|
|
|
@@ -48,3 +56,57 @@ export function releaseMergeLock(collection: string, holderId: string): void {
|
|
|
48
56
|
const existing = ls.getItem(key);
|
|
49
57
|
if (existing && existing.startsWith(holderId + ":")) ls.removeItem(key);
|
|
50
58
|
}
|
|
59
|
+
|
|
60
|
+
// ── File-based lock (cross-process / shared storage) ──────────────────────────────────────────────
|
|
61
|
+
const FILE_LOCK_KEY = ".merge-lock";
|
|
62
|
+
const FILE_LOCK_TTL_MS = 5 * 60 * 1000;
|
|
63
|
+
const FILE_LOCK_SETTLE_MS = 15 * 1000;
|
|
64
|
+
const FILE_LOCK_HEARTBEAT_MS = 2 * 60 * 1000;
|
|
65
|
+
|
|
66
|
+
async function readMergeFileLock(storage: FileStorage): Promise<{ id: string; time: number } | undefined> {
|
|
67
|
+
try {
|
|
68
|
+
const buf = await storage.get(FILE_LOCK_KEY);
|
|
69
|
+
if (!buf) return undefined;
|
|
70
|
+
const s = buf.toString("utf8");
|
|
71
|
+
const colon = s.lastIndexOf(":");
|
|
72
|
+
if (colon < 0) return undefined;
|
|
73
|
+
const time = parseInt(s.slice(colon + 1), 10);
|
|
74
|
+
if (!Number.isFinite(time)) return undefined;
|
|
75
|
+
return { id: s.slice(0, colon), time };
|
|
76
|
+
} catch { return undefined; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function writeMergeFileLock(storage: FileStorage, holderId: string, time: number): Promise<void> {
|
|
80
|
+
await storage.set(FILE_LOCK_KEY, Buffer.from(`${holderId}:${time}`, "utf8") as Buffer);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Acquire the cross-process file lock. Resolves to true ONLY if, after writing our ID and waiting
|
|
84
|
+
// FILE_LOCK_SETTLE_MS for concurrent writers to settle, the file still names us. Caller must
|
|
85
|
+
// releaseMergeFileLock() on the same storage + holderId when done.
|
|
86
|
+
export async function tryAcquireMergeFileLock(storage: FileStorage, holderId: string): Promise<boolean> {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const existing = await readMergeFileLock(storage);
|
|
89
|
+
if (existing && existing.id !== holderId && now - existing.time < FILE_LOCK_TTL_MS) return false;
|
|
90
|
+
await writeMergeFileLock(storage, holderId, now);
|
|
91
|
+
await new Promise(r => setTimeout(r, FILE_LOCK_SETTLE_MS));
|
|
92
|
+
const after = await readMergeFileLock(storage);
|
|
93
|
+
return !!after && after.id === holderId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Keep the lock fresh. The merge might run longer than FILE_LOCK_TTL_MS; without periodic re-stamping,
|
|
97
|
+
// another process would see the lock as stale and acquire it. Returns a stop function the caller calls
|
|
98
|
+
// in `finally`. Heartbeat is best-effort: a failed write is logged but not propagated.
|
|
99
|
+
export function startMergeFileLockHeartbeat(storage: FileStorage, holderId: string): () => void {
|
|
100
|
+
const interval = setInterval(() => {
|
|
101
|
+
void writeMergeFileLock(storage, holderId, Date.now()).catch(() => { /* best-effort */ });
|
|
102
|
+
}, FILE_LOCK_HEARTBEAT_MS);
|
|
103
|
+
(interval as { unref?: () => void }).unref?.();
|
|
104
|
+
return () => clearInterval(interval);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function releaseMergeFileLock(storage: FileStorage, holderId: string): Promise<void> {
|
|
108
|
+
try {
|
|
109
|
+
const existing = await readMergeFileLock(storage);
|
|
110
|
+
if (existing && existing.id === holderId) await storage.remove(FILE_LOCK_KEY);
|
|
111
|
+
} catch { /* ignore */ }
|
|
112
|
+
}
|
|
@@ -22,176 +22,176 @@ import { wrapHandle, NodeJSDirectoryHandleWrapper, DirectoryWrapper } from "./Fi
|
|
|
22
22
|
|
|
23
23
|
// Common, memorable words (frequency-ordered subset) for passphrase generation.
|
|
24
24
|
const WORDS: string[] = [
|
|
25
|
-
"that","with","which","this","from","have","they","were","their","there","when","them","said","been",
|
|
26
|
-
"would","will","what","more","then","into","some","could","time","very","than","your","other","upon",
|
|
27
|
-
"about","only","little","like","these","great","after","well","made","before","such","over","should",
|
|
28
|
-
"first","good","must","much","down","where","know","most","here","come","those","never","life","long",
|
|
29
|
-
"came","being","many","through","even","himself","back","every","shall","again","make","without","might",
|
|
30
|
-
"while","same","under","just","still","people","place","think","house","take","last","found","away",
|
|
31
|
-
"hand","went","thought","also","though","three","another","eyes","years","work","right","once","night",
|
|
32
|
-
"young","nothing","against","small","head","left","part","ever","world","each","father","give","between",
|
|
33
|
-
"face","king","things","love","because","took","always","tell","called","water","both","side","look",
|
|
34
|
-
"having","room","mind","half","heart","name","home","country","whole","however","find","among","going",
|
|
35
|
-
"thing","lord","looked","seen","mother","general","done","seemed","told","whom","days","soon","better",
|
|
36
|
-
"letter","woman","heard","asked","course","thus","moment","knew","light","enough","white","almost",
|
|
37
|
-
"until","quite","hands","words","death","large","taken","since","gave","given","best","state","brought",
|
|
38
|
-
"does","whose","door","others","power","perhaps","present","next","morning","poor","lady","four","high",
|
|
39
|
-
"year","turned","less","word","full","during","rather","want","order","near","feet","true","miss",
|
|
40
|
-
"matter","began","cannot","used","known","felt","above","round","thou","voice","till","case","nature",
|
|
41
|
-
"indeed","church","kind","certain","fire","often","stood","fact","friend","girl","five","land","says",
|
|
42
|
-
"john","myself","along","point","dear","wife","city","within","sent","times","keep","passed","form",
|
|
43
|
-
"second","body","money","believe","hundred","open","several","means","child","english","herself","sure",
|
|
44
|
-
"looking","women","already","black","alone","least","gone","held","itself","whether","hope","river",
|
|
45
|
-
"ground","either","number","chapter","england","leave","rest","town","hear","greek","friends","book",
|
|
46
|
-
"hour","short","cried","read","behind","became","making","family","earth","captain","around","dead",
|
|
47
|
-
"reason","call","become","lost","line","replied","help","coming","speak","manner","french","twenty",
|
|
48
|
-
"spirit","really","early","story","hard","close","human","public","truth","strong","master","care",
|
|
49
|
-
"towards","history","kept","later","states","dark","able","mean","return","brother","person","subject",
|
|
50
|
-
"soul","party","arms","thee","seems","common","fell","fine","feel","show","table","turn","wish","evening",
|
|
51
|
-
"free","cause","south","ready","north","across","rose","live","account","doubt","company","miles","road",
|
|
52
|
-
"bring","london","sense","horse","carried","hold","sight","fear","answer","idea","force","need","deep",
|
|
53
|
-
"further","nearly","past","army","blood","street","court","reached","view","school","sort","taking",
|
|
54
|
-
"else","chief","hours","beyond","cold","none","longer","strange","fellow","clear","service","natural",
|
|
55
|
-
"suppose","late","talk","front","stand","purpose","seem","neither","ought","west","real","except","sound",
|
|
56
|
-
"gold","forward","feeling","added","boys","self","peace","happy","living","husband","toward","spoke",
|
|
57
|
-
"fair","france","trees","effect","latter","length","change","died","green","united","fall","pretty",
|
|
58
|
-
"placed","meet","forth","office","comes","pass","written","ship","enemy","saying","tree","foot","blue",
|
|
59
|
-
"note","prince","hair","heaven","wild","play","entered","society","laid","wind","doing","paper","opened",
|
|
60
|
-
"bear","third","queen","mine","greater","various","faith","wanted","boat","stone","lived","george",
|
|
61
|
-
"window","doctor","action","books","tried","letters","makes","minutes","parts","wood","period","duty",
|
|
62
|
-
"york","instead","heavy","persons","battle","field","british","object","century","island","beauty",
|
|
63
|
-
"christ","sister","glad","below","east","opinion","save","places","months","food","sweet","trouble",
|
|
64
|
-
"system","born","desire","works","mary","chance","william","seven","single","hardly","sleep","mouth",
|
|
65
|
-
"horses","silence","ancient","broken","hall","send","rich","girls","besides","lips","henry","figure",
|
|
66
|
-
"slowly","hill","wall","future","lines","follow","german","march","filled","brown","deal","eight",
|
|
67
|
-
"covered","smile","former","week","drew","easy","paris","camp","stay","cross","seeing","result","started",
|
|
68
|
-
"caught","houses","appear","wrote","giving","simple","arrived","formed","bright","wide","uncle","piece",
|
|
69
|
-
"walked","knows","carry","middle","village","holy","merely","getting","raised","afraid","struck",
|
|
70
|
-
"charles","board","visit","royal","spring","dinner","evil","outside","wrong","summer","moved","danger",
|
|
71
|
-
"mark","fresh","plain","thirty","scene","quickly","wonder","jack","indian","walk","learned","class",
|
|
72
|
-
"secret","wait","command","easily","garden","loved","married","fight","leaving","music","usual","cases",
|
|
73
|
-
"winter","respect","value","quiet","please","stopped","write","laughed","america","chair","paid","duke",
|
|
74
|
-
"leaves","lower","colonel","perfect","james","worth","author","finally","youth","regard","glass",
|
|
75
|
-
"picture","built","modern","success","race","showed","tears","unless","mere","lives","grew","charge",
|
|
76
|
-
"beside","remain","waiting","study","hath","shot","unto","tone","iron","watch","grace","flowers","killed",
|
|
77
|
-
"allowed","news","step","proper","sitting","floor","justice","noble","goes","walls","laws","meant",
|
|
78
|
-
"bound","forms","fifty","drawn","private","fast","indians","judge","meeting","usually","sudden","gives",
|
|
79
|
-
"reach","bank","attempt","shore","stop","plan","silent","special","spot","officer","silver","passing",
|
|
80
|
-
"broke","lake","soft","journey","beneath","shown","turning","members","enter","lead","trade","names",
|
|
81
|
-
"escape","troops","bill","corner","rule","ladies","species","rock","similar","running","rate","cast",
|
|
82
|
-
"nation","post","likely","simply","orders","dress","fish","minute","birds","europe","passage","surface",
|
|
83
|
-
"snow","attack","higher","rise","grand","reply","honour","notice","speech","trying","game","writing",
|
|
84
|
-
"closed","rome","example","aunt","learn","morrow","offered","peter","equal","space","page","wished",
|
|
85
|
-
"changed","animal","social","twelve","appears","receive","valley","degree","train","steps","fixed",
|
|
86
|
-
"count","forest","matters","foreign","native","broad","moral","animals","safe","roman","break","pale",
|
|
87
|
-
"memory","warm","trust","yellow","sides","main","bird","reading","coast","pain","grave","forget","move",
|
|
88
|
-
"fortune","madame","proved","forty","lying","quick","wise","jesus","working","surely","looks","seat",
|
|
89
|
-
"divine","touch","loss","paul","heads","spent","ordered","report","caused","thomas","ones","drawing",
|
|
90
|
-
"talking","breath","meaning","month","union","pleased","powers","emperor","laugh","affairs","narrow",
|
|
91
|
-
"square","science","begin","promise","takes","conduct","serious","thank","curious","pieces","health",
|
|
92
|
-
"exactly","ways","upper","decided","nine","stream","wine","engaged","points","amount","named","song",
|
|
93
|
-
"worse","size","castle","crown","mass","liberty","stage","guard","servant","greatly","price","weeks",
|
|
94
|
-
"neck","ears","drink","crowd","thick","fallen","hills","spoken","golden","capital","sign","spite","terms",
|
|
95
|
-
"sake","council","threw","ships","portion","support","clothes","effort","fully","holding","majesty",
|
|
96
|
-
"glory","pure","facts","sword","sorry","fate","bread","prove","station","sharp","dream","vain","major",
|
|
97
|
-
"smiled","instant","spread","serve","sick","june","ideas","thrown","start","weather","gray","courage",
|
|
98
|
-
"anxious","woods","path","rising","grass","moon","gate","forced","fancy","bottom","expect","remains",
|
|
99
|
-
"shook","bishop","watched","settled","events","rain","quarter","heat","palace","glance","kingdom",
|
|
100
|
-
"papers","aside","worthy","taste","pride","july","opening","daily","leading","wounded","famous","offer",
|
|
101
|
-
"group","distant","weight","highest","vast","popular","passion","knowing","allow","sought","lies",
|
|
102
|
-
"marked","series","growing","measure","hearts","robert","obliged","western","hence","style","dropped",
|
|
103
|
-
"nations","grown","honor","edge","pray","tall","frank","poet","streets","season","pointed","mighty",
|
|
104
|
-
"temple","extent","thin","blow","freedom","entire","keeping","prevent","shut","storm","lose","college",
|
|
105
|
-
"cost","marry","spirits","worked","colour","ring","listen","fruit","waters","fashion","share","hung",
|
|
106
|
-
"proud","fingers","method","served","grow","fort","draw","dollars","quietly","yours","vessel","direct",
|
|
107
|
-
"refused","bridge","gods","inside","title","advance","priest","becomes","dick","double","seek","guess",
|
|
108
|
-
"spanish","larger","ball","finding","edward","removed","brave","tired","agreed","sacred","sons","guns",
|
|
109
|
-
"played","richard","devil","pounds","honest","false","cloth","soldier","tongue","smith","wealth","failed",
|
|
110
|
-
"height","faces","equally","legs","pocket","twice","volume","prayer","county","notes","louis","unknown",
|
|
111
|
-
"delight","rights","nice","taught","coat","dare","slight","rocks","enemies","control","forces","germany",
|
|
112
|
-
"kill","process","david","rapidly","comfort","islands","centre","salt","april","windows","noticed",
|
|
113
|
-
"truly","color","fields","date","august","desired","breast","skin","gentle","smoke","search","joined",
|
|
114
|
-
"nobody","results","needed","weak","type","stones","follows","theory","shape","waited","telling","relief",
|
|
115
|
-
"bearing","bent","mile","dressed","birth","spain","female","clearly","moving","drive","crossed","member",
|
|
116
|
-
"saved","teeth","flesh","cover","shows","farther","stands","figures","numbers","bodies","supply","motion",
|
|
117
|
-
"grey","bell","alive","seized","shadow","produce","touched","driven","talked","empire","sunday","pair",
|
|
118
|
-
"fourth","slave","regular","dozen","beat","cousin","sand","soil","rough","claim","angry","pity","walking",
|
|
119
|
-
"acts","pope","rooms","task","stock","tender","throw","reader","india","hotel","fifteen","arrival",
|
|
120
|
-
"plate","stars","willing","stories","saint","amongst","virtue","chamber","civil","yards","habit","writer",
|
|
121
|
-
"putting","origin","italy","hearing","genius","milk","busy","fool","burning","duties","brain","slow",
|
|
122
|
-
"belief","bore","mention","grant","library","inches","press","calm","total","ride","lands","labor",
|
|
123
|
-
"parties","clean","catch","treated","rode","whilst","level","efforts","minds","welcome","wisdom","supper",
|
|
124
|
-
"final","mercy","aware","lovely","empty","wants","midst","central","rank","proof","burst","term","farm",
|
|
125
|
-
"applied","loud","nearer","harry","closely","kings","explain","deck","noise","hurt","advice","absence",
|
|
126
|
-
"circle","objects","secure","plants","parents","falling","base","fellows","sorrow","flat","flower",
|
|
127
|
-
"calling","imagine","range","sold","favour","happen","showing","earl","hole","careful","dust","prison",
|
|
128
|
-
"useful","accept","firm","sing","port","seated","custom","wore","doors","lifted","edition","tale",
|
|
129
|
-
"affair","policy","roof","express","ahead","worship","partly","address","highly","victory","ocean",
|
|
130
|
-
"begun","buried","content","smiling","liked","active","quality","philip","current","divided","growth",
|
|
131
|
-
"huge","moments","article","speed","poetry","machine","join","nose","unable","ceased","gained","worn",
|
|
132
|
-
"eastern","safety","ages","vessels","hopes","corn","slaves","drop","plant","evident","local","police",
|
|
133
|
-
"october","obtain","italian","deeply","dying","wholly","dogs","playing","plenty","apart","suffer","maid",
|
|
134
|
-
"record","fairly","kindly","terror","drove","ends","sugar","capable","irish","message","chosen","flight",
|
|
135
|
-
"reasons","couple","meat","printed","blind","energy","bitter","baby","mistake","tower","ireland","trial",
|
|
136
|
-
"wear","brief","market","band","causes","clouds","goods","admit","cities","earlier","tells","stated",
|
|
137
|
-
"pressed","crime","fought","guide","hoped","list","banks","knight","fleet","pulled","tail","patient",
|
|
138
|
-
"mental","boats","kinds","stairs","star","cattle","rare","wings","disease","section","actual","bare",
|
|
139
|
-
"extreme","cruel","worst","escaped","dance","strike","views","eggs","needs","store","choice","fond",
|
|
140
|
-
"adopted","suit","singing","fail","souls","thanks","hidden","shame","faint","shop","older","joseph",
|
|
141
|
-
"knees","gently","blessed","yard","cent","grief","male","sooner","january","excited","region","praise",
|
|
142
|
-
"throne","classes","effects","demand","gain","fault","labour","variety","loose","cook","devoted","ended",
|
|
143
|
-
"changes","witness","bought","lover","visited","club","sheep","cloud","enjoy","asleep","cool","dull",
|
|
144
|
-
"painted","arose","armed","dignity","chiefly","voices","sail","event","signs","hero","schools","branch",
|
|
145
|
-
"hurried","arthur","kitchen","stick","coffee","thinks","eager","natives","flying","boston","eternal",
|
|
146
|
-
"source","fill","anger","vision","murder","viii","park","avoid","awful","raise","desert","plans","pardon",
|
|
147
|
-
"assured","wound","finger","bible","rear","details","cabin","whence","reign","weary","slavery","visible",
|
|
148
|
-
"shouted","seldom","skill","hast","throat","reality","leader","egypt","credit","smaller","severe","calls",
|
|
149
|
-
"younger","shell","sprang","anybody","handed","despair","asking","inch","mystery","manners","knife",
|
|
150
|
-
"design","sounds","wishes","stepped","towns","sixty","immense","china","favor","latin","hate","setting",
|
|
151
|
-
"excuse","chinese","behold","rushed","choose","ease","lights","paused","mission","voyage","gift","bold",
|
|
152
|
-
"meal","britain","smooth","mounted","utterly","lest","helped","picked","falls","pipe","bones","earnest",
|
|
153
|
-
"granted","swept","anxiety","teach","waves","softly","savage","hunting","defence","rapid","artist",
|
|
154
|
-
"recent","supreme","russian","created","habits","exist","guilty","pages","crew","hell","remark","request",
|
|
155
|
-
"harm","solemn","observe","trail","copy","violent","dreams","proceed","luck","steel","sell","temper",
|
|
156
|
-
"appeal","hide","fled","belong","triumph","abroad","waste","consent","writers","possess","jews","require",
|
|
157
|
-
"priests","railway","founded","pushed","host","solid","maybe","degrees","cheeks","mount","ruin","owing",
|
|
158
|
-
"fierce","reduced","mankind","text","swift","riding","silk","shade","career","wicked","afford","alas",
|
|
159
|
-
"rules","treaty","correct","spend","foolish","methods","remarks","horror","shining","enjoyed","tribes",
|
|
160
|
-
"lack","frame","gets","eagerly","russia","alike","somehow","nodded","problem","fever","sees","stern",
|
|
161
|
-
"dawn","forgive","flag","managed","fame","travel","estate","cotton","hurry","agree","feared","kiss",
|
|
162
|
-
"million","martin","mixed","risk","butter","jane","assumed","pause","benefit","unhappy","lighted",
|
|
163
|
-
"scheme","slept","plainly","forgot","delay","merry","rush","wooden","secured","poem","rolled","angel",
|
|
164
|
-
"guests","farmer","humble","glanced","shortly","rope","image","lately","africa","fired","retired","bull",
|
|
165
|
-
"steam","keen","loving","borne","readily","beloved","average","teacher","attend","flame","area","burned",
|
|
166
|
-
"thrust","negro","nurse","spare","fifth","thence","roads","refuse","tent","kissed","gates","gaze","fatal",
|
|
167
|
-
"israel","verse","scenes","confess","uttered","build","acted","hanging","reward","issue","utmost",
|
|
168
|
-
"fathers","noted","widow","related","urged","mode","failure","nervous","render","masters","tribe",
|
|
169
|
-
"colored","turns","alarm","rivers","using","eleven","rage","hers","lamp","retreat","amid","gospel",
|
|
170
|
-
"exposed","johnson","marks","tide","emotion","destroy","inner","sisters","lion","helen","tied","grounds",
|
|
171
|
-
"acid","wheel","issued","invited","gazed","senate","finds","seed","regret","clay","chain","crowded",
|
|
172
|
-
"bosom","limited","steady","chap","anne","francis","cavalry","freely","charm","faced","ideal","useless",
|
|
173
|
-
"warning","shelter","vote","xpage","cottage","beast","outer","folks","misery","anyone","expense","firmly",
|
|
174
|
-
"eating","lonely","owner","trace","coal","hastily","elder","utter","begins","chapel","nights","dared",
|
|
175
|
-
"signal","stared","track","lords","speaks","bottle","blame","claims","shoes","sigh","ghost","folk",
|
|
176
|
-
"dutch","flung","flew","cheek","staff","walter","clever","acting","hungry","poems","cutting","forever",
|
|
177
|
-
"exact","haste","dancing","trip","lincoln","pull","vice","slipped","thine","loves","permit","mill",
|
|
178
|
-
"engine","hollow","seeking","bowed","largely","charged","painful","madam","roll","leaf","prayers",
|
|
179
|
-
"element","agent","cries","songs","theatre","creek","match","ashamed","runs","aspect","canada","treat",
|
|
180
|
-
"legal","arts","corps","noon","nearest","scale","hunt","shadows","winds","landed","beings","tiny",
|
|
181
|
-
"gardens","bless","clerk","doth","derived","hang","heavens","cape","ours","brow","test","senses",
|
|
182
|
-
"opposed","error","driving","nest","headed","groups","princes","feast","admiral","poured","brings",
|
|
183
|
-
"pursued","germans","bears","lesson","journal","unusual","marched","wing","remove","studied","passes",
|
|
184
|
-
"actions","burden","colony","scott","bride","yield","crying","forming","rifle","powder","rested","shake",
|
|
185
|
-
"guest","begged","occur","altar","sorts","beaten","wave","papa","sang","depth","aloud","contact",
|
|
186
|
-
"intense","marble","poverty","detail","writes","root","metal","jean","existed","ability","purple",
|
|
187
|
-
"counsel","lane","wives","sending","heavily","leaders","queer","tales","sins","mexico","protect","clock",
|
|
188
|
-
"stuff","jones","pine","records","cave","baron","medical","pick","stayed","magic","seventy","pour",
|
|
189
|
-
"saddle","sank","dread","deny","wire","rude","holds","strain","autumn","shed","shoot","settle","basis",
|
|
190
|
-
"readers","route","beach","safely","oxford","notion","thunder","sale","saints","pound","shock","elected",
|
|
191
|
-
"signed","whereas","maiden","courts","rarely","wolf","mamma","billy","student","charity","impulse",
|
|
192
|
-
"tones","mortal","raising","wedding","belongs","chest","sharply","sounded","poets","lawyer","hugh",
|
|
193
|
-
"plays","awake","landing","debt","alice","venture","idle","uniform","contain","tobacco","boots","fears",
|
|
194
|
-
"leaned","studies","customs","prize","stir","obey","oath","badly","pursuit","resting",
|
|
25
|
+
"that", "with", "which", "this", "from", "have", "they", "were", "their", "there", "when", "them", "said", "been",
|
|
26
|
+
"would", "will", "what", "more", "then", "into", "some", "could", "time", "very", "than", "your", "other", "upon",
|
|
27
|
+
"about", "only", "little", "like", "these", "great", "after", "well", "made", "before", "such", "over", "should",
|
|
28
|
+
"first", "good", "must", "much", "down", "where", "know", "most", "here", "come", "those", "never", "life", "long",
|
|
29
|
+
"came", "being", "many", "through", "even", "himself", "back", "every", "shall", "again", "make", "without", "might",
|
|
30
|
+
"while", "same", "under", "just", "still", "people", "place", "think", "house", "take", "last", "found", "away",
|
|
31
|
+
"hand", "went", "thought", "also", "though", "three", "another", "eyes", "years", "work", "right", "once", "night",
|
|
32
|
+
"young", "nothing", "against", "small", "head", "left", "part", "ever", "world", "each", "father", "give", "between",
|
|
33
|
+
"face", "king", "things", "love", "because", "took", "always", "tell", "called", "water", "both", "side", "look",
|
|
34
|
+
"having", "room", "mind", "half", "heart", "name", "home", "country", "whole", "however", "find", "among", "going",
|
|
35
|
+
"thing", "lord", "looked", "seen", "mother", "general", "done", "seemed", "told", "whom", "days", "soon", "better",
|
|
36
|
+
"letter", "woman", "heard", "asked", "course", "thus", "moment", "knew", "light", "enough", "white", "almost",
|
|
37
|
+
"until", "quite", "hands", "words", "death", "large", "taken", "since", "gave", "given", "best", "state", "brought",
|
|
38
|
+
"does", "whose", "door", "others", "power", "perhaps", "present", "next", "morning", "poor", "lady", "four", "high",
|
|
39
|
+
"year", "turned", "less", "word", "full", "during", "rather", "want", "order", "near", "feet", "true", "miss",
|
|
40
|
+
"matter", "began", "cannot", "used", "known", "felt", "above", "round", "thou", "voice", "till", "case", "nature",
|
|
41
|
+
"indeed", "church", "kind", "certain", "fire", "often", "stood", "fact", "friend", "girl", "five", "land", "says",
|
|
42
|
+
"john", "myself", "along", "point", "dear", "wife", "city", "within", "sent", "times", "keep", "passed", "form",
|
|
43
|
+
"second", "body", "money", "believe", "hundred", "open", "several", "means", "child", "english", "herself", "sure",
|
|
44
|
+
"looking", "women", "already", "black", "alone", "least", "gone", "held", "itself", "whether", "hope", "river",
|
|
45
|
+
"ground", "either", "number", "chapter", "england", "leave", "rest", "town", "hear", "greek", "friends", "book",
|
|
46
|
+
"hour", "short", "cried", "read", "behind", "became", "making", "family", "earth", "captain", "around", "dead",
|
|
47
|
+
"reason", "call", "become", "lost", "line", "replied", "help", "coming", "speak", "manner", "french", "twenty",
|
|
48
|
+
"spirit", "really", "early", "story", "hard", "close", "human", "public", "truth", "strong", "master", "care",
|
|
49
|
+
"towards", "history", "kept", "later", "states", "dark", "able", "mean", "return", "brother", "person", "subject",
|
|
50
|
+
"soul", "party", "arms", "thee", "seems", "common", "fell", "fine", "feel", "show", "table", "turn", "wish", "evening",
|
|
51
|
+
"free", "cause", "south", "ready", "north", "across", "rose", "live", "account", "doubt", "company", "miles", "road",
|
|
52
|
+
"bring", "london", "sense", "horse", "carried", "hold", "sight", "fear", "answer", "idea", "force", "need", "deep",
|
|
53
|
+
"further", "nearly", "past", "army", "blood", "street", "court", "reached", "view", "school", "sort", "taking",
|
|
54
|
+
"else", "chief", "hours", "beyond", "cold", "none", "longer", "strange", "fellow", "clear", "service", "natural",
|
|
55
|
+
"suppose", "late", "talk", "front", "stand", "purpose", "seem", "neither", "ought", "west", "real", "except", "sound",
|
|
56
|
+
"gold", "forward", "feeling", "added", "boys", "self", "peace", "happy", "living", "husband", "toward", "spoke",
|
|
57
|
+
"fair", "france", "trees", "effect", "latter", "length", "change", "died", "green", "united", "fall", "pretty",
|
|
58
|
+
"placed", "meet", "forth", "office", "comes", "pass", "written", "ship", "enemy", "saying", "tree", "foot", "blue",
|
|
59
|
+
"note", "prince", "hair", "heaven", "wild", "play", "entered", "society", "laid", "wind", "doing", "paper", "opened",
|
|
60
|
+
"bear", "third", "queen", "mine", "greater", "various", "faith", "wanted", "boat", "stone", "lived", "george",
|
|
61
|
+
"window", "doctor", "action", "books", "tried", "letters", "makes", "minutes", "parts", "wood", "period", "duty",
|
|
62
|
+
"york", "instead", "heavy", "persons", "battle", "field", "british", "object", "century", "island", "beauty",
|
|
63
|
+
"christ", "sister", "glad", "below", "east", "opinion", "save", "places", "months", "food", "sweet", "trouble",
|
|
64
|
+
"system", "born", "desire", "works", "mary", "chance", "william", "seven", "single", "hardly", "sleep", "mouth",
|
|
65
|
+
"horses", "silence", "ancient", "broken", "hall", "send", "rich", "girls", "besides", "lips", "henry", "figure",
|
|
66
|
+
"slowly", "hill", "wall", "future", "lines", "follow", "german", "march", "filled", "brown", "deal", "eight",
|
|
67
|
+
"covered", "smile", "former", "week", "drew", "easy", "paris", "camp", "stay", "cross", "seeing", "result", "started",
|
|
68
|
+
"caught", "houses", "appear", "wrote", "giving", "simple", "arrived", "formed", "bright", "wide", "uncle", "piece",
|
|
69
|
+
"walked", "knows", "carry", "middle", "village", "holy", "merely", "getting", "raised", "afraid", "struck",
|
|
70
|
+
"charles", "board", "visit", "royal", "spring", "dinner", "evil", "outside", "wrong", "summer", "moved", "danger",
|
|
71
|
+
"mark", "fresh", "plain", "thirty", "scene", "quickly", "wonder", "jack", "indian", "walk", "learned", "class",
|
|
72
|
+
"secret", "wait", "command", "easily", "garden", "loved", "married", "fight", "leaving", "music", "usual", "cases",
|
|
73
|
+
"winter", "respect", "value", "quiet", "please", "stopped", "write", "laughed", "america", "chair", "paid", "duke",
|
|
74
|
+
"leaves", "lower", "colonel", "perfect", "james", "worth", "author", "finally", "youth", "regard", "glass",
|
|
75
|
+
"picture", "built", "modern", "success", "race", "showed", "tears", "unless", "mere", "lives", "grew", "charge",
|
|
76
|
+
"beside", "remain", "waiting", "study", "hath", "shot", "unto", "tone", "iron", "watch", "grace", "flowers", "killed",
|
|
77
|
+
"allowed", "news", "step", "proper", "sitting", "floor", "justice", "noble", "goes", "walls", "laws", "meant",
|
|
78
|
+
"bound", "forms", "fifty", "drawn", "private", "fast", "indians", "judge", "meeting", "usually", "sudden", "gives",
|
|
79
|
+
"reach", "bank", "attempt", "shore", "stop", "plan", "silent", "special", "spot", "officer", "silver", "passing",
|
|
80
|
+
"broke", "lake", "soft", "journey", "beneath", "shown", "turning", "members", "enter", "lead", "trade", "names",
|
|
81
|
+
"escape", "troops", "bill", "corner", "rule", "ladies", "species", "rock", "similar", "running", "rate", "cast",
|
|
82
|
+
"nation", "post", "likely", "simply", "orders", "dress", "fish", "minute", "birds", "europe", "passage", "surface",
|
|
83
|
+
"snow", "attack", "higher", "rise", "grand", "reply", "honour", "notice", "speech", "trying", "game", "writing",
|
|
84
|
+
"closed", "rome", "example", "aunt", "learn", "morrow", "offered", "peter", "equal", "space", "page", "wished",
|
|
85
|
+
"changed", "animal", "social", "twelve", "appears", "receive", "valley", "degree", "train", "steps", "fixed",
|
|
86
|
+
"count", "forest", "matters", "foreign", "native", "broad", "moral", "animals", "safe", "roman", "break", "pale",
|
|
87
|
+
"memory", "warm", "trust", "yellow", "sides", "main", "bird", "reading", "coast", "pain", "grave", "forget", "move",
|
|
88
|
+
"fortune", "madame", "proved", "forty", "lying", "quick", "wise", "jesus", "working", "surely", "looks", "seat",
|
|
89
|
+
"divine", "touch", "loss", "paul", "heads", "spent", "ordered", "report", "caused", "thomas", "ones", "drawing",
|
|
90
|
+
"talking", "breath", "meaning", "month", "union", "pleased", "powers", "emperor", "laugh", "affairs", "narrow",
|
|
91
|
+
"square", "science", "begin", "promise", "takes", "conduct", "serious", "thank", "curious", "pieces", "health",
|
|
92
|
+
"exactly", "ways", "upper", "decided", "nine", "stream", "wine", "engaged", "points", "amount", "named", "song",
|
|
93
|
+
"worse", "size", "castle", "crown", "mass", "liberty", "stage", "guard", "servant", "greatly", "price", "weeks",
|
|
94
|
+
"neck", "ears", "drink", "crowd", "thick", "fallen", "hills", "spoken", "golden", "capital", "sign", "spite", "terms",
|
|
95
|
+
"sake", "council", "threw", "ships", "portion", "support", "clothes", "effort", "fully", "holding", "majesty",
|
|
96
|
+
"glory", "pure", "facts", "sword", "sorry", "fate", "bread", "prove", "station", "sharp", "dream", "vain", "major",
|
|
97
|
+
"smiled", "instant", "spread", "serve", "sick", "june", "ideas", "thrown", "start", "weather", "gray", "courage",
|
|
98
|
+
"anxious", "woods", "path", "rising", "grass", "moon", "gate", "forced", "fancy", "bottom", "expect", "remains",
|
|
99
|
+
"shook", "bishop", "watched", "settled", "events", "rain", "quarter", "heat", "palace", "glance", "kingdom",
|
|
100
|
+
"papers", "aside", "worthy", "taste", "pride", "july", "opening", "daily", "leading", "wounded", "famous", "offer",
|
|
101
|
+
"group", "distant", "weight", "highest", "vast", "popular", "passion", "knowing", "allow", "sought", "lies",
|
|
102
|
+
"marked", "series", "growing", "measure", "hearts", "robert", "obliged", "western", "hence", "style", "dropped",
|
|
103
|
+
"nations", "grown", "honor", "edge", "pray", "tall", "frank", "poet", "streets", "season", "pointed", "mighty",
|
|
104
|
+
"temple", "extent", "thin", "blow", "freedom", "entire", "keeping", "prevent", "shut", "storm", "lose", "college",
|
|
105
|
+
"cost", "marry", "spirits", "worked", "colour", "ring", "listen", "fruit", "waters", "fashion", "share", "hung",
|
|
106
|
+
"proud", "fingers", "method", "served", "grow", "fort", "draw", "dollars", "quietly", "yours", "vessel", "direct",
|
|
107
|
+
"refused", "bridge", "gods", "inside", "title", "advance", "priest", "becomes", "dick", "double", "seek", "guess",
|
|
108
|
+
"spanish", "larger", "ball", "finding", "edward", "removed", "brave", "tired", "agreed", "sacred", "sons", "guns",
|
|
109
|
+
"played", "richard", "devil", "pounds", "honest", "false", "cloth", "soldier", "tongue", "smith", "wealth", "failed",
|
|
110
|
+
"height", "faces", "equally", "legs", "pocket", "twice", "volume", "prayer", "county", "notes", "louis", "unknown",
|
|
111
|
+
"delight", "rights", "nice", "taught", "coat", "dare", "slight", "rocks", "enemies", "control", "forces", "germany",
|
|
112
|
+
"kill", "process", "david", "rapidly", "comfort", "islands", "centre", "salt", "april", "windows", "noticed",
|
|
113
|
+
"truly", "color", "fields", "date", "august", "desired", "breast", "skin", "gentle", "smoke", "search", "joined",
|
|
114
|
+
"nobody", "results", "needed", "weak", "type", "stones", "follows", "theory", "shape", "waited", "telling", "relief",
|
|
115
|
+
"bearing", "bent", "mile", "dressed", "birth", "spain", "female", "clearly", "moving", "drive", "crossed", "member",
|
|
116
|
+
"saved", "teeth", "flesh", "cover", "shows", "farther", "stands", "figures", "numbers", "bodies", "supply", "motion",
|
|
117
|
+
"grey", "bell", "alive", "seized", "shadow", "produce", "touched", "driven", "talked", "empire", "sunday", "pair",
|
|
118
|
+
"fourth", "slave", "regular", "dozen", "beat", "cousin", "sand", "soil", "rough", "claim", "angry", "pity", "walking",
|
|
119
|
+
"acts", "pope", "rooms", "task", "stock", "tender", "throw", "reader", "india", "hotel", "fifteen", "arrival",
|
|
120
|
+
"plate", "stars", "willing", "stories", "saint", "amongst", "virtue", "chamber", "civil", "yards", "habit", "writer",
|
|
121
|
+
"putting", "origin", "italy", "hearing", "genius", "milk", "busy", "fool", "burning", "duties", "brain", "slow",
|
|
122
|
+
"belief", "bore", "mention", "grant", "library", "inches", "press", "calm", "total", "ride", "lands", "labor",
|
|
123
|
+
"parties", "clean", "catch", "treated", "rode", "whilst", "level", "efforts", "minds", "welcome", "wisdom", "supper",
|
|
124
|
+
"final", "mercy", "aware", "lovely", "empty", "wants", "midst", "central", "rank", "proof", "burst", "term", "farm",
|
|
125
|
+
"applied", "loud", "nearer", "harry", "closely", "kings", "explain", "deck", "noise", "hurt", "advice", "absence",
|
|
126
|
+
"circle", "objects", "secure", "plants", "parents", "falling", "base", "fellows", "sorrow", "flat", "flower",
|
|
127
|
+
"calling", "imagine", "range", "sold", "favour", "happen", "showing", "earl", "hole", "careful", "dust", "prison",
|
|
128
|
+
"useful", "accept", "firm", "sing", "port", "seated", "custom", "wore", "doors", "lifted", "edition", "tale",
|
|
129
|
+
"affair", "policy", "roof", "express", "ahead", "worship", "partly", "address", "highly", "victory", "ocean",
|
|
130
|
+
"begun", "buried", "content", "smiling", "liked", "active", "quality", "philip", "current", "divided", "growth",
|
|
131
|
+
"huge", "moments", "article", "speed", "poetry", "machine", "join", "nose", "unable", "ceased", "gained", "worn",
|
|
132
|
+
"eastern", "safety", "ages", "vessels", "hopes", "corn", "slaves", "drop", "plant", "evident", "local", "police",
|
|
133
|
+
"october", "obtain", "italian", "deeply", "dying", "wholly", "dogs", "playing", "plenty", "apart", "suffer", "maid",
|
|
134
|
+
"record", "fairly", "kindly", "terror", "drove", "ends", "sugar", "capable", "irish", "message", "chosen", "flight",
|
|
135
|
+
"reasons", "couple", "meat", "printed", "blind", "energy", "bitter", "baby", "mistake", "tower", "ireland", "trial",
|
|
136
|
+
"wear", "brief", "market", "band", "causes", "clouds", "goods", "admit", "cities", "earlier", "tells", "stated",
|
|
137
|
+
"pressed", "crime", "fought", "guide", "hoped", "list", "banks", "knight", "fleet", "pulled", "tail", "patient",
|
|
138
|
+
"mental", "boats", "kinds", "stairs", "star", "cattle", "rare", "wings", "disease", "section", "actual", "bare",
|
|
139
|
+
"extreme", "cruel", "worst", "escaped", "dance", "strike", "views", "eggs", "needs", "store", "choice", "fond",
|
|
140
|
+
"adopted", "suit", "singing", "fail", "souls", "thanks", "hidden", "shame", "faint", "shop", "older", "joseph",
|
|
141
|
+
"knees", "gently", "blessed", "yard", "cent", "grief", "male", "sooner", "january", "excited", "region", "praise",
|
|
142
|
+
"throne", "classes", "effects", "demand", "gain", "fault", "labour", "variety", "loose", "cook", "devoted", "ended",
|
|
143
|
+
"changes", "witness", "bought", "lover", "visited", "club", "sheep", "cloud", "enjoy", "asleep", "cool", "dull",
|
|
144
|
+
"painted", "arose", "armed", "dignity", "chiefly", "voices", "sail", "event", "signs", "hero", "schools", "branch",
|
|
145
|
+
"hurried", "arthur", "kitchen", "stick", "coffee", "thinks", "eager", "natives", "flying", "boston", "eternal",
|
|
146
|
+
"source", "fill", "anger", "vision", "murder", "viii", "park", "avoid", "awful", "raise", "desert", "plans", "pardon",
|
|
147
|
+
"assured", "wound", "finger", "bible", "rear", "details", "cabin", "whence", "reign", "weary", "slavery", "visible",
|
|
148
|
+
"shouted", "seldom", "skill", "hast", "throat", "reality", "leader", "egypt", "credit", "smaller", "severe", "calls",
|
|
149
|
+
"younger", "shell", "sprang", "anybody", "handed", "despair", "asking", "inch", "mystery", "manners", "knife",
|
|
150
|
+
"design", "sounds", "wishes", "stepped", "towns", "sixty", "immense", "china", "favor", "latin", "hate", "setting",
|
|
151
|
+
"excuse", "chinese", "behold", "rushed", "choose", "ease", "lights", "paused", "mission", "voyage", "gift", "bold",
|
|
152
|
+
"meal", "britain", "smooth", "mounted", "utterly", "lest", "helped", "picked", "falls", "pipe", "bones", "earnest",
|
|
153
|
+
"granted", "swept", "anxiety", "teach", "waves", "softly", "savage", "hunting", "defence", "rapid", "artist",
|
|
154
|
+
"recent", "supreme", "russian", "created", "habits", "exist", "guilty", "pages", "crew", "hell", "remark", "request",
|
|
155
|
+
"harm", "solemn", "observe", "trail", "copy", "violent", "dreams", "proceed", "luck", "steel", "sell", "temper",
|
|
156
|
+
"appeal", "hide", "fled", "belong", "triumph", "abroad", "waste", "consent", "writers", "possess", "jews", "require",
|
|
157
|
+
"priests", "railway", "founded", "pushed", "host", "solid", "maybe", "degrees", "cheeks", "mount", "ruin", "owing",
|
|
158
|
+
"fierce", "reduced", "mankind", "text", "swift", "riding", "silk", "shade", "career", "wicked", "afford", "alas",
|
|
159
|
+
"rules", "treaty", "correct", "spend", "foolish", "methods", "remarks", "horror", "shining", "enjoyed", "tribes",
|
|
160
|
+
"lack", "frame", "gets", "eagerly", "russia", "alike", "somehow", "nodded", "problem", "fever", "sees", "stern",
|
|
161
|
+
"dawn", "forgive", "flag", "managed", "fame", "travel", "estate", "cotton", "hurry", "agree", "feared", "kiss",
|
|
162
|
+
"million", "martin", "mixed", "risk", "butter", "jane", "assumed", "pause", "benefit", "unhappy", "lighted",
|
|
163
|
+
"scheme", "slept", "plainly", "forgot", "delay", "merry", "rush", "wooden", "secured", "poem", "rolled", "angel",
|
|
164
|
+
"guests", "farmer", "humble", "glanced", "shortly", "rope", "image", "lately", "africa", "fired", "retired", "bull",
|
|
165
|
+
"steam", "keen", "loving", "borne", "readily", "beloved", "average", "teacher", "attend", "flame", "area", "burned",
|
|
166
|
+
"thrust", "negro", "nurse", "spare", "fifth", "thence", "roads", "refuse", "tent", "kissed", "gates", "gaze", "fatal",
|
|
167
|
+
"israel", "verse", "scenes", "confess", "uttered", "build", "acted", "hanging", "reward", "issue", "utmost",
|
|
168
|
+
"fathers", "noted", "widow", "related", "urged", "mode", "failure", "nervous", "render", "masters", "tribe",
|
|
169
|
+
"colored", "turns", "alarm", "rivers", "using", "eleven", "rage", "hers", "lamp", "retreat", "amid", "gospel",
|
|
170
|
+
"exposed", "johnson", "marks", "tide", "emotion", "destroy", "inner", "sisters", "lion", "helen", "tied", "grounds",
|
|
171
|
+
"acid", "wheel", "issued", "invited", "gazed", "senate", "finds", "seed", "regret", "clay", "chain", "crowded",
|
|
172
|
+
"bosom", "limited", "steady", "chap", "anne", "francis", "cavalry", "freely", "charm", "faced", "ideal", "useless",
|
|
173
|
+
"warning", "shelter", "vote", "xpage", "cottage", "beast", "outer", "folks", "misery", "anyone", "expense", "firmly",
|
|
174
|
+
"eating", "lonely", "owner", "trace", "coal", "hastily", "elder", "utter", "begins", "chapel", "nights", "dared",
|
|
175
|
+
"signal", "stared", "track", "lords", "speaks", "bottle", "blame", "claims", "shoes", "sigh", "ghost", "folk",
|
|
176
|
+
"dutch", "flung", "flew", "cheek", "staff", "walter", "clever", "acting", "hungry", "poems", "cutting", "forever",
|
|
177
|
+
"exact", "haste", "dancing", "trip", "lincoln", "pull", "vice", "slipped", "thine", "loves", "permit", "mill",
|
|
178
|
+
"engine", "hollow", "seeking", "bowed", "largely", "charged", "painful", "madam", "roll", "leaf", "prayers",
|
|
179
|
+
"element", "agent", "cries", "songs", "theatre", "creek", "match", "ashamed", "runs", "aspect", "canada", "treat",
|
|
180
|
+
"legal", "arts", "corps", "noon", "nearest", "scale", "hunt", "shadows", "winds", "landed", "beings", "tiny",
|
|
181
|
+
"gardens", "bless", "clerk", "doth", "derived", "hang", "heavens", "cape", "ours", "brow", "test", "senses",
|
|
182
|
+
"opposed", "error", "driving", "nest", "headed", "groups", "princes", "feast", "admiral", "poured", "brings",
|
|
183
|
+
"pursued", "germans", "bears", "lesson", "journal", "unusual", "marched", "wing", "remove", "studied", "passes",
|
|
184
|
+
"actions", "burden", "colony", "scott", "bride", "yield", "crying", "forming", "rifle", "powder", "rested", "shake",
|
|
185
|
+
"guest", "begged", "occur", "altar", "sorts", "beaten", "wave", "papa", "sang", "depth", "aloud", "contact",
|
|
186
|
+
"intense", "marble", "poverty", "detail", "writes", "root", "metal", "jean", "existed", "ability", "purple",
|
|
187
|
+
"counsel", "lane", "wives", "sending", "heavily", "leaders", "queer", "tales", "sins", "mexico", "protect", "clock",
|
|
188
|
+
"stuff", "jones", "pine", "records", "cave", "baron", "medical", "pick", "stayed", "magic", "seventy", "pour",
|
|
189
|
+
"saddle", "sank", "dread", "deny", "wire", "rude", "holds", "strain", "autumn", "shed", "shoot", "settle", "basis",
|
|
190
|
+
"readers", "route", "beach", "safely", "oxford", "notion", "thunder", "sale", "saints", "pound", "shock", "elected",
|
|
191
|
+
"signed", "whereas", "maiden", "courts", "rarely", "wolf", "mamma", "billy", "student", "charity", "impulse",
|
|
192
|
+
"tones", "mortal", "raising", "wedding", "belongs", "chest", "sharply", "sounded", "poets", "lawyer", "hugh",
|
|
193
|
+
"plays", "awake", "landing", "debt", "alice", "venture", "idle", "uniform", "contain", "tobacco", "boots", "fears",
|
|
194
|
+
"leaned", "studies", "customs", "prize", "stir", "obey", "oath", "badly", "pursuit", "resting",
|
|
195
195
|
];
|
|
196
196
|
|
|
197
197
|
export function generatePassword(wordCount: number): string {
|
|
@@ -493,6 +493,7 @@ export function startRemoteFileServer(options: RemoteFileServerOptions): Promise
|
|
|
493
493
|
ws.send(Buffer.concat([len, h, body || EMPTY]));
|
|
494
494
|
};
|
|
495
495
|
void (async () => {
|
|
496
|
+
// TODO: Fix this code. It's some of the worst code I've ever seen. All the try-catches are bad. There should just be a single try-catch. The comparisons are bad. They're pretending like null can exist in places when it can't. We're using the any type for no reason.
|
|
496
497
|
let header: any;
|
|
497
498
|
let body: Buffer;
|
|
498
499
|
try {
|
|
@@ -608,9 +609,7 @@ export async function autocompactBulkDatabases(root: string): Promise<void> {
|
|
|
608
609
|
} catch { continue; } // no bulkDatabases2 here
|
|
609
610
|
for (const name of names) {
|
|
610
611
|
try {
|
|
611
|
-
|
|
612
|
-
const res = await getCompactor(baseDir, name).tryMergeNow();
|
|
613
|
-
if (res.merged) console.log(` [autocompact] ${name}: merged (${Date.now() - t}ms)`);
|
|
612
|
+
await getCompactor(baseDir, name).tryMergeNow();
|
|
614
613
|
} catch (e) {
|
|
615
614
|
console.warn(` [autocompact] ${name}: failed — ${(e as Error).message}`);
|
|
616
615
|
}
|