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.
Files changed (32) hide show
  1. package/index.d.ts +76 -33
  2. package/package.json +1 -1
  3. package/storage/ArchivesDisk.d.ts +3 -4
  4. package/storage/ArchivesDisk.ts +9 -2
  5. package/storage/IArchives.d.ts +21 -6
  6. package/storage/IArchives.ts +25 -6
  7. package/storage/backblaze.d.ts +3 -4
  8. package/storage/backblaze.ts +87 -35
  9. package/storage/dist/ArchivesDisk.ts.cache +3 -2
  10. package/storage/dist/IArchives.ts.cache +2 -2
  11. package/storage/dist/backblaze.ts.cache +8 -2
  12. package/storage/remoteStorage/ArchivesRemote.d.ts +3 -5
  13. package/storage/remoteStorage/ArchivesRemote.ts +5 -5
  14. package/storage/remoteStorage/ArchivesUrl.d.ts +2 -1
  15. package/storage/remoteStorage/ArchivesUrl.ts +13 -3
  16. package/storage/remoteStorage/blobStore.d.ts +13 -4
  17. package/storage/remoteStorage/blobStore.ts +191 -105
  18. package/storage/remoteStorage/createArchives.d.ts +3 -5
  19. package/storage/remoteStorage/createArchives.ts +5 -10
  20. package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +3 -3
  21. package/storage/remoteStorage/dist/blobStore.ts.cache +178 -91
  22. package/storage/remoteStorage/dist/createArchives.ts.cache +2 -2
  23. package/storage/remoteStorage/dist/storageController.ts.cache +4 -4
  24. package/storage/remoteStorage/dist/storageServerState.ts.cache +46 -12
  25. package/storage/remoteStorage/remoteConfig.d.ts +3 -1
  26. package/storage/remoteStorage/remoteConfig.ts +11 -1
  27. package/storage/remoteStorage/sourcesList.d.ts +15 -0
  28. package/storage/remoteStorage/sourcesList.ts +69 -0
  29. package/storage/remoteStorage/storageController.d.ts +3 -3
  30. package/storage/remoteStorage/storageController.ts +6 -9
  31. package/storage/remoteStorage/storageServerState.d.ts +3 -0
  32. 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 the given time. Only exists when getConfig().supportsChangesAfter;
2264
- * backed by an index, so it is fast (unlike a full findInfo scan). Deletions are not reported.
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
- getChangesAfter?(time: number): Promise<ArchiveFileInfo[]>;
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
- getChangesAfter(time: number): Promise<ArchiveFileInfo[]>;
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
- getChangesAfter?(time: number): Promise<ArchiveFileInfo[]>;
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 applyScanned;
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
- getChangesAfter(time: number): Promise<ArchiveFileInfo[]>;
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
- getChangesAfter(time: number): Promise<ArchiveFileInfo[]>;
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
- getChangesAfter: (account: string, bucketName: string, time: number) => Promise<ArchiveFileInfo[]>;
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
  {
2
2
  "name": "sliftutils",
3
- "version": "1.7.48",
3
+ "version": "1.7.49",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -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?: {
@@ -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
- public async set(key: string, data: Buffer, config?: { lastModified?: number }): Promise<string> {
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) {
@@ -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 the given time. Only exists when getConfig().supportsChangesAfter;
163
- * backed by an index, so it is fast (unlike a full findInfo scan). Deletions are not reported.
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
- getChangesAfter?(time: number): Promise<ArchiveFileInfo[]>;
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
  }
@@ -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 no getChangesAfter/getSyncStatus.
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 getChangesAfter is implemented (fast change polling, instead of full rescans)
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?: { lastModified?: number }): Promise<string>;
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 the given time. Only exists when getConfig().supportsChangesAfter;
194
- * backed by an index, so it is fast (unlike a full findInfo scan). Deletions are not reported.
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
- getChangesAfter?(time: number): Promise<ArchiveFileInfo[]>;
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
  }
@@ -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";