sliftutils 1.7.48 → 1.7.49
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 +76 -33
- package/package.json +1 -1
- package/storage/ArchivesDisk.d.ts +3 -4
- package/storage/ArchivesDisk.ts +9 -2
- package/storage/IArchives.d.ts +21 -6
- package/storage/IArchives.ts +25 -6
- package/storage/backblaze.d.ts +3 -4
- package/storage/backblaze.ts +87 -35
- package/storage/dist/ArchivesDisk.ts.cache +3 -2
- package/storage/dist/IArchives.ts.cache +2 -2
- package/storage/dist/backblaze.ts.cache +8 -2
- package/storage/remoteStorage/ArchivesRemote.d.ts +3 -5
- package/storage/remoteStorage/ArchivesRemote.ts +5 -5
- package/storage/remoteStorage/ArchivesUrl.d.ts +2 -1
- package/storage/remoteStorage/ArchivesUrl.ts +13 -3
- package/storage/remoteStorage/blobStore.d.ts +13 -4
- package/storage/remoteStorage/blobStore.ts +191 -105
- package/storage/remoteStorage/createArchives.d.ts +3 -5
- package/storage/remoteStorage/createArchives.ts +5 -10
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +3 -3
- package/storage/remoteStorage/dist/blobStore.ts.cache +178 -91
- package/storage/remoteStorage/dist/createArchives.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageController.ts.cache +4 -4
- package/storage/remoteStorage/dist/storageServerState.ts.cache +46 -12
- 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 +3 -3
- package/storage/remoteStorage/storageController.ts +6 -9
- package/storage/remoteStorage/storageServerState.d.ts +3 -0
- package/storage/remoteStorage/storageServerState.ts +37 -12
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?: {
|
|
@@ -2149,6 +2148,18 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2149
2148
|
allowedOrigins?: string[];
|
|
2150
2149
|
};
|
|
2151
2150
|
export declare const FULL_VALID_WINDOW: [number, number];
|
|
2151
|
+
export type ChangesAfterConfig = {
|
|
2152
|
+
time: number;
|
|
2153
|
+
/** 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. */
|
|
2154
|
+
routes?: [number, number][];
|
|
2155
|
+
};
|
|
2156
|
+
export type SetConfig = {
|
|
2157
|
+
lastModified?: number;
|
|
2158
|
+
/** 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. */
|
|
2159
|
+
forceSetImmutable?: boolean;
|
|
2160
|
+
/** 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. */
|
|
2161
|
+
noChecks?: boolean;
|
|
2162
|
+
};
|
|
2152
2163
|
export type ArchiveFileInfo = {
|
|
2153
2164
|
path: string;
|
|
2154
2165
|
createTime: number;
|
|
@@ -2180,6 +2191,8 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2180
2191
|
};
|
|
2181
2192
|
export type ArchivesSource = {
|
|
2182
2193
|
source: IArchives;
|
|
2194
|
+
/** 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. */
|
|
2195
|
+
url: string;
|
|
2183
2196
|
validWindow: [number, number];
|
|
2184
2197
|
route?: [number, number];
|
|
2185
2198
|
noFullSync?: boolean;
|
|
@@ -2233,9 +2246,7 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2233
2246
|
* VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency,
|
|
2234
2247
|
* see ArchivesChain) and the caller needs the returned key to ever read the value back.
|
|
2235
2248
|
*/
|
|
2236
|
-
set(fileName: string, data: Buffer, config?:
|
|
2237
|
-
lastModified?: number;
|
|
2238
|
-
}): Promise<string>;
|
|
2249
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2239
2250
|
del(fileName: string): Promise<void>;
|
|
2240
2251
|
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
2241
2252
|
setLargeFile(config: {
|
|
@@ -2260,10 +2271,13 @@ declare module "sliftutils/storage/IArchives" {
|
|
|
2260
2271
|
/** The bucket's configuration, which tells whether the optional functions are supported. */
|
|
2261
2272
|
getConfig(): Promise<ArchivesConfig>;
|
|
2262
2273
|
/**
|
|
2263
|
-
* All files changed after
|
|
2264
|
-
*
|
|
2274
|
+
* All files changed after config.time, optionally restricted to keys routing into one of
|
|
2275
|
+
* config.routes (used by scanning, so partially-overlapping shards only receive their slice).
|
|
2276
|
+
* When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE
|
|
2277
|
+
* reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo
|
|
2278
|
+
* listing filtered in memory - correct, but no cheaper than the listing itself.
|
|
2265
2279
|
*/
|
|
2266
|
-
|
|
2280
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2267
2281
|
/** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */
|
|
2268
2282
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
2269
2283
|
}
|
|
@@ -2526,7 +2540,7 @@ declare module "sliftutils/storage/TransactionStorage" {
|
|
|
2526
2540
|
declare module "sliftutils/storage/backblaze" {
|
|
2527
2541
|
/// <reference types="node" />
|
|
2528
2542
|
/// <reference types="node" />
|
|
2529
|
-
import { IArchives, ArchivesConfig } from "./IArchives";
|
|
2543
|
+
import { IArchives, ArchivesConfig, ChangesAfterConfig, ArchiveFileInfo, SetConfig } from "./IArchives";
|
|
2530
2544
|
export declare class ArchivesBackblaze implements IArchives {
|
|
2531
2545
|
private config;
|
|
2532
2546
|
constructor(config: {
|
|
@@ -2565,9 +2579,7 @@ declare module "sliftutils/storage/backblaze" {
|
|
|
2565
2579
|
} | undefined>;
|
|
2566
2580
|
getConfig(): Promise<ArchivesConfig>;
|
|
2567
2581
|
hasWriteAccess(): Promise<boolean>;
|
|
2568
|
-
set(fileName: string, data: Buffer, config?:
|
|
2569
|
-
lastModified?: number;
|
|
2570
|
-
}): Promise<string>;
|
|
2582
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2571
2583
|
del(fileName: string): Promise<void>;
|
|
2572
2584
|
setLargeFile(config: {
|
|
2573
2585
|
path: string;
|
|
@@ -2581,6 +2593,7 @@ declare module "sliftutils/storage/backblaze" {
|
|
|
2581
2593
|
shallow?: boolean;
|
|
2582
2594
|
type: "files" | "folders";
|
|
2583
2595
|
}): Promise<string[]>;
|
|
2596
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2584
2597
|
findInfo(prefix: string, config?: {
|
|
2585
2598
|
shallow?: boolean;
|
|
2586
2599
|
type: "files" | "folders";
|
|
@@ -2841,7 +2854,7 @@ declare module "sliftutils/storage/remoteFileStorage" {
|
|
|
2841
2854
|
declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
2842
2855
|
/// <reference types="node" />
|
|
2843
2856
|
/// <reference types="node" />
|
|
2844
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
2857
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, SetConfig } from "../IArchives";
|
|
2845
2858
|
export type ArchivesRemoteConfig = {
|
|
2846
2859
|
url: string;
|
|
2847
2860
|
waitForAccess?: boolean;
|
|
@@ -2896,9 +2909,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2896
2909
|
writeTime: number;
|
|
2897
2910
|
size: number;
|
|
2898
2911
|
} | undefined>;
|
|
2899
|
-
set(fileName: string, data: Buffer, config?:
|
|
2900
|
-
lastModified?: number;
|
|
2901
|
-
}): Promise<string>;
|
|
2912
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
2902
2913
|
del(fileName: string): Promise<void>;
|
|
2903
2914
|
getInfo(fileName: string): Promise<{
|
|
2904
2915
|
writeTime: number;
|
|
@@ -2912,7 +2923,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2912
2923
|
shallow?: boolean;
|
|
2913
2924
|
type: "files" | "folders";
|
|
2914
2925
|
}): Promise<string[]>;
|
|
2915
|
-
|
|
2926
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2916
2927
|
getConfig(): Promise<ArchivesConfig>;
|
|
2917
2928
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
2918
2929
|
setLargeFile(config: {
|
|
@@ -2927,7 +2938,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
|
2927
2938
|
declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
|
|
2928
2939
|
/// <reference types="node" />
|
|
2929
2940
|
/// <reference types="node" />
|
|
2930
|
-
import { IArchives, ArchiveFileInfo, ArchivesConfig } from "../IArchives";
|
|
2941
|
+
import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig } from "../IArchives";
|
|
2931
2942
|
export declare class ArchivesUrl implements IArchives {
|
|
2932
2943
|
private base;
|
|
2933
2944
|
constructor(base: string);
|
|
@@ -2969,6 +2980,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
|
|
|
2969
2980
|
shallow?: boolean;
|
|
2970
2981
|
type: "files" | "folders";
|
|
2971
2982
|
}): Promise<ArchiveFileInfo[]>;
|
|
2983
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
2972
2984
|
getURL(path: string): Promise<string>;
|
|
2973
2985
|
getConfig(): Promise<ArchivesConfig>;
|
|
2974
2986
|
hasWriteAccess(): Promise<boolean>;
|
|
@@ -2984,7 +2996,7 @@ declare module "sliftutils/storage/remoteStorage/accessPage" {
|
|
|
2984
2996
|
declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
2985
2997
|
/// <reference types="node" />
|
|
2986
2998
|
/// <reference types="node" />
|
|
2987
|
-
import { IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, SyncActivity } from "../IArchives";
|
|
2999
|
+
import { IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, ChangesAfterConfig, SyncActivity } from "../IArchives";
|
|
2988
3000
|
export declare const DEFAULT_FAST_WRITE_DELAY: number;
|
|
2989
3001
|
export declare const WINDOW_END_FLUSH_MARGIN: number;
|
|
2990
3002
|
export type WriteConfig = {
|
|
@@ -3019,7 +3031,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3019
3031
|
shallow?: boolean;
|
|
3020
3032
|
type?: "files" | "folders";
|
|
3021
3033
|
}): Promise<ArchiveFileInfo[]>;
|
|
3022
|
-
|
|
3034
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3023
3035
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
3024
3036
|
getSyncProgress?(): {
|
|
3025
3037
|
index: {
|
|
@@ -3050,6 +3062,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3050
3062
|
};
|
|
3051
3063
|
export type BlobSourceSpec = {
|
|
3052
3064
|
identity: string;
|
|
3065
|
+
url: string;
|
|
3053
3066
|
validWindow: [number, number];
|
|
3054
3067
|
route?: [number, number];
|
|
3055
3068
|
noFullSync?: boolean;
|
|
@@ -3063,6 +3076,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3063
3076
|
onIndexChanged?: ((key: string) => void) | undefined;
|
|
3064
3077
|
readerDiskLimit?: number | undefined;
|
|
3065
3078
|
onWriteCounted?: ((kind: "original" | "flushed", bytes: number) => void) | undefined;
|
|
3079
|
+
resolveSourceUrl?: ((url: string) => IArchives) | undefined;
|
|
3066
3080
|
} | undefined);
|
|
3067
3081
|
private stopped;
|
|
3068
3082
|
private index;
|
|
@@ -3076,7 +3090,14 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3076
3090
|
private overlay;
|
|
3077
3091
|
private sourceStates;
|
|
3078
3092
|
private syncStarted;
|
|
3093
|
+
private sourcesList;
|
|
3094
|
+
private slotSourcesListIndexes;
|
|
3095
|
+
private slotRegistrations;
|
|
3079
3096
|
private isLive;
|
|
3097
|
+
private registerSlot;
|
|
3098
|
+
private sourcesListIndexOfSlot;
|
|
3099
|
+
private slotForSourcesListIndex;
|
|
3100
|
+
private getEntryHolder;
|
|
3080
3101
|
init: {
|
|
3081
3102
|
(): Promise<void>;
|
|
3082
3103
|
reset(): void;
|
|
@@ -3125,7 +3146,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3125
3146
|
private runSourceSync;
|
|
3126
3147
|
private scanSource;
|
|
3127
3148
|
private reconcileSource;
|
|
3128
|
-
private
|
|
3149
|
+
private updateScanIndex;
|
|
3129
3150
|
private pollChanges;
|
|
3130
3151
|
private copySourceFiles;
|
|
3131
3152
|
private waitForRequiredScans;
|
|
@@ -3160,7 +3181,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3160
3181
|
shallow?: boolean;
|
|
3161
3182
|
type?: "files" | "folders";
|
|
3162
3183
|
}): Promise<ArchiveFileInfo[]>;
|
|
3163
|
-
|
|
3184
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3164
3185
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
3165
3186
|
private getDiskSource;
|
|
3166
3187
|
startLargeUpload(): Promise<string>;
|
|
@@ -3183,7 +3204,7 @@ declare module "sliftutils/storage/remoteStorage/cliArgs" {
|
|
|
3183
3204
|
declare module "sliftutils/storage/remoteStorage/createArchives" {
|
|
3184
3205
|
/// <reference types="node" />
|
|
3185
3206
|
/// <reference types="node" />
|
|
3186
|
-
import { IArchives, RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
3207
|
+
import { IArchives, RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, SetConfig } from "../IArchives";
|
|
3187
3208
|
import { ServerBucketInfo, ActiveBucketInfo } from "./storageServerState";
|
|
3188
3209
|
/** 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
3210
|
export { parseHostedUrl, parseBackblazeUrl, getBucketBaseUrl } from "./remoteConfig";
|
|
@@ -3260,13 +3281,11 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
|
|
|
3260
3281
|
shallow?: boolean;
|
|
3261
3282
|
type: "files" | "folders";
|
|
3262
3283
|
}): Promise<ArchiveFileInfo[]>;
|
|
3263
|
-
|
|
3284
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
3264
3285
|
getSyncStatus(): Promise<ArchivesSyncStatus>;
|
|
3265
3286
|
getConfig(): Promise<ArchivesConfig>;
|
|
3266
3287
|
hasWriteAccess(): Promise<boolean>;
|
|
3267
|
-
set(fileName: string, data: Buffer, config?:
|
|
3268
|
-
lastModified?: number;
|
|
3269
|
-
}): Promise<string>;
|
|
3288
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
3270
3289
|
private setRoutingConfig;
|
|
3271
3290
|
del(fileName: string): Promise<void>;
|
|
3272
3291
|
private setVariableShard;
|
|
@@ -3376,12 +3395,14 @@ declare module "sliftutils/storage/remoteStorage/productionEnv" {
|
|
|
3376
3395
|
declare module "sliftutils/storage/remoteStorage/remoteConfig" {
|
|
3377
3396
|
/// <reference types="node" />
|
|
3378
3397
|
/// <reference types="node" />
|
|
3379
|
-
import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "../IArchives";
|
|
3398
|
+
import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ChangesAfterConfig } from "../IArchives";
|
|
3380
3399
|
export declare const ROUTING_FILE = "storage/storagerouting.json";
|
|
3381
3400
|
/** 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
3401
|
export declare function parseVariableRoute(key: string): number | undefined;
|
|
3383
3402
|
/** Where a key routes in [0, 1). A materialized variable-shard suffix completely overrides the hash. */
|
|
3384
3403
|
export declare function getRoute(key: string): number;
|
|
3404
|
+
/** 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. */
|
|
3405
|
+
export declare function filterChanges(files: ArchiveFileInfo[], config: ChangesAfterConfig): ArchiveFileInfo[];
|
|
3385
3406
|
export declare function routeContains(route: [number, number] | undefined, value: number): boolean;
|
|
3386
3407
|
export declare function routesOverlap(a: [number, number] | undefined, b: [number, number] | undefined): boolean;
|
|
3387
3408
|
/** The overlap of two route ranges, or undefined when they don't overlap. */
|
|
@@ -3460,6 +3481,25 @@ declare module "sliftutils/storage/remoteStorage/sourceWrapper" {
|
|
|
3460
3481
|
|
|
3461
3482
|
}
|
|
3462
3483
|
|
|
3484
|
+
declare module "sliftutils/storage/remoteStorage/sourcesList" {
|
|
3485
|
+
export declare class SourcesList {
|
|
3486
|
+
private filePath;
|
|
3487
|
+
constructor(filePath: string);
|
|
3488
|
+
private urls;
|
|
3489
|
+
private indexes;
|
|
3490
|
+
private endsClean;
|
|
3491
|
+
private lastReload;
|
|
3492
|
+
private appendQueue;
|
|
3493
|
+
private load;
|
|
3494
|
+
getUrl(sourcesListIndex: number): string | undefined;
|
|
3495
|
+
/** 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. */
|
|
3496
|
+
getUrlReloading(sourcesListIndex: number): Promise<string | undefined>;
|
|
3497
|
+
/** 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. */
|
|
3498
|
+
ensure(url: string): Promise<number>;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3463
3503
|
declare module "sliftutils/storage/remoteStorage/storageClientController" {
|
|
3464
3504
|
/** Subscribe to server-pushed routing change notifications. Returns the unsubscribe function. */
|
|
3465
3505
|
export declare function onServerRoutingChanged(listener: () => void): () => void;
|
|
@@ -3472,7 +3512,7 @@ declare module "sliftutils/storage/remoteStorage/storageClientController" {
|
|
|
3472
3512
|
declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
3473
3513
|
/// <reference types="node" />
|
|
3474
3514
|
/// <reference types="node" />
|
|
3475
|
-
import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
3515
|
+
import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig } from "../IArchives";
|
|
3476
3516
|
import { ServerBucketInfo, ActiveBucketInfo } from "./storageServerState";
|
|
3477
3517
|
export declare const REMOTE_STORAGE_CLASS_GUID = "RemoteStorageController-b7e42a91";
|
|
3478
3518
|
export declare const STORAGE_AUTH_PURPOSE = "remoteStorage-auth-1";
|
|
@@ -3542,7 +3582,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
3542
3582
|
writeTime: number;
|
|
3543
3583
|
size: number;
|
|
3544
3584
|
} | undefined>;
|
|
3545
|
-
set: (account: string, bucketName: string, path: string, data: Buffer, lastModified?: number) => Promise<void>;
|
|
3585
|
+
set: (account: string, bucketName: string, path: string, data: Buffer, lastModified?: number, forceSetImmutable?: boolean) => Promise<void>;
|
|
3546
3586
|
del: (account: string, bucketName: string, path: string) => Promise<void>;
|
|
3547
3587
|
getInfo: (account: string, bucketName: string, path: string) => Promise<{
|
|
3548
3588
|
writeTime: number;
|
|
@@ -3552,7 +3592,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
3552
3592
|
shallow?: boolean;
|
|
3553
3593
|
type?: "files" | "folders";
|
|
3554
3594
|
}) => Promise<ArchiveFileInfo[]>;
|
|
3555
|
-
|
|
3595
|
+
getChangesAfter2: (account: string, bucketName: string, config: ChangesAfterConfig) => Promise<ArchiveFileInfo[]>;
|
|
3556
3596
|
getArchivesConfig: (account: string, bucketName: string) => Promise<ArchivesConfig>;
|
|
3557
3597
|
listBuckets: (account: string) => Promise<ServerBucketInfo[]>;
|
|
3558
3598
|
getActiveBucket: (account: string, bucketName: string) => Promise<ActiveBucketInfo | string>;
|
|
@@ -3647,10 +3687,13 @@ declare module "sliftutils/storage/remoteStorage/storageServerState" {
|
|
|
3647
3687
|
};
|
|
3648
3688
|
export declare function addExtraListenPort(port: number): void;
|
|
3649
3689
|
export declare function removeExtraListenPort(port: number): void;
|
|
3690
|
+
/** 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. */
|
|
3691
|
+
export declare function resolveSourceArchives(url: string): IArchives;
|
|
3650
3692
|
export declare function getLoadedBucket(account: string, bucketName: string): Promise<LoadedBucket | undefined>;
|
|
3651
3693
|
export declare function assertMutable(bucket: LoadedBucket, filePath: string, writeTime: number): Promise<void>;
|
|
3652
3694
|
export declare function writeBucketFile(account: string, bucketName: string, filePath: string, data: Buffer, config?: {
|
|
3653
3695
|
lastModified?: number;
|
|
3696
|
+
forceSetImmutable?: boolean;
|
|
3654
3697
|
}): Promise<void>;
|
|
3655
3698
|
export declare function getBucketConfig(bucket: LoadedBucket): ArchivesConfig;
|
|
3656
3699
|
/** 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?: {
|
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) {
|
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;
|
|
@@ -132,9 +146,7 @@ export interface IArchives {
|
|
|
132
146
|
* VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency,
|
|
133
147
|
* see ArchivesChain) and the caller needs the returned key to ever read the value back.
|
|
134
148
|
*/
|
|
135
|
-
set(fileName: string, data: Buffer, config?:
|
|
136
|
-
lastModified?: number;
|
|
137
|
-
}): Promise<string>;
|
|
149
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
138
150
|
del(fileName: string): Promise<void>;
|
|
139
151
|
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
140
152
|
setLargeFile(config: {
|
|
@@ -159,10 +171,13 @@ export interface IArchives {
|
|
|
159
171
|
/** The bucket's configuration, which tells whether the optional functions are supported. */
|
|
160
172
|
getConfig(): Promise<ArchivesConfig>;
|
|
161
173
|
/**
|
|
162
|
-
* All files changed after
|
|
163
|
-
*
|
|
174
|
+
* All files changed after config.time, optionally restricted to keys routing into one of
|
|
175
|
+
* config.routes (used by scanning, so partially-overlapping shards only receive their slice).
|
|
176
|
+
* When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE
|
|
177
|
+
* reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo
|
|
178
|
+
* listing filtered in memory - correct, but no cheaper than the listing itself.
|
|
164
179
|
*/
|
|
165
|
-
|
|
180
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
166
181
|
/** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */
|
|
167
182
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
168
183
|
}
|
package/storage/IArchives.ts
CHANGED
|
@@ -58,7 +58,7 @@ export type HostedConfig = CommonConfig & {
|
|
|
58
58
|
// Fast mode: the server acknowledges writes once they are in memory, flushing to disk after writeDelay (default 5 minutes) and coalescing writes to the same file. A server crash loses writes that haven't flushed yet.
|
|
59
59
|
fast?: boolean;
|
|
60
60
|
writeDelay?: number;
|
|
61
|
-
// The bucket is served straight from the server's disk, with no index — so no fast writes and
|
|
61
|
+
// The bucket is served straight from the server's disk, with no index — so no fast writes, no getSyncStatus, and getChangesAfter2 falls back to a full listing.
|
|
62
62
|
rawDisk?: boolean;
|
|
63
63
|
// Writes to paths that already exist are disallowed (deletes still work).
|
|
64
64
|
immutable?: boolean;
|
|
@@ -86,6 +86,20 @@ export const FULL_VALID_WINDOW: [number, number] = [0, Number.MAX_SAFE_INTEGER];
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
export type ChangesAfterConfig = {
|
|
90
|
+
time: number;
|
|
91
|
+
/** 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. */
|
|
92
|
+
routes?: [number, number][];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type SetConfig = {
|
|
96
|
+
lastModified?: number;
|
|
97
|
+
/** 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. */
|
|
98
|
+
forceSetImmutable?: boolean;
|
|
99
|
+
/** 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. */
|
|
100
|
+
noChecks?: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
89
103
|
// createTime is a misnomer kept for compatibility — it is really the LAST-WRITE time, same as getInfo's writeTime. Neither Backblaze nor our remote storage tracks a distinct creation date: each write stamps a fresh timestamp on the current version, so both fields are just "when the bytes served by get() were most recently written".
|
|
90
104
|
export type ArchiveFileInfo = { path: string; createTime: number; size: number };
|
|
91
105
|
|
|
@@ -102,7 +116,7 @@ export type SyncActivity = {
|
|
|
102
116
|
};
|
|
103
117
|
|
|
104
118
|
export type ArchivesConfig = {
|
|
105
|
-
// Whether
|
|
119
|
+
// Whether getChangesAfter2 is natively index-backed (fast change polling; every backend still serves it, but the others emulate it with a full listing)
|
|
106
120
|
supportsChangesAfter?: boolean;
|
|
107
121
|
// The bucket's full routing config (ROUTING_FILE). Absent for sources that don't have one (a bare disk source, or a bucket that doesn't exist yet).
|
|
108
122
|
remoteConfig?: RemoteConfig;
|
|
@@ -119,6 +133,8 @@ export type ArchivesConfig = {
|
|
|
119
133
|
// A synchronization source of a BlobStore (which synchronizes an index + local cache from them)
|
|
120
134
|
export type ArchivesSource = {
|
|
121
135
|
source: IArchives;
|
|
136
|
+
/** 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. */
|
|
137
|
+
url: string;
|
|
122
138
|
// From the source's CommonConfig. Values with write times outside the window are ignored when scanning, and once its end is past (see windowAcceptsWrites) the source stops receiving writes entirely - still scanned (it holds the authoritative data for its window), it just stops growing.
|
|
123
139
|
validWindow: [number, number];
|
|
124
140
|
// From the source's CommonConfig (intersected with the owning store's own route): only keys routing into [start, end) are accepted from this source's scans and sent to it in writes/reconciliation. The routing file is exempt - config flows everywhere. Absent = all keys.
|
|
@@ -177,7 +193,7 @@ export interface IArchives {
|
|
|
177
193
|
* VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency,
|
|
178
194
|
* see ArchivesChain) and the caller needs the returned key to ever read the value back.
|
|
179
195
|
*/
|
|
180
|
-
set(fileName: string, data: Buffer, config?:
|
|
196
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
181
197
|
del(fileName: string): Promise<void>;
|
|
182
198
|
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
183
199
|
setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined> }): Promise<void>;
|
|
@@ -190,10 +206,13 @@ export interface IArchives {
|
|
|
190
206
|
/** The bucket's configuration, which tells whether the optional functions are supported. */
|
|
191
207
|
getConfig(): Promise<ArchivesConfig>;
|
|
192
208
|
/**
|
|
193
|
-
* All files changed after
|
|
194
|
-
*
|
|
209
|
+
* All files changed after config.time, optionally restricted to keys routing into one of
|
|
210
|
+
* config.routes (used by scanning, so partially-overlapping shards only receive their slice).
|
|
211
|
+
* When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE
|
|
212
|
+
* reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo
|
|
213
|
+
* listing filtered in memory - correct, but no cheaper than the listing itself.
|
|
195
214
|
*/
|
|
196
|
-
|
|
215
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
197
216
|
/** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */
|
|
198
217
|
getSyncStatus?(): Promise<ArchivesSyncStatus>;
|
|
199
218
|
}
|
package/storage/backblaze.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { IArchives, ArchivesConfig } from "./IArchives";
|
|
3
|
+
import { IArchives, ArchivesConfig, ChangesAfterConfig, ArchiveFileInfo, SetConfig } from "./IArchives";
|
|
4
4
|
export declare class ArchivesBackblaze implements IArchives {
|
|
5
5
|
private config;
|
|
6
6
|
constructor(config: {
|
|
@@ -39,9 +39,7 @@ export declare class ArchivesBackblaze implements IArchives {
|
|
|
39
39
|
} | undefined>;
|
|
40
40
|
getConfig(): Promise<ArchivesConfig>;
|
|
41
41
|
hasWriteAccess(): Promise<boolean>;
|
|
42
|
-
set(fileName: string, data: Buffer, config?:
|
|
43
|
-
lastModified?: number;
|
|
44
|
-
}): Promise<string>;
|
|
42
|
+
set(fileName: string, data: Buffer, config?: SetConfig): Promise<string>;
|
|
45
43
|
del(fileName: string): Promise<void>;
|
|
46
44
|
setLargeFile(config: {
|
|
47
45
|
path: string;
|
|
@@ -55,6 +53,7 @@ export declare class ArchivesBackblaze implements IArchives {
|
|
|
55
53
|
shallow?: boolean;
|
|
56
54
|
type: "files" | "folders";
|
|
57
55
|
}): Promise<string[]>;
|
|
56
|
+
getChangesAfter2(config: ChangesAfterConfig): Promise<ArchiveFileInfo[]>;
|
|
58
57
|
findInfo(prefix: string, config?: {
|
|
59
58
|
shallow?: boolean;
|
|
60
59
|
type: "files" | "folders";
|