sliftutils 1.7.99 → 1.7.100
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 +74 -3
- package/package.json +1 -1
- package/storage/ArchivesDisk.d.ts +2 -1
- package/storage/ArchivesDisk.ts +9 -3
- package/storage/IArchives.d.ts +8 -0
- package/storage/IArchives.ts +8 -0
- package/storage/LogMap.d.ts +1 -1
- package/storage/TransactionFile.d.ts +56 -0
- package/storage/{LogMap.ts → TransactionFile.ts} +7 -4
- package/storage/backblaze.ts +4 -1
- package/storage/dist/ArchivesDisk.ts.cache +11 -5
- package/storage/dist/IArchives.ts.cache +2 -2
- package/storage/dist/TransactionFile.ts.cache +212 -0
- package/storage/dist/backblaze.ts.cache +6 -3
- package/storage/remoteStorage/ArchivesRemote.ts +21 -2
- package/storage/remoteStorage/blobStore.d.ts +2 -1
- package/storage/remoteStorage/blobStore.ts +7 -7
- package/storage/remoteStorage/createArchives.ts +25 -8
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +27 -5
- package/storage/remoteStorage/dist/blobStore.ts.cache +5 -5
- package/storage/remoteStorage/dist/createArchives.ts.cache +26 -9
- package/storage/remoteStorage/dist/storageController.ts.cache +4 -3
- package/storage/remoteStorage/dist/storeSync.ts.cache +71 -21
- package/storage/remoteStorage/spec.md +1 -1
- package/storage/remoteStorage/storageController.d.ts +1 -0
- package/storage/remoteStorage/storageController.ts +3 -2
- package/storage/remoteStorage/storeSync.ts +64 -19
package/index.d.ts
CHANGED
|
@@ -910,7 +910,8 @@ declare module "sliftutils/storage/ArchivesDisk" {
|
|
|
910
910
|
private collectFiles;
|
|
911
911
|
setLargeFile(config: SetLargeFileConfig): Promise<void>;
|
|
912
912
|
startLargeUpload(): Promise<string>;
|
|
913
|
-
|
|
913
|
+
/** offset makes the write POSITIONAL instead of appending: a retried part lands exactly where the failed attempt would have, so part retries are idempotent. One upload must use either appends or offsets throughout, never both - the cached handle keeps its first flags, and O_APPEND ignores the position argument. */
|
|
914
|
+
appendLargeUpload(id: string, data: Buffer, offset?: number): Promise<void>;
|
|
914
915
|
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
915
916
|
cancelLargeUpload(id: string): Promise<void>;
|
|
916
917
|
getURL(path: string): Promise<string>;
|
|
@@ -2169,6 +2170,8 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2169
2170
|
internal?: boolean;
|
|
2170
2171
|
/** Also return size-0 results (tombstones - an empty file IS a missing file) instead of treating them as absent. Off by default, matching getInfo's flag of the same name. Synchronization passes this so a DELETED file (with its write time) is distinguishable from a file that never existed. */
|
|
2171
2172
|
includeTombstones?: boolean;
|
|
2173
|
+
/** How many extra times the WHOLE operation is retried after every source in a pass failed - any error counts (the wrong-window/route markers still get their config re-resolve first). Only applies to fallback dispatch (multi-source, not noFallbacks), where it defaults to 3; the noFallbacks/write-node path already retries on its own deadline. Multi-part uploads additionally retry per part regardless of this. */
|
|
2174
|
+
retries?: number;
|
|
2172
2175
|
};
|
|
2173
2176
|
export type FindConfig = {
|
|
2174
2177
|
shallow?: boolean;
|
|
@@ -2185,12 +2188,16 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2185
2188
|
noChecks?: boolean;
|
|
2186
2189
|
/** See SetConfig.fallbacks. */
|
|
2187
2190
|
fallbacks?: boolean;
|
|
2191
|
+
/** See GetConfig.retries. */
|
|
2192
|
+
retries?: number;
|
|
2188
2193
|
};
|
|
2189
2194
|
export type GetInfoConfig = {
|
|
2190
2195
|
/** Also report size-0 entries (tombstones - an empty file IS a missing file). Off by default, so a deleted key reports undefined, matching get. Synchronization-style callers pass this when they need a deletion's write time (e.g. to compare it against a write they are about to make). */
|
|
2191
2196
|
includeTombstones?: boolean;
|
|
2192
2197
|
/** See GetConfig.noFallbacks: answer ONLY from the primary source (the one writes would target) instead of falling back across the redundant sources. */
|
|
2193
2198
|
noFallbacks?: boolean;
|
|
2199
|
+
/** See GetConfig.retries. */
|
|
2200
|
+
retries?: number;
|
|
2194
2201
|
};
|
|
2195
2202
|
export type ChangesAfterConfig = {
|
|
2196
2203
|
time: number;
|
|
@@ -2207,6 +2214,8 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2207
2214
|
internal?: boolean;
|
|
2208
2215
|
/** Writes normally go ONLY to the write node (the first current-window source covering the key), retrying it even while it is down - consistent, but unavailable when that node is. With fallbacks, the write node is still tried first, but on failure the write lands on the next current-window source covering the key (synchronization moves it to the write node later) - availability at the cost of reads possibly missing the write until it propagates. Single-source archives ignore the flag. */
|
|
2209
2216
|
fallbacks?: boolean;
|
|
2217
|
+
/** See GetConfig.retries. */
|
|
2218
|
+
retries?: number;
|
|
2210
2219
|
};
|
|
2211
2220
|
/** setLargeFile's config: a SetConfig (it IS a set - the same immutability, ordering, internal, and fallbacks rules apply) plus the stream carrying the bytes. */
|
|
2212
2221
|
export type SetLargeFileConfig = SetConfig & {
|
|
@@ -2456,7 +2465,7 @@ declare module "sliftutils/storage/LogMap" {
|
|
|
2456
2465
|
time: number;
|
|
2457
2466
|
changedAt: number;
|
|
2458
2467
|
};
|
|
2459
|
-
export declare class
|
|
2468
|
+
export declare class TransactionFile<T> {
|
|
2460
2469
|
private filePath;
|
|
2461
2470
|
constructor(filePath: string);
|
|
2462
2471
|
private values;
|
|
@@ -2622,6 +2631,66 @@ declare module "sliftutils/storage/StorageObservableAsync" {
|
|
|
2622
2631
|
|
|
2623
2632
|
}
|
|
2624
2633
|
|
|
2634
|
+
declare module "sliftutils/storage/TransactionFile" {
|
|
2635
|
+
/** A live value: what was stored, when it was written (the caller's ordering), and when we last changed it (ours). */
|
|
2636
|
+
export type LogEntry<T> = {
|
|
2637
|
+
value: T;
|
|
2638
|
+
time: number;
|
|
2639
|
+
changedAt: number;
|
|
2640
|
+
};
|
|
2641
|
+
/** A deleted key: when it was deleted, and when we learned. The value is gone; the time is the point of a tombstone. */
|
|
2642
|
+
export type LogTombstone = {
|
|
2643
|
+
time: number;
|
|
2644
|
+
changedAt: number;
|
|
2645
|
+
};
|
|
2646
|
+
export declare class TransactionFile<T> {
|
|
2647
|
+
private filePath;
|
|
2648
|
+
constructor(filePath: string);
|
|
2649
|
+
private values;
|
|
2650
|
+
private deleted;
|
|
2651
|
+
private logRecords;
|
|
2652
|
+
private pending;
|
|
2653
|
+
private flushTimer;
|
|
2654
|
+
private writeChain;
|
|
2655
|
+
/** Reads the log and replays it into memory. Every other method assumes this has finished. */
|
|
2656
|
+
load: {
|
|
2657
|
+
(): Promise<void>;
|
|
2658
|
+
reset(): void;
|
|
2659
|
+
set(newValue: Promise<void>): void;
|
|
2660
|
+
};
|
|
2661
|
+
/** The live value, or undefined when the key does not exist here (deleted included - a deletion is an absence, see getDeleted for its time). */
|
|
2662
|
+
get(key: string): LogEntry<T> | undefined;
|
|
2663
|
+
/** When the key was deleted, if it was. Absent both here and in get means we have never heard of it. */
|
|
2664
|
+
getDeleted(key: string): LogTombstone | undefined;
|
|
2665
|
+
/** The time the key last changed either way, or 0 if we have never heard of it - what a new write has to beat. */
|
|
2666
|
+
timeOf(key: string): number;
|
|
2667
|
+
/** O(1), and counts only what exists. */
|
|
2668
|
+
get size(): number;
|
|
2669
|
+
get deletedSize(): number;
|
|
2670
|
+
/** Live values only. Live, in insertion order - deleting during iteration is safe (JS skips entries removed before they are reached), which is what the passes that walk everything and prune as they go rely on. */
|
|
2671
|
+
entries(): IterableIterator<[string, LogEntry<T>]>;
|
|
2672
|
+
/** The tombstones, which is a much smaller walk than the values - so expiring them, or listing what was deleted since some time, costs what it should. */
|
|
2673
|
+
deletedEntries(): IterableIterator<[string, LogTombstone]>;
|
|
2674
|
+
/** Stores a value as of `time`. Returns false when something at least as new is already here, in which case nothing changed - an out-of-order write is not an error, it is just late. */
|
|
2675
|
+
set(key: string, value: T, time: number): boolean;
|
|
2676
|
+
/** Deletes as of `time`, keeping the tombstone. Returns false when something at least as new is already here. */
|
|
2677
|
+
delete(key: string, time: number): boolean;
|
|
2678
|
+
/** Forgets the key entirely, tombstone included - for a tombstone old enough that nobody needs to hear about the deletion any more, and for an entry that turned out never to have existed. Not a deletion: it leaves nothing behind to propagate. */
|
|
2679
|
+
purge(key: string): void;
|
|
2680
|
+
private applySet;
|
|
2681
|
+
private applyDelete;
|
|
2682
|
+
private append;
|
|
2683
|
+
private scheduleFlush;
|
|
2684
|
+
/** Writes everything pending (rewriting the log first if it has grown too far past what it describes). */
|
|
2685
|
+
flush(): Promise<void>;
|
|
2686
|
+
private directory;
|
|
2687
|
+
private write;
|
|
2688
|
+
private appendPending;
|
|
2689
|
+
private compact;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2625
2694
|
declare module "sliftutils/storage/TransactionStorage" {
|
|
2626
2695
|
/// <reference types="node" />
|
|
2627
2696
|
/// <reference types="node" />
|
|
@@ -3241,7 +3310,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3241
3310
|
import { StoreSync } from "./storeSync";
|
|
3242
3311
|
import { StoreConfig } from "./storeConfig";
|
|
3243
3312
|
export declare const WINDOW_END_FLUSH_MARGIN: number;
|
|
3244
|
-
/** What we store about a file. Its times are not in here: the index keeps those for every key, deleted ones included (see
|
|
3313
|
+
/** What we store about a file. Its times are not in here: the index keeps those for every key, deleted ones included (see TransactionFile). */
|
|
3245
3314
|
type IndexValue = {
|
|
3246
3315
|
size: number;
|
|
3247
3316
|
sourcesListIndex: number;
|
|
@@ -3425,6 +3494,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3425
3494
|
appendLargeUpload(config: {
|
|
3426
3495
|
id: string;
|
|
3427
3496
|
data: Buffer;
|
|
3497
|
+
offset?: number;
|
|
3428
3498
|
}): Promise<void>;
|
|
3429
3499
|
finishLargeUpload(config: {
|
|
3430
3500
|
id: string;
|
|
@@ -4294,6 +4364,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
4294
4364
|
uploadPart: (config: {
|
|
4295
4365
|
uploadId: string;
|
|
4296
4366
|
data: Buffer;
|
|
4367
|
+
offset?: number;
|
|
4297
4368
|
}) => Promise<void>;
|
|
4298
4369
|
finishLargeFile: (config: {
|
|
4299
4370
|
uploadId: string;
|
package/package.json
CHANGED
|
@@ -37,7 +37,8 @@ export declare class ArchivesDisk implements IArchives {
|
|
|
37
37
|
private collectFiles;
|
|
38
38
|
setLargeFile(config: SetLargeFileConfig): Promise<void>;
|
|
39
39
|
startLargeUpload(): Promise<string>;
|
|
40
|
-
|
|
40
|
+
/** offset makes the write POSITIONAL instead of appending: a retried part lands exactly where the failed attempt would have, so part retries are idempotent. One upload must use either appends or offsets throughout, never both - the cached handle keeps its first flags, and O_APPEND ignores the position argument. */
|
|
41
|
+
appendLargeUpload(id: string, data: Buffer, offset?: number): Promise<void>;
|
|
41
42
|
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
42
43
|
cancelLargeUpload(id: string): Promise<void>;
|
|
43
44
|
getURL(path: string): Promise<string>;
|
package/storage/ArchivesDisk.ts
CHANGED
|
@@ -322,13 +322,19 @@ export class ArchivesDisk implements IArchives {
|
|
|
322
322
|
this.largeUploads.set(id, { tmpPath: path.join(this.uploadsDir, `upload_${id}.tmp`) });
|
|
323
323
|
return id;
|
|
324
324
|
}
|
|
325
|
-
|
|
325
|
+
/** offset makes the write POSITIONAL instead of appending: a retried part lands exactly where the failed attempt would have, so part retries are idempotent. One upload must use either appends or offsets throughout, never both - the cached handle keeps its first flags, and O_APPEND ignores the position argument. */
|
|
326
|
+
public async appendLargeUpload(id: string, data: Buffer, offset?: number): Promise<void> {
|
|
326
327
|
let upload = this.largeUploads.get(id);
|
|
327
328
|
if (!upload) throw new Error(`Unknown large upload ${id}`);
|
|
328
329
|
const tmpPath = upload.tmpPath;
|
|
329
330
|
await this.handles.run(tmpPath, async () => {
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
if (offset === undefined) {
|
|
332
|
+
let handle = await this.handles.getHandle(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
|
|
333
|
+
await handle.write(data, 0, data.length);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
let handle = await this.handles.getHandle(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT);
|
|
337
|
+
await handle.write(data, 0, data.length, offset);
|
|
332
338
|
});
|
|
333
339
|
}
|
|
334
340
|
public async finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void> {
|
package/storage/IArchives.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export type GetConfig = {
|
|
|
75
75
|
internal?: boolean;
|
|
76
76
|
/** Also return size-0 results (tombstones - an empty file IS a missing file) instead of treating them as absent. Off by default, matching getInfo's flag of the same name. Synchronization passes this so a DELETED file (with its write time) is distinguishable from a file that never existed. */
|
|
77
77
|
includeTombstones?: boolean;
|
|
78
|
+
/** How many extra times the WHOLE operation is retried after every source in a pass failed - any error counts (the wrong-window/route markers still get their config re-resolve first). Only applies to fallback dispatch (multi-source, not noFallbacks), where it defaults to 3; the noFallbacks/write-node path already retries on its own deadline. Multi-part uploads additionally retry per part regardless of this. */
|
|
79
|
+
retries?: number;
|
|
78
80
|
};
|
|
79
81
|
export type FindConfig = {
|
|
80
82
|
shallow?: boolean;
|
|
@@ -91,12 +93,16 @@ export type DelConfig = {
|
|
|
91
93
|
noChecks?: boolean;
|
|
92
94
|
/** See SetConfig.fallbacks. */
|
|
93
95
|
fallbacks?: boolean;
|
|
96
|
+
/** See GetConfig.retries. */
|
|
97
|
+
retries?: number;
|
|
94
98
|
};
|
|
95
99
|
export type GetInfoConfig = {
|
|
96
100
|
/** Also report size-0 entries (tombstones - an empty file IS a missing file). Off by default, so a deleted key reports undefined, matching get. Synchronization-style callers pass this when they need a deletion's write time (e.g. to compare it against a write they are about to make). */
|
|
97
101
|
includeTombstones?: boolean;
|
|
98
102
|
/** See GetConfig.noFallbacks: answer ONLY from the primary source (the one writes would target) instead of falling back across the redundant sources. */
|
|
99
103
|
noFallbacks?: boolean;
|
|
104
|
+
/** See GetConfig.retries. */
|
|
105
|
+
retries?: number;
|
|
100
106
|
};
|
|
101
107
|
export type ChangesAfterConfig = {
|
|
102
108
|
time: number;
|
|
@@ -113,6 +119,8 @@ export type SetConfig = {
|
|
|
113
119
|
internal?: boolean;
|
|
114
120
|
/** Writes normally go ONLY to the write node (the first current-window source covering the key), retrying it even while it is down - consistent, but unavailable when that node is. With fallbacks, the write node is still tried first, but on failure the write lands on the next current-window source covering the key (synchronization moves it to the write node later) - availability at the cost of reads possibly missing the write until it propagates. Single-source archives ignore the flag. */
|
|
115
121
|
fallbacks?: boolean;
|
|
122
|
+
/** See GetConfig.retries. */
|
|
123
|
+
retries?: number;
|
|
116
124
|
};
|
|
117
125
|
/** setLargeFile's config: a SetConfig (it IS a set - the same immutability, ordering, internal, and fallbacks rules apply) plus the stream carrying the bytes. */
|
|
118
126
|
export type SetLargeFileConfig = SetConfig & {
|
package/storage/IArchives.ts
CHANGED
|
@@ -110,6 +110,8 @@ export type GetConfig = {
|
|
|
110
110
|
internal?: boolean;
|
|
111
111
|
/** Also return size-0 results (tombstones - an empty file IS a missing file) instead of treating them as absent. Off by default, matching getInfo's flag of the same name. Synchronization passes this so a DELETED file (with its write time) is distinguishable from a file that never existed. */
|
|
112
112
|
includeTombstones?: boolean;
|
|
113
|
+
/** How many extra times the WHOLE operation is retried after every source in a pass failed - any error counts (the wrong-window/route markers still get their config re-resolve first). Only applies to fallback dispatch (multi-source, not noFallbacks), where it defaults to 3; the noFallbacks/write-node path already retries on its own deadline. Multi-part uploads additionally retry per part regardless of this. */
|
|
114
|
+
retries?: number;
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
export type FindConfig = {
|
|
@@ -128,6 +130,8 @@ export type DelConfig = {
|
|
|
128
130
|
noChecks?: boolean;
|
|
129
131
|
/** See SetConfig.fallbacks. */
|
|
130
132
|
fallbacks?: boolean;
|
|
133
|
+
/** See GetConfig.retries. */
|
|
134
|
+
retries?: number;
|
|
131
135
|
};
|
|
132
136
|
|
|
133
137
|
export type GetInfoConfig = {
|
|
@@ -135,6 +139,8 @@ export type GetInfoConfig = {
|
|
|
135
139
|
includeTombstones?: boolean;
|
|
136
140
|
/** See GetConfig.noFallbacks: answer ONLY from the primary source (the one writes would target) instead of falling back across the redundant sources. */
|
|
137
141
|
noFallbacks?: boolean;
|
|
142
|
+
/** See GetConfig.retries. */
|
|
143
|
+
retries?: number;
|
|
138
144
|
};
|
|
139
145
|
|
|
140
146
|
export type ChangesAfterConfig = {
|
|
@@ -153,6 +159,8 @@ export type SetConfig = {
|
|
|
153
159
|
internal?: boolean;
|
|
154
160
|
/** Writes normally go ONLY to the write node (the first current-window source covering the key), retrying it even while it is down - consistent, but unavailable when that node is. With fallbacks, the write node is still tried first, but on failure the write lands on the next current-window source covering the key (synchronization moves it to the write node later) - availability at the cost of reads possibly missing the write until it propagates. Single-source archives ignore the flag. */
|
|
155
161
|
fallbacks?: boolean;
|
|
162
|
+
/** See GetConfig.retries. */
|
|
163
|
+
retries?: number;
|
|
156
164
|
};
|
|
157
165
|
|
|
158
166
|
/** setLargeFile's config: a SetConfig (it IS a set - the same immutability, ordering, internal, and fallbacks rules apply) plus the stream carrying the bytes. */
|
package/storage/LogMap.d.ts
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** A live value: what was stored, when it was written (the caller's ordering), and when we last changed it (ours). */
|
|
2
|
+
export type LogEntry<T> = {
|
|
3
|
+
value: T;
|
|
4
|
+
time: number;
|
|
5
|
+
changedAt: number;
|
|
6
|
+
};
|
|
7
|
+
/** A deleted key: when it was deleted, and when we learned. The value is gone; the time is the point of a tombstone. */
|
|
8
|
+
export type LogTombstone = {
|
|
9
|
+
time: number;
|
|
10
|
+
changedAt: number;
|
|
11
|
+
};
|
|
12
|
+
export declare class TransactionFile<T> {
|
|
13
|
+
private filePath;
|
|
14
|
+
constructor(filePath: string);
|
|
15
|
+
private values;
|
|
16
|
+
private deleted;
|
|
17
|
+
private logRecords;
|
|
18
|
+
private pending;
|
|
19
|
+
private flushTimer;
|
|
20
|
+
private writeChain;
|
|
21
|
+
/** Reads the log and replays it into memory. Every other method assumes this has finished. */
|
|
22
|
+
load: {
|
|
23
|
+
(): Promise<void>;
|
|
24
|
+
reset(): void;
|
|
25
|
+
set(newValue: Promise<void>): void;
|
|
26
|
+
};
|
|
27
|
+
/** The live value, or undefined when the key does not exist here (deleted included - a deletion is an absence, see getDeleted for its time). */
|
|
28
|
+
get(key: string): LogEntry<T> | undefined;
|
|
29
|
+
/** When the key was deleted, if it was. Absent both here and in get means we have never heard of it. */
|
|
30
|
+
getDeleted(key: string): LogTombstone | undefined;
|
|
31
|
+
/** The time the key last changed either way, or 0 if we have never heard of it - what a new write has to beat. */
|
|
32
|
+
timeOf(key: string): number;
|
|
33
|
+
/** O(1), and counts only what exists. */
|
|
34
|
+
get size(): number;
|
|
35
|
+
get deletedSize(): number;
|
|
36
|
+
/** Live values only. Live, in insertion order - deleting during iteration is safe (JS skips entries removed before they are reached), which is what the passes that walk everything and prune as they go rely on. */
|
|
37
|
+
entries(): IterableIterator<[string, LogEntry<T>]>;
|
|
38
|
+
/** The tombstones, which is a much smaller walk than the values - so expiring them, or listing what was deleted since some time, costs what it should. */
|
|
39
|
+
deletedEntries(): IterableIterator<[string, LogTombstone]>;
|
|
40
|
+
/** Stores a value as of `time`. Returns false when something at least as new is already here, in which case nothing changed - an out-of-order write is not an error, it is just late. */
|
|
41
|
+
set(key: string, value: T, time: number): boolean;
|
|
42
|
+
/** Deletes as of `time`, keeping the tombstone. Returns false when something at least as new is already here. */
|
|
43
|
+
delete(key: string, time: number): boolean;
|
|
44
|
+
/** Forgets the key entirely, tombstone included - for a tombstone old enough that nobody needs to hear about the deletion any more, and for an entry that turned out never to have existed. Not a deletion: it leaves nothing behind to propagate. */
|
|
45
|
+
purge(key: string): void;
|
|
46
|
+
private applySet;
|
|
47
|
+
private applyDelete;
|
|
48
|
+
private append;
|
|
49
|
+
private scheduleFlush;
|
|
50
|
+
/** Writes everything pending (rewriting the log first if it has grown too far past what it describes). */
|
|
51
|
+
flush(): Promise<void>;
|
|
52
|
+
private directory;
|
|
53
|
+
private write;
|
|
54
|
+
private appendPending;
|
|
55
|
+
private compact;
|
|
56
|
+
}
|
|
@@ -18,18 +18,21 @@ export type LogEntry<T> = { value: T; time: number; changedAt: number };
|
|
|
18
18
|
/** A deleted key: when it was deleted, and when we learned. The value is gone; the time is the point of a tombstone. */
|
|
19
19
|
export type LogTombstone = { time: number; changedAt: number };
|
|
20
20
|
|
|
21
|
-
// One line of the log. JSON.stringify escapes newlines inside keys and values, so a record can never contain the separator.
|
|
21
|
+
// One line of the log, with single-letter field names because there is one record per set/delete/purge ever made and the field names are most of a record's overhead. JSON.stringify escapes newlines inside keys and values, so a record can never contain the separator.
|
|
22
22
|
type LogRecord<T> = {
|
|
23
|
+
// key
|
|
23
24
|
k: string;
|
|
24
|
-
//
|
|
25
|
+
// time, lastModified time of the file, or the delete time, or the purge time
|
|
25
26
|
t: number;
|
|
26
|
-
//
|
|
27
|
+
// value
|
|
27
28
|
v?: T;
|
|
29
|
+
// deleted, the file on disk is deleted, preserved when we compact
|
|
28
30
|
d?: 1;
|
|
31
|
+
// purged, the file on disk is deleted, not preserved when we compact
|
|
29
32
|
p?: 1;
|
|
30
33
|
};
|
|
31
34
|
|
|
32
|
-
export class
|
|
35
|
+
export class TransactionFile<T> {
|
|
33
36
|
constructor(private filePath: string) { }
|
|
34
37
|
|
|
35
38
|
private values = new Map<string, LogEntry<T>>();
|
package/storage/backblaze.ts
CHANGED
|
@@ -629,7 +629,10 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
|
|
632
|
+
// EVERYTHING else retries too (bounded by `retries`): backblaze fails a small fraction of requests with ever-new error shapes, and aborting a multi-part upload (or a full sync) on one unmatched error costs the whole transfer. A genuinely permanent error just pays the few retry delays before surfacing.
|
|
633
|
+
console.warn(`[${context}] Unrecognized error, retrying in 5s (${retries} left): ${err.message}`);
|
|
634
|
+
await delay(5000);
|
|
635
|
+
return this.apiRetryLogic(context, fnc, retries - 1);
|
|
633
636
|
}
|
|
634
637
|
}
|
|
635
638
|
|