sliftutils 1.7.48 → 1.7.50
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 +99 -38
- package/package.json +1 -1
- package/storage/ArchivesDisk.d.ts +5 -5
- package/storage/ArchivesDisk.ts +19 -5
- package/storage/IArchives.d.ts +36 -7
- package/storage/IArchives.ts +76 -8
- package/storage/backblaze.d.ts +4 -4
- package/storage/backblaze.ts +89 -36
- package/storage/dist/ArchivesDisk.ts.cache +8 -2
- package/storage/dist/IArchives.ts.cache +2 -2
- package/storage/dist/backblaze.ts.cache +86 -36
- package/storage/remoteStorage/ArchivesRemote.d.ts +4 -5
- package/storage/remoteStorage/ArchivesRemote.ts +7 -7
- package/storage/remoteStorage/ArchivesUrl.d.ts +2 -1
- package/storage/remoteStorage/ArchivesUrl.ts +13 -3
- package/storage/remoteStorage/blobStore.d.ts +15 -6
- package/storage/remoteStorage/blobStore.ts +198 -115
- package/storage/remoteStorage/createArchives.d.ts +4 -5
- package/storage/remoteStorage/createArchives.ts +6 -11
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +5 -5
- package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +15 -4
- package/storage/remoteStorage/dist/blobStore.ts.cache +194 -108
- package/storage/remoteStorage/dist/createArchives.ts.cache +4 -9
- package/storage/remoteStorage/dist/remoteConfig.ts.cache +15 -3
- package/storage/remoteStorage/dist/sourcesList.ts.cache +79 -0
- package/storage/remoteStorage/dist/storageController.ts.cache +7 -10
- package/storage/remoteStorage/dist/storageServerState.ts.cache +50 -18
- package/storage/remoteStorage/remoteConfig.d.ts +3 -1
- package/storage/remoteStorage/remoteConfig.ts +11 -1
- package/storage/remoteStorage/sourcesList.d.ts +15 -0
- package/storage/remoteStorage/sourcesList.ts +69 -0
- package/storage/remoteStorage/storageController.d.ts +4 -4
- package/storage/remoteStorage/storageController.ts +11 -14
- package/storage/remoteStorage/storageServerState.d.ts +3 -0
- package/storage/remoteStorage/storageServerState.ts +40 -15
package/index.d.ts
CHANGED
|
@@ -858,7 +858,7 @@ declare module "sliftutils/render-utils/observer" {
|
|
|
858
858
|
declare module "sliftutils/storage/ArchivesDisk" {
|
|
859
859
|
/// <reference types="node" />
|
|
860
860
|
/// <reference types="node" />
|
|
861
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig } from "./IArchives";
|
|
861
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig, SetConfig } from "./IArchives";
|
|
862
862
|
export declare class ArchivesDisk implements IArchives {
|
|
863
863
|
private folder;
|
|
864
864
|
constructor(folder: string);
|
|
@@ -874,11 +874,10 @@ declare module "sliftutils/storage/ArchivesDisk" {
|
|
|
874
874
|
};
|
|
875
875
|
getDebugName(): string;
|
|
876
876
|
getConfig(): Promise<ArchivesConfig>;
|
|
877
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
877
878
|
hasWriteAccess(): Promise<boolean>;
|
|
878
879
|
private filePath;
|
|
879
|
-
set(key: string, data: Buffer, config?:
|
|
880
|
-
lastModified?: number;
|
|
881
|
-
}): Promise<string>;
|
|
880
|
+
set(key: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
882
881
|
del(key: string): Promise<void>;
|
|
883
882
|
get(key: string, config?: {
|
|
884
883
|
range?: {
|
|
@@ -911,11 +910,12 @@ declare module "sliftutils/storage/ArchivesDisk" {
|
|
|
911
910
|
private collectFiles;
|
|
912
911
|
setLargeFile(config: {
|
|
913
912
|
path: string;
|
|
913
|
+
lastModified?: number;
|
|
914
914
|
getNextData(): Promise<Buffer | undefined>;
|
|
915
915
|
}): Promise<void>;
|
|
916
916
|
startLargeUpload(): Promise<string>;
|
|
917
917
|
appendLargeUpload(id: string, data: Buffer): Promise<void>;
|
|
918
|
-
finishLargeUpload(id: string, key: string): Promise<void>;
|
|
918
|
+
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
919
919
|
cancelLargeUpload(id: string): Promise<void>;
|
|
920
920
|
getURL(path: string): Promise<string>;
|
|
921
921
|
}
|
|
@@ -2149,6 +2149,18 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2149
2149
|
allowedOrigins?: string[];
|
|
2150
2150
|
};
|
|
2151
2151
|
export declare const FULL_VALID_WINDOW: [number, number];
|
|
2152
|
+
export type ChangesAfterConfig = {
|
|
2153
|
+
time: number;
|
|
2154
|
+
/** Only keys routing into one of these [start, end) ranges. Only scanning passes this - it lets a store syncing a partial shard ask for just its slice. */
|
|
2155
|
+
routes?: [number, number][];
|
|
2156
|
+
};
|
|
2157
|
+
export type SetConfig = {
|
|
2158
|
+
lastModified?: number;
|
|
2159
|
+
/** Makes the write acceptable on immutable targets: an existing path is simply kept (immutability wins - nothing is overwritten) instead of the write throwing. Requires lastModified. Synchronization MUST pass this on every push - a plain set throws on immutable targets, which would abort reconciliation whenever one source in a chain is immutable. */
|
|
2160
|
+
forceSetImmutable?: boolean;
|
|
2161
|
+
/** Skips the target-side safety reads around the write (backblaze: the pre-write getInfo comparison and the post-upload existence poll). For writers whose own bookkeeping already decides what to write and orders it by write time (BlobStore's index-driven writes and synchronization), those reads are pure extra API calls - but the default stays checked, because other users of the raw backends rely on the checks. */
|
|
2162
|
+
noChecks?: boolean;
|
|
2163
|
+
};
|
|
2152
2164
|
export type ArchiveFileInfo = {
|
|
2153
2165
|
path: string;
|
|
2154
2166
|
createTime: number;
|
|
@@ -2180,6 +2192,8 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2180
2192
|
};
|
|
2181
2193
|
export type ArchivesSource = {
|
|
2182
2194
|
source: IArchives;
|
|
2195
|
+
/** The persistent identity of the endpoint: its routing URL (hosted/backblaze), or the disk folder path for the base disk source. The store persists this (via its append-only sources list) as IndexEntry.sourcesListIndex, so it must mean the same endpoint forever. */
|
|
2196
|
+
url: string;
|
|
2183
2197
|
validWindow: [number, number];
|
|
2184
2198
|
route?: [number, number];
|
|
2185
2199
|
noFullSync?: boolean;
|
|
@@ -2190,6 +2204,19 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2190
2204
|
export declare const FULL_ROUTE: [number, number];
|
|
2191
2205
|
export declare const VARIABLE_SHARD = "VARIABLE_SHARD_f0234jfah08fgyhfgyssdds83nmp";
|
|
2192
2206
|
export declare function windowAcceptsWrites(validWindow: [number, number] | undefined): boolean;
|
|
2207
|
+
/** Copies one file between two archives. Small files go as a single get2+set; past LARGE_COPY_THRESHOLD the copy streams through setLargeFile in LARGE_COPY_CHUNK ranged reads, so the whole file is never in memory. size/writeTime usually come from the caller's metadata scan; when either is omitted, getInfo fills them in. Returns the copied file's info, or undefined when the source doesn't have the file. */
|
|
2208
|
+
export declare function copyArchiveFile(config: {
|
|
2209
|
+
from: IArchives;
|
|
2210
|
+
to: IArchives;
|
|
2211
|
+
path: string;
|
|
2212
|
+
size?: number;
|
|
2213
|
+
writeTime?: number;
|
|
2214
|
+
forceSetImmutable?: boolean;
|
|
2215
|
+
noChecks?: boolean;
|
|
2216
|
+
}): Promise<{
|
|
2217
|
+
writeTime: number;
|
|
2218
|
+
size: number;
|
|
2219
|
+
} | undefined>;
|
|
2193
2220
|
export type ArchivesSyncSourceStatus = {
|
|
2194
2221
|
debugName: string;
|
|
2195
2222
|
validWindow: [number, number];
|
|
@@ -2233,13 +2260,12 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2233
2260
|
* VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency,
|
|
2234
2261
|
* see ArchivesChain) and the caller needs the returned key to ever read the value back.
|
|
2235
2262
|
*/
|
|
2236
|
-
set(fileName: string, data: Buffer, config?:
|
|
2237
|
-
lastModified?: number;
|
|
2238
|
-
}): Promise<string>;
|
|
2263
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2239
2264
|
del(fileName: string): Promise<void>;
|
|
2240
|
-
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
2265
|
+
/** Streams a file too large to hold in memory. getNextData returns undefined when done. lastModified stamps the finished file like set's (synchronized copies need it to keep write ordering); backends that stamp their own times (backblaze) accept and ignore it. */
|
|
2241
2266
|
setLargeFile(config: {
|
|
2242
2267
|
path: string;
|
|
2268
|
+
lastModified?: number;
|
|
2243
2269
|
getNextData(): Promise<Buffer | undefined>;
|
|
2244
2270
|
}): Promise<void>;
|
|
2245
2271
|
/** writeTime is the last-write time — see ArchiveFileInfo.createTime, which is the same value. */
|
|
@@ -2260,10 +2286,13 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2260
2286
|
/** The bucket's configuration, which tells whether the optional functions are supported. */
|
|
2261
2287
|
getConfig(): Promise<ArchivesConfig>;
|
|
2262
2288
|
/**
|
|
2263
|
-
* All files changed after
|
|
2264
|
-
*
|
|
2289
|
+
* All files changed after config.time, optionally restricted to keys routing into one of
|
|
2290
|
+
* config.routes (used by scanning, so partially-overlapping shards only receive their slice).
|
|
2291
|
+
* When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE
|
|
2292
|
+
* reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo
|
|
2293
|
+
* listing filtered in memory - correct, but no cheaper than the listing itself.
|
|
2265
2294
|
*/
|
|
2266
|
-
|
|
2295
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2267
2296
|
/** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */
|
|
2268
2297
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
2269
2298
|
}
|
|
@@ -2526,7 +2555,7 @@ declare module "sliftutils/storage/TransactionStorage" {
|
|
|
2526
2555
|
declare module "sliftutils/storage/backblaze" {
|
|
2527
2556
|
/// <reference types="node" />
|
|
2528
2557
|
/// <reference types="node" />
|
|
2529
|
-
import { IArchives, ArchivesConfig } from "./IArchives";
|
|
2558
|
+
import { IArchives, ArchivesConfig, ChangesAfterConfig, ArchiveFileInfo, SetConfig } from "./IArchives";
|
|
2530
2559
|
export declare class ArchivesBackblaze implements IArchives {
|
|
2531
2560
|
private config;
|
|
2532
2561
|
constructor(config: {
|
|
@@ -2565,12 +2594,11 @@ declare module "sliftutils/storage/backblaze" {
|
|
|
2565
2594
|
} | undefined>;
|
|
2566
2595
|
getConfig(): Promise<ArchivesConfig>;
|
|
2567
2596
|
hasWriteAccess(): Promise<boolean>;
|
|
2568
|
-
set(fileName: string, data: Buffer, config?:
|
|
2569
|
-
lastModified?: number;
|
|
2570
|
-
}): Promise<string>;
|
|
2597
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2571
2598
|
del(fileName: string): Promise<void>;
|
|
2572
2599
|
setLargeFile(config: {
|
|
2573
2600
|
path: string;
|
|
2601
|
+
lastModified?: number;
|
|
2574
2602
|
getNextData(): Promise<Buffer | undefined>;
|
|
2575
2603
|
}): Promise<void>;
|
|
2576
2604
|
getInfo(fileName: string): Promise<{
|
|
@@ -2581,6 +2609,7 @@ declare module "sliftutils/storage/backblaze" {
|
|
|
2581
2609
|
shallow?: boolean;
|
|
2582
2610
|
type: "files" | "folders";
|
|
2583
2611
|
}): Promise<string[]>;
|
|
2612
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2584
2613
|
findInfo(prefix: string, config?: {
|
|
2585
2614
|
shallow?: boolean;
|
|
2586
2615
|
type: "files" | "folders";
|
|
@@ -2841,7 +2870,7 @@ declare module "sliftutils/storage/remoteFileStorage" {
|
|
|
2841
2870
|
declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
2842
2871
|
/// <reference types="node" />
|
|
2843
2872
|
/// <reference types="node" />
|
|
2844
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
2873
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, SetConfig } from "../IArchives";
|
|
2845
2874
|
export type ArchivesRemoteConfig = {
|
|
2846
2875
|
url: string;
|
|
2847
2876
|
waitForAccess?: boolean;
|
|
@@ -2896,9 +2925,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2896
2925
|
writeTime: number;
|
|
2897
2926
|
size: number;
|
|
2898
2927
|
} | undefined>;
|
|
2899
|
-
set(fileName: string, data: Buffer, config?:
|
|
2900
|
-
lastModified?: number;
|
|
2901
|
-
}): Promise<string>;
|
|
2928
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2902
2929
|
del(fileName: string): Promise<void>;
|
|
2903
2930
|
getInfo(fileName: string): Promise<{
|
|
2904
2931
|
writeTime: number;
|
|
@@ -2912,11 +2939,12 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2912
2939
|
shallow?: boolean;
|
|
2913
2940
|
type: "files" | "folders";
|
|
2914
2941
|
}): Promise<string[]>;
|
|
2915
|
-
|
|
2942
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2916
2943
|
getConfig(): Promise<ArchivesConfig>;
|
|
2917
2944
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
2918
2945
|
setLargeFile(config: {
|
|
2919
2946
|
path: string;
|
|
2947
|
+
lastModified?: number;
|
|
2920
2948
|
getNextData(): Promise<Buffer | undefined>;
|
|
2921
2949
|
}): Promise<void>;
|
|
2922
2950
|
getURL(path: string): Promise<string>;
|
|
@@ -2927,7 +2955,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2927
2955
|
declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
|
|
2928
2956
|
/// <reference types="node" />
|
|
2929
2957
|
/// <reference types="node" />
|
|
2930
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig } from "../IArchives";
|
|
2958
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig } from "../IArchives";
|
|
2931
2959
|
export declare class ArchivesUrl implements IArchives {
|
|
2932
2960
|
private base;
|
|
2933
2961
|
constructor(base: string);
|
|
@@ -2969,6 +2997,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
|
|
|
2969
2997
|
shallow?: boolean;
|
|
2970
2998
|
type: "files" | "folders";
|
|
2971
2999
|
}): Promise<ArchiveFileInfo[]>;
|
|
3000
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2972
3001
|
getURL(path: string): Promise<string>;
|
|
2973
3002
|
getConfig(): Promise<ArchivesConfig>;
|
|
2974
3003
|
hasWriteAccess(): Promise<boolean>;
|
|
@@ -2984,7 +3013,7 @@ declare module "sliftutils/storage/remoteStorage/accessPage" {
|
|
|
2984
3013
|
declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
2985
3014
|
/// <reference types="node" />
|
|
2986
3015
|
/// <reference types="node" />
|
|
2987
|
-
import { IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, SyncActivity } from "../IArchives";
|
|
3016
|
+
import { IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, ChangesAfterConfig, SyncActivity } from "../IArchives";
|
|
2988
3017
|
export declare const DEFAULT_FAST_WRITE_DELAY: number;
|
|
2989
3018
|
export declare const WINDOW_END_FLUSH_MARGIN: number;
|
|
2990
3019
|
export type WriteConfig = {
|
|
@@ -3019,7 +3048,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3019
3048
|
shallow?: boolean;
|
|
3020
3049
|
type?: "files" | "folders";
|
|
3021
3050
|
}): Promise<ArchiveFileInfo[]>;
|
|
3022
|
-
|
|
3051
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3023
3052
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
3024
3053
|
getSyncProgress?(): {
|
|
3025
3054
|
index: {
|
|
@@ -3045,11 +3074,12 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3045
3074
|
}>;
|
|
3046
3075
|
startLargeUpload(): Promise<string>;
|
|
3047
3076
|
appendLargeUpload(id: string, data: Buffer): Promise<void>;
|
|
3048
|
-
finishLargeUpload(id: string, key: string): Promise<void>;
|
|
3077
|
+
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
3049
3078
|
cancelLargeUpload(id: string): Promise<void>;
|
|
3050
3079
|
};
|
|
3051
3080
|
export type BlobSourceSpec = {
|
|
3052
3081
|
identity: string;
|
|
3082
|
+
url: string;
|
|
3053
3083
|
validWindow: [number, number];
|
|
3054
3084
|
route?: [number, number];
|
|
3055
3085
|
noFullSync?: boolean;
|
|
@@ -3063,6 +3093,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3063
3093
|
onIndexChanged?: ((key: string) => void) | undefined;
|
|
3064
3094
|
readerDiskLimit?: number | undefined;
|
|
3065
3095
|
onWriteCounted?: ((kind: "original" | "flushed", bytes: number) => void) | undefined;
|
|
3096
|
+
resolveSourceUrl?: ((url: string) => IArchives) | undefined;
|
|
3066
3097
|
} | undefined);
|
|
3067
3098
|
private stopped;
|
|
3068
3099
|
private index;
|
|
@@ -3076,7 +3107,14 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3076
3107
|
private overlay;
|
|
3077
3108
|
private sourceStates;
|
|
3078
3109
|
private syncStarted;
|
|
3110
|
+
private sourcesList;
|
|
3111
|
+
private slotSourcesListIndexes;
|
|
3112
|
+
private slotRegistrations;
|
|
3079
3113
|
private isLive;
|
|
3114
|
+
private registerSlot;
|
|
3115
|
+
private sourcesListIndexOfSlot;
|
|
3116
|
+
private slotForSourcesListIndex;
|
|
3117
|
+
private getEntryHolder;
|
|
3080
3118
|
init: {
|
|
3081
3119
|
(): Promise<void>;
|
|
3082
3120
|
reset(): void;
|
|
@@ -3125,7 +3163,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3125
3163
|
private runSourceSync;
|
|
3126
3164
|
private scanSource;
|
|
3127
3165
|
private reconcileSource;
|
|
3128
|
-
private
|
|
3166
|
+
private updateScanIndex;
|
|
3129
3167
|
private pollChanges;
|
|
3130
3168
|
private copySourceFiles;
|
|
3131
3169
|
private waitForRequiredScans;
|
|
@@ -3160,12 +3198,12 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3160
3198
|
shallow?: boolean;
|
|
3161
3199
|
type?: "files" | "folders";
|
|
3162
3200
|
}): Promise<ArchiveFileInfo[]>;
|
|
3163
|
-
|
|
3201
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3164
3202
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
3165
3203
|
private getDiskSource;
|
|
3166
3204
|
startLargeUpload(): Promise<string>;
|
|
3167
3205
|
appendLargeUpload(id: string, data: Buffer): Promise<void>;
|
|
3168
|
-
finishLargeUpload(id: string, key: string): Promise<void>;
|
|
3206
|
+
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
3169
3207
|
cancelLargeUpload(id: string): Promise<void>;
|
|
3170
3208
|
private flushOverlay;
|
|
3171
3209
|
private evicting;
|
|
@@ -3183,7 +3221,7 @@ declare module "sliftutils/storage/remoteStorage/cliArgs" {
|
|
|
3183
3221
|
declare module "sliftutils/storage/remoteStorage/createArchives" {
|
|
3184
3222
|
/// <reference types="node" />
|
|
3185
3223
|
/// <reference types="node" />
|
|
3186
|
-
import { IArchives, RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
3224
|
+
import { IArchives, RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, SetConfig } from "../IArchives";
|
|
3187
3225
|
import { ServerBucketInfo, ActiveBucketInfo } from "./storageServerState";
|
|
3188
3226
|
/** The address, port, account, and bucket name a bucket routing URL addresses. Throws when the URL isn't a hosted bucket routing URL (https://host:port/file/<account>/<bucketName>/storage/storagerouting.json). */
|
|
3189
3227
|
export { parseHostedUrl, parseBackblazeUrl, getBucketBaseUrl } from "./remoteConfig";
|
|
@@ -3260,18 +3298,17 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
|
|
|
3260
3298
|
shallow?: boolean;
|
|
3261
3299
|
type: "files" | "folders";
|
|
3262
3300
|
}): Promise<ArchiveFileInfo[]>;
|
|
3263
|
-
|
|
3301
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3264
3302
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
3265
3303
|
getConfig(): Promise<ArchivesConfig>;
|
|
3266
3304
|
hasWriteAccess(): Promise<boolean>;
|
|
3267
|
-
set(fileName: string, data: Buffer, config?:
|
|
3268
|
-
lastModified?: number;
|
|
3269
|
-
}): Promise<string>;
|
|
3305
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
3270
3306
|
private setRoutingConfig;
|
|
3271
3307
|
del(fileName: string): Promise<void>;
|
|
3272
3308
|
private setVariableShard;
|
|
3273
3309
|
setLargeFile(config: {
|
|
3274
3310
|
path: string;
|
|
3311
|
+
lastModified?: number;
|
|
3275
3312
|
getNextData(): Promise<Buffer | undefined>;
|
|
3276
3313
|
}): Promise<void>;
|
|
3277
3314
|
getURL(path: string): Promise<string>;
|
|
@@ -3376,12 +3413,14 @@ declare module "sliftutils/storage/remoteStorage/productionEnv" {
|
|
|
3376
3413
|
declare module "sliftutils/storage/remoteStorage/remoteConfig" {
|
|
3377
3414
|
/// <reference types="node" />
|
|
3378
3415
|
/// <reference types="node" />
|
|
3379
|
-
import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "../IArchives";
|
|
3416
|
+
import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ChangesAfterConfig } from "../IArchives";
|
|
3380
3417
|
export declare const ROUTING_FILE = "storage/storagerouting.json";
|
|
3381
3418
|
/** The variable-shard route override embedded in the key ("<sentinel>_<value>", see VARIABLE_SHARD), or undefined when the key has no sentinel or the sentinel has no value yet. */
|
|
3382
3419
|
export declare function parseVariableRoute(key: string): number | undefined;
|
|
3383
3420
|
/** Where a key routes in [0, 1). A materialized variable-shard suffix completely overrides the hash. */
|
|
3384
3421
|
export declare function getRoute(key: string): number;
|
|
3422
|
+
/** The in-memory getChangesAfter2 emulation, for backends without a native change feed: a full listing filtered down to files written after config.time whose keys route into config.routes. */
|
|
3423
|
+
export declare function filterChanges(files: ArchiveFileInfo[], config: ChangesAfterConfig): ArchiveFileInfo[];
|
|
3385
3424
|
export declare function routeContains(route: [number, number] | undefined, value: number): boolean;
|
|
3386
3425
|
export declare function routesOverlap(a: [number, number] | undefined, b: [number, number] | undefined): boolean;
|
|
3387
3426
|
/** The overlap of two route ranges, or undefined when they don't overlap. */
|
|
@@ -3460,6 +3499,25 @@ declare module "sliftutils/storage/remoteStorage/sourceWrapper" {
|
|
|
3460
3499
|
|
|
3461
3500
|
}
|
|
3462
3501
|
|
|
3502
|
+
declare module "sliftutils/storage/remoteStorage/sourcesList" {
|
|
3503
|
+
export declare class SourcesList {
|
|
3504
|
+
private filePath;
|
|
3505
|
+
constructor(filePath: string);
|
|
3506
|
+
private urls;
|
|
3507
|
+
private indexes;
|
|
3508
|
+
private endsClean;
|
|
3509
|
+
private lastReload;
|
|
3510
|
+
private appendQueue;
|
|
3511
|
+
private load;
|
|
3512
|
+
getUrl(sourcesListIndex: number): string | undefined;
|
|
3513
|
+
/** For a sourcesListIndex beyond our in-memory list (another process appended since we read): re-reads the file, at most once per RELOAD_THROTTLE. Returning undefined can therefore mean "throttled", not "definitely absent" - never treat it as proof the index is bogus. */
|
|
3514
|
+
getUrlReloading(sourcesListIndex: number): Promise<string | undefined>;
|
|
3515
|
+
/** The sourcesListIndex of the url, appending it if it is new. Appends are serialized within this process; before each append the file is re-read, so appends by other processes are picked up instead of duplicated. */
|
|
3516
|
+
ensure(url: string): Promise<number>;
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3463
3521
|
declare module "sliftutils/storage/remoteStorage/storageClientController" {
|
|
3464
3522
|
/** Subscribe to server-pushed routing change notifications. Returns the unsubscribe function. */
|
|
3465
3523
|
export declare function onServerRoutingChanged(listener: () => void): () => void;
|
|
@@ -3472,7 +3530,7 @@ declare module "sliftutils/storage/remoteStorage/storageClientController" {
|
|
|
3472
3530
|
declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
3473
3531
|
/// <reference types="node" />
|
|
3474
3532
|
/// <reference types="node" />
|
|
3475
|
-
import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
3533
|
+
import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig } from "../IArchives";
|
|
3476
3534
|
import { ServerBucketInfo, ActiveBucketInfo } from "./storageServerState";
|
|
3477
3535
|
export declare const REMOTE_STORAGE_CLASS_GUID = "RemoteStorageController-b7e42a91";
|
|
3478
3536
|
export declare const STORAGE_AUTH_PURPOSE = "remoteStorage-auth-1";
|
|
@@ -3542,7 +3600,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
3542
3600
|
writeTime: number;
|
|
3543
3601
|
size: number;
|
|
3544
3602
|
} | undefined>;
|
|
3545
|
-
set: (account: string, bucketName: string, path: string, data: Buffer, lastModified?: number) => Promise<void>;
|
|
3603
|
+
set: (account: string, bucketName: string, path: string, data: Buffer, lastModified?: number, forceSetImmutable?: boolean) => Promise<void>;
|
|
3546
3604
|
del: (account: string, bucketName: string, path: string) => Promise<void>;
|
|
3547
3605
|
getInfo: (account: string, bucketName: string, path: string) => Promise<{
|
|
3548
3606
|
writeTime: number;
|
|
@@ -3552,7 +3610,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
3552
3610
|
shallow?: boolean;
|
|
3553
3611
|
type?: "files" | "folders";
|
|
3554
3612
|
}) => Promise<ArchiveFileInfo[]>;
|
|
3555
|
-
|
|
3613
|
+
getChangesAfter2: (account: string, bucketName: string, config: ChangesAfterConfig) => Promise<ArchiveFileInfo[]>;
|
|
3556
3614
|
getArchivesConfig: (account: string, bucketName: string) => Promise<ArchivesConfig>;
|
|
3557
3615
|
listBuckets: (account: string) => Promise<ServerBucketInfo[]>;
|
|
3558
3616
|
getActiveBucket: (account: string, bucketName: string) => Promise<ActiveBucketInfo | string>;
|
|
@@ -3570,7 +3628,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
3570
3628
|
}[];
|
|
3571
3629
|
} | undefined>;
|
|
3572
3630
|
getSyncStatus: (account: string, bucketName: string) => Promise<ArchivesSyncStatus>;
|
|
3573
|
-
startLargeFile: (account: string, bucketName: string, path: string) => Promise<string>;
|
|
3631
|
+
startLargeFile: (account: string, bucketName: string, path: string, lastModified?: number) => Promise<string>;
|
|
3574
3632
|
uploadPart: (uploadId: string, data: Buffer) => Promise<void>;
|
|
3575
3633
|
finishLargeFile: (uploadId: string) => Promise<void>;
|
|
3576
3634
|
cancelLargeFile: (uploadId: string) => Promise<void>;
|
|
@@ -3647,10 +3705,13 @@ declare module "sliftutils/storage/remoteStorage/storageServerState" {
|
|
|
3647
3705
|
};
|
|
3648
3706
|
export declare function addExtraListenPort(port: number): void;
|
|
3649
3707
|
export declare function removeExtraListenPort(port: number): void;
|
|
3708
|
+
/** A cached IArchives for a persisted source identity: a routing URL (hosted/backblaze) or a disk folder path - the form BlobStore's sources list stores. Configuration (valid windows, routes) decides WHEN a source should be used; for reading bytes the index says a source holds, the URL alone is enough - even for sources no longer in any config. */
|
|
3709
|
+
export declare function resolveSourceArchives(url: string): IArchives;
|
|
3650
3710
|
export declare function getLoadedBucket(account: string, bucketName: string): Promise<LoadedBucket | undefined>;
|
|
3651
3711
|
export declare function assertMutable(bucket: LoadedBucket, filePath: string, writeTime: number): Promise<void>;
|
|
3652
3712
|
export declare function writeBucketFile(account: string, bucketName: string, filePath: string, data: Buffer, config?: {
|
|
3653
3713
|
lastModified?: number;
|
|
3714
|
+
forceSetImmutable?: boolean;
|
|
3654
3715
|
}): Promise<void>;
|
|
3655
3716
|
export declare function getBucketConfig(bucket: LoadedBucket): ArchivesConfig;
|
|
3656
3717
|
/** Which buckets this process currently has loaded - what a deploy successor asks its predecessor for, so it activates exactly the buckets that are actually in use. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig } from "./IArchives";
|
|
3
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig, SetConfig } from "./IArchives";
|
|
4
4
|
export declare class ArchivesDisk implements IArchives {
|
|
5
5
|
private folder;
|
|
6
6
|
constructor(folder: string);
|
|
@@ -16,11 +16,10 @@ export declare class ArchivesDisk implements IArchives {
|
|
|
16
16
|
};
|
|
17
17
|
getDebugName(): string;
|
|
18
18
|
getConfig(): Promise<ArchivesConfig>;
|
|
19
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
19
20
|
hasWriteAccess(): Promise<boolean>;
|
|
20
21
|
private filePath;
|
|
21
|
-
set(key: string, data: Buffer, config?:
|
|
22
|
-
lastModified?: number;
|
|
23
|
-
}): Promise<string>;
|
|
22
|
+
set(key: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
24
23
|
del(key: string): Promise<void>;
|
|
25
24
|
get(key: string, config?: {
|
|
26
25
|
range?: {
|
|
@@ -53,11 +52,12 @@ export declare class ArchivesDisk implements IArchives {
|
|
|
53
52
|
private collectFiles;
|
|
54
53
|
setLargeFile(config: {
|
|
55
54
|
path: string;
|
|
55
|
+
lastModified?: number;
|
|
56
56
|
getNextData(): Promise<Buffer | undefined>;
|
|
57
57
|
}): Promise<void>;
|
|
58
58
|
startLargeUpload(): Promise<string>;
|
|
59
59
|
appendLargeUpload(id: string, data: Buffer): Promise<void>;
|
|
60
|
-
finishLargeUpload(id: string, key: string): Promise<void>;
|
|
60
|
+
finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void>;
|
|
61
61
|
cancelLargeUpload(id: string): Promise<void>;
|
|
62
62
|
getURL(path: string): Promise<string>;
|
|
63
63
|
}
|
package/storage/ArchivesDisk.ts
CHANGED
|
@@ -3,7 +3,8 @@ import path from "path";
|
|
|
3
3
|
import { lazy } from "socket-function/src/caching";
|
|
4
4
|
import { runInfinitePoll } from "socket-function/src/batching";
|
|
5
5
|
import { sort, binarySearchBasic } from "socket-function/src/misc";
|
|
6
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig, assertValidLastModified } from "./IArchives";
|
|
6
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig, SetConfig, assertValidLastModified } from "./IArchives";
|
|
7
|
+
import { filterChanges } from "./remoteStorage/remoteConfig";
|
|
7
8
|
|
|
8
9
|
// The base file-system IArchives: storage is one-to-one with the file system, every key is exactly one real file under <folder>/files, so the file system itself is the index. File handles are cached and reused, and closed once idle (see FileHandleCache). All operations on a file run in serial, so they can't collide with each other or with handle closing. Used as the disk synchronization source of BlobStore (see remoteStorage/blobStore.ts).
|
|
9
10
|
|
|
@@ -124,6 +125,11 @@ export class ArchivesDisk implements IArchives {
|
|
|
124
125
|
return {};
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
public async getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]> {
|
|
129
|
+
// No native change feed - a full listing filtered in memory (see the scanning note in BlobStore.scanSource for why the listing itself takes no filters)
|
|
130
|
+
return filterChanges(await this.findInfo(""), config);
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
public async hasWriteAccess(): Promise<boolean> {
|
|
128
134
|
return true;
|
|
129
135
|
}
|
|
@@ -136,7 +142,8 @@ export class ArchivesDisk implements IArchives {
|
|
|
136
142
|
return result;
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
|
|
145
|
+
// forceSetImmutable is accepted and needs no handling: disk sources are never immutable, and the older-write no-op below already gives synchronization its only-take-the-latest semantics
|
|
146
|
+
public async set(key: string, data: Buffer, config?: SetConfig): Promise<string> {
|
|
140
147
|
await this.init();
|
|
141
148
|
let lastModified = config?.lastModified;
|
|
142
149
|
if (lastModified) {
|
|
@@ -255,7 +262,7 @@ export class ArchivesDisk implements IArchives {
|
|
|
255
262
|
}
|
|
256
263
|
}
|
|
257
264
|
|
|
258
|
-
public async setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined> }): Promise<void> {
|
|
265
|
+
public async setLargeFile(config: { path: string; lastModified?: number; getNextData(): Promise<Buffer | undefined> }): Promise<void> {
|
|
259
266
|
let id = await this.startLargeUpload();
|
|
260
267
|
try {
|
|
261
268
|
while (true) {
|
|
@@ -263,7 +270,7 @@ export class ArchivesDisk implements IArchives {
|
|
|
263
270
|
if (!data) break;
|
|
264
271
|
await this.appendLargeUpload(id, data);
|
|
265
272
|
}
|
|
266
|
-
await this.finishLargeUpload(id, config.path);
|
|
273
|
+
await this.finishLargeUpload(id, config.path, config.lastModified);
|
|
267
274
|
} catch (e) {
|
|
268
275
|
await this.cancelLargeUpload(id);
|
|
269
276
|
throw e;
|
|
@@ -286,9 +293,12 @@ export class ArchivesDisk implements IArchives {
|
|
|
286
293
|
await handle.write(data, 0, data.length);
|
|
287
294
|
});
|
|
288
295
|
}
|
|
289
|
-
public async finishLargeUpload(id: string, key: string): Promise<void> {
|
|
296
|
+
public async finishLargeUpload(id: string, key: string, lastModified?: number): Promise<void> {
|
|
290
297
|
let upload = this.largeUploads.get(id);
|
|
291
298
|
if (!upload) throw new Error(`Unknown large upload ${id}`);
|
|
299
|
+
if (lastModified) {
|
|
300
|
+
assertValidLastModified(lastModified);
|
|
301
|
+
}
|
|
292
302
|
const tmpPath = upload.tmpPath;
|
|
293
303
|
this.largeUploads.delete(id);
|
|
294
304
|
let filePath = this.filePath(key);
|
|
@@ -304,6 +314,10 @@ export class ArchivesDisk implements IArchives {
|
|
|
304
314
|
// Nothing was ever appended, so the upload file was never created
|
|
305
315
|
await fs.promises.writeFile(filePath, Buffer.alloc(0));
|
|
306
316
|
}
|
|
317
|
+
// The rename preserves the temp file's mtime, which is just when the last append happened - the logical write time has to be stamped explicitly (it is the metadata scans order everything by)
|
|
318
|
+
if (lastModified) {
|
|
319
|
+
await fs.promises.utimes(filePath, new Date(lastModified), new Date(lastModified));
|
|
320
|
+
}
|
|
307
321
|
});
|
|
308
322
|
}
|
|
309
323
|
public async cancelLargeUpload(id: string): Promise<void> {
|
package/storage/IArchives.d.ts
CHANGED
|
@@ -48,6 +48,18 @@ export type BackblazeConfig = CommonConfig & {
|
|
|
48
48
|
allowedOrigins?: string[];
|
|
49
49
|
};
|
|
50
50
|
export declare const FULL_VALID_WINDOW: [number, number];
|
|
51
|
+
export type ChangesAfterConfig = {
|
|
52
|
+
time: number;
|
|
53
|
+
/** Only keys routing into one of these [start, end) ranges. Only scanning passes this - it lets a store syncing a partial shard ask for just its slice. */
|
|
54
|
+
routes?: [number, number][];
|
|
55
|
+
};
|
|
56
|
+
export type SetConfig = {
|
|
57
|
+
lastModified?: number;
|
|
58
|
+
/** Makes the write acceptable on immutable targets: an existing path is simply kept (immutability wins - nothing is overwritten) instead of the write throwing. Requires lastModified. Synchronization MUST pass this on every push - a plain set throws on immutable targets, which would abort reconciliation whenever one source in a chain is immutable. */
|
|
59
|
+
forceSetImmutable?: boolean;
|
|
60
|
+
/** Skips the target-side safety reads around the write (backblaze: the pre-write getInfo comparison and the post-upload existence poll). For writers whose own bookkeeping already decides what to write and orders it by write time (BlobStore's index-driven writes and synchronization), those reads are pure extra API calls - but the default stays checked, because other users of the raw backends rely on the checks. */
|
|
61
|
+
noChecks?: boolean;
|
|
62
|
+
};
|
|
51
63
|
export type ArchiveFileInfo = {
|
|
52
64
|
path: string;
|
|
53
65
|
createTime: number;
|
|
@@ -79,6 +91,8 @@ export type ArchivesConfig = {
|
|
|
79
91
|
};
|
|
80
92
|
export type ArchivesSource = {
|
|
81
93
|
source: IArchives;
|
|
94
|
+
/** The persistent identity of the endpoint: its routing URL (hosted/backblaze), or the disk folder path for the base disk source. The store persists this (via its append-only sources list) as IndexEntry.sourcesListIndex, so it must mean the same endpoint forever. */
|
|
95
|
+
url: string;
|
|
82
96
|
validWindow: [number, number];
|
|
83
97
|
route?: [number, number];
|
|
84
98
|
noFullSync?: boolean;
|
|
@@ -89,6 +103,19 @@ export declare const STORAGE_WRONG_ROUTE = "REMOTE_STORAGE_WRONG_ROUTE_c94d2e17"
|
|
|
89
103
|
export declare const FULL_ROUTE: [number, number];
|
|
90
104
|
export declare const VARIABLE_SHARD = "VARIABLE_SHARD_f0234jfah08fgyhfgyssdds83nmp";
|
|
91
105
|
export declare function windowAcceptsWrites(validWindow: [number, number] | undefined): boolean;
|
|
106
|
+
/** Copies one file between two archives. Small files go as a single get2+set; past LARGE_COPY_THRESHOLD the copy streams through setLargeFile in LARGE_COPY_CHUNK ranged reads, so the whole file is never in memory. size/writeTime usually come from the caller's metadata scan; when either is omitted, getInfo fills them in. Returns the copied file's info, or undefined when the source doesn't have the file. */
|
|
107
|
+
export declare function copyArchiveFile(config: {
|
|
108
|
+
from: IArchives;
|
|
109
|
+
to: IArchives;
|
|
110
|
+
path: string;
|
|
111
|
+
size?: number;
|
|
112
|
+
writeTime?: number;
|
|
113
|
+
forceSetImmutable?: boolean;
|
|
114
|
+
noChecks?: boolean;
|
|
115
|
+
}): Promise<{
|
|
116
|
+
writeTime: number;
|
|
117
|
+
size: number;
|
|
118
|
+
} | undefined>;
|
|
92
119
|
export type ArchivesSyncSourceStatus = {
|
|
93
120
|
debugName: string;
|
|
94
121
|
validWindow: [number, number];
|
|
@@ -132,13 +159,12 @@ export interface IArchives {
|
|
|
132
159
|
* VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency,
|
|
133
160
|
* see ArchivesChain) and the caller needs the returned key to ever read the value back.
|
|
134
161
|
*/
|
|
135
|
-
set(fileName: string, data: Buffer, config?:
|
|
136
|
-
lastModified?: number;
|
|
137
|
-
}): Promise<string>;
|
|
162
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
138
163
|
del(fileName: string): Promise<void>;
|
|
139
|
-
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
164
|
+
/** Streams a file too large to hold in memory. getNextData returns undefined when done. lastModified stamps the finished file like set's (synchronized copies need it to keep write ordering); backends that stamp their own times (backblaze) accept and ignore it. */
|
|
140
165
|
setLargeFile(config: {
|
|
141
166
|
path: string;
|
|
167
|
+
lastModified?: number;
|
|
142
168
|
getNextData(): Promise<Buffer | undefined>;
|
|
143
169
|
}): Promise<void>;
|
|
144
170
|
/** writeTime is the last-write time — see ArchiveFileInfo.createTime, which is the same value. */
|
|
@@ -159,10 +185,13 @@ export interface IArchives {
|
|
|
159
185
|
/** The bucket's configuration, which tells whether the optional functions are supported. */
|
|
160
186
|
getConfig(): Promise<ArchivesConfig>;
|
|
161
187
|
/**
|
|
162
|
-
* All files changed after
|
|
163
|
-
*
|
|
188
|
+
* All files changed after config.time, optionally restricted to keys routing into one of
|
|
189
|
+
* config.routes (used by scanning, so partially-overlapping shards only receive their slice).
|
|
190
|
+
* When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE
|
|
191
|
+
* reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo
|
|
192
|
+
* listing filtered in memory - correct, but no cheaper than the listing itself.
|
|
164
193
|
*/
|
|
165
|
-
|
|
194
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
166
195
|
/** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */
|
|
167
196
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
168
197
|
}
|