sliftutils 1.7.10 → 1.7.12

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 (84) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/examplestorage/exampleserver.ts +0 -2
  3. package/index.d.ts +301 -54
  4. package/misc/dist/environment.ts.cache +3 -3
  5. package/misc/dist/getSecret.ts.cache +139 -0
  6. package/misc/dist/strings.ts.cache +13 -0
  7. package/misc/dist/zip.ts.cache +9 -0
  8. package/misc/getSecret.ts +38 -16
  9. package/misc/https/cloudflareHelpers.d.ts +19 -0
  10. package/misc/https/cloudflareHelpers.ts +69 -0
  11. package/misc/https/dist/certs.ts.cache +115 -179
  12. package/misc/https/dist/cloudflareHelpers.ts.cache +68 -0
  13. package/misc/https/dist/dns.ts.cache +112 -100
  14. package/misc/https/dist/hostServer.ts.cache +130 -0
  15. package/misc/https/dist/httpsCerts.ts.cache +24 -5
  16. package/misc/https/dist/persistentLocalStorage.ts.cache +3 -2
  17. package/misc/https/dns.d.ts +41 -12
  18. package/misc/https/dns.ts +104 -101
  19. package/misc/https/hostServer.d.ts +0 -6
  20. package/misc/https/hostServer.ts +9 -5
  21. package/package.json +2 -2
  22. package/render-utils/dist/observer.tsx.cache +3 -2
  23. package/spec.txt +0 -90
  24. package/storage/ArchivesDisk.d.ts +2 -0
  25. package/storage/ArchivesDisk.ts +7 -3
  26. package/storage/BulkDatabase2/dist/BulkDatabaseBase.ts.cache +1242 -0
  27. package/storage/BulkDatabase2/dist/BulkDatabaseFormat.ts.cache +513 -0
  28. package/storage/BulkDatabase2/dist/BulkDatabaseMerge.ts.cache +352 -0
  29. package/storage/BulkDatabase2/dist/BulkDatabaseReader.ts.cache +306 -0
  30. package/storage/BulkDatabase2/dist/LoadedIndex.ts.cache +435 -0
  31. package/storage/BulkDatabase2/dist/WriteOverlay.ts.cache +58 -0
  32. package/storage/BulkDatabase2/dist/blockCache.ts.cache +179 -0
  33. package/storage/BulkDatabase2/dist/mergeLock.ts.cache +149 -0
  34. package/storage/BulkDatabase2/dist/mergeMarkers.ts.cache +101 -0
  35. package/storage/BulkDatabase2/dist/streamLog.ts.cache +227 -0
  36. package/storage/BulkDatabase2/dist/syncClient.ts.cache +104 -0
  37. package/storage/IArchives.d.ts +47 -15
  38. package/storage/IArchives.ts +86 -31
  39. package/storage/backblaze.d.ts +15 -0
  40. package/storage/backblaze.ts +199 -79
  41. package/storage/dist/ArchivesDisk.ts.cache +373 -0
  42. package/storage/dist/FileFolderAPI.tsx.cache +508 -65
  43. package/storage/dist/IArchives.ts.cache +44 -0
  44. package/storage/dist/IndexedDBFileFolderAPI.ts.cache +8 -2
  45. package/storage/dist/JSONStorage.ts.cache +6 -2
  46. package/storage/dist/PendingManager.tsx.cache +1 -1
  47. package/storage/dist/TransactionStorage.ts.cache +231 -92
  48. package/storage/dist/backblaze.ts.cache +797 -0
  49. package/storage/dist/fileSystemPointer.ts.cache +36 -3
  50. package/storage/dist/remoteFileStorage.ts.cache +495 -0
  51. package/storage/remoteStorage/ArchivesRemote.d.ts +5 -1
  52. package/storage/remoteStorage/ArchivesRemote.ts +28 -8
  53. package/storage/remoteStorage/ArchivesUrl.d.ts +2 -0
  54. package/storage/remoteStorage/ArchivesUrl.ts +16 -4
  55. package/storage/remoteStorage/blobStore.d.ts +63 -3
  56. package/storage/remoteStorage/blobStore.ts +419 -101
  57. package/storage/remoteStorage/createArchives.d.ts +31 -10
  58. package/storage/remoteStorage/createArchives.ts +470 -253
  59. package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +197 -0
  60. package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +85 -0
  61. package/storage/remoteStorage/dist/accessPage.tsx.cache +222 -0
  62. package/storage/remoteStorage/dist/blobStore.ts.cache +837 -0
  63. package/storage/remoteStorage/dist/cliArgs.ts.cache +16 -0
  64. package/storage/remoteStorage/dist/createArchives.ts.cache +610 -0
  65. package/storage/remoteStorage/dist/remoteConfig.ts.cache +206 -0
  66. package/storage/remoteStorage/dist/sourceWrapper.ts.cache +223 -0
  67. package/storage/remoteStorage/dist/storageController.ts.cache +500 -0
  68. package/storage/remoteStorage/dist/storageServer.ts.cache +106 -0
  69. package/storage/remoteStorage/dist/storageServerCli.ts.cache +39 -0
  70. package/storage/remoteStorage/dist/storageServerState.ts.cache +518 -0
  71. package/storage/remoteStorage/remoteConfig.d.ts +8 -0
  72. package/storage/remoteStorage/remoteConfig.ts +105 -5
  73. package/storage/remoteStorage/sourceWrapper.d.ts +41 -0
  74. package/storage/remoteStorage/sourceWrapper.ts +223 -0
  75. package/storage/remoteStorage/spec.md +31 -0
  76. package/storage/remoteStorage/storageController.d.ts +11 -0
  77. package/storage/remoteStorage/storageController.ts +109 -40
  78. package/storage/remoteStorage/storageServer.d.ts +0 -5
  79. package/storage/remoteStorage/storageServer.ts +17 -10
  80. package/storage/remoteStorage/storageServerCli.ts +0 -3
  81. package/storage/remoteStorage/storageServerState.d.ts +7 -1
  82. package/storage/remoteStorage/storageServerState.ts +230 -48
  83. package/testsite/server.ts +0 -2
  84. package/yarn.lock +11 -6
package/index.d.ts CHANGED
@@ -136,29 +136,81 @@ declare module "sliftutils/misc/https/certs" {
136
136
 
137
137
  }
138
138
 
139
+ declare module "sliftutils/misc/https/cloudflareHelpers" {
140
+ /// <reference types="node" />
141
+ /// <reference types="node" />
142
+ export type CloudflareCreds = {
143
+ key: string;
144
+ /** Set for legacy global API keys, which auth via X-Auth-Email/X-Auth-Key. Absent for API tokens, which auth via Authorization: Bearer. */
145
+ email?: string;
146
+ };
147
+ export declare const getCloudflareCreds: {
148
+ (): Promise<CloudflareCreds>;
149
+ reset(): void;
150
+ set(newValue: Promise<CloudflareCreds>): void;
151
+ };
152
+ export declare function cloudflareGETCall<T>(path: string, params?: {
153
+ [key: string]: string;
154
+ }): Promise<T>;
155
+ export declare function cloudflarePOSTCall<T>(path: string, params: {
156
+ [key: string]: unknown;
157
+ }): Promise<T>;
158
+ export declare function cloudflareCall<T>(path: string, payload: Buffer, method: string): Promise<T>;
159
+
160
+ }
161
+
139
162
  declare module "sliftutils/misc/https/dns" {
163
+ /** Parses our tag back out; 0 (i.e. always stale) when it's absent or unparseable. */
164
+ export declare function freshnessTime(comment?: string): number;
165
+ export declare const hasDNSWritePermissions: {
166
+ (): Promise<boolean>;
167
+ reset(): void;
168
+ set(newValue: Promise<boolean>): void;
169
+ };
170
+ export declare const getZoneId: {
171
+ (key: string): Promise<string>;
172
+ clear(key: string): void;
173
+ clearAll(): void;
174
+ forceSet(key: string, value: Promise<string>): void;
175
+ getAllKeys(): string[];
176
+ get(key: string): Promise<string> | undefined;
177
+ };
140
178
  export declare function getRecordsRaw(type: string, key: string): Promise<{
141
179
  id: string;
142
180
  type: string;
143
181
  name: string;
144
182
  content: string;
145
183
  proxied: boolean;
184
+ modified_on: string;
185
+ comment?: string | undefined;
146
186
  }[]>;
187
+ /** Cloudflare's batch endpoint applies deletes, then patches, then posts in a single database
188
+ * transaction. We route edits (patches) through here because the standalone PATCH/PUT verbs
189
+ * aren't usable in our setup, and because it lets "remove others + assert target" happen
190
+ * without a window where the name resolves to nothing. */
191
+ export declare function batchRecords(zoneId: string, batch: {
192
+ deletes?: {
193
+ id: string;
194
+ }[];
195
+ patches?: {
196
+ id: string;
197
+ comment?: string;
198
+ }[];
199
+ posts?: {
200
+ type: string;
201
+ name: string;
202
+ content: string;
203
+ ttl: number;
204
+ proxied: boolean;
205
+ comment?: string;
206
+ }[];
207
+ }): Promise<void>;
147
208
  export declare function getRecords(type: string, key: string): Promise<string[]>;
148
209
  export declare function deleteRecord(type: string, key: string, value: string): Promise<void>;
149
- /** Removes all existing records (unless the record is already present) */
150
- export declare function setRecord(type: string, key: string, value: string, proxied?: "proxied"): Promise<void>;
210
+ /** Removes all existing records (unless the record is already present and fresh) */
211
+ export declare function setRecord(type: string, key: string, value: string, proxied?: "proxied", staleAfter?: number): Promise<void>;
151
212
  /** Keeps existing records */
152
- export declare function addRecord(type: string, key: string, value: string, proxied?: "proxied"): Promise<void>;
153
- /** Provide Cloudflare credentials directly instead of relying on ./cloudflare.json. Exactly one of
154
- * key (the API token itself) or path (a file to read it from) — TypeScript rejects both/neither. */
155
- export declare function setCloudflareCredentials(config: {
156
- value: {
157
- key: string;
158
- } | {
159
- path: string;
160
- };
161
- }): void;
213
+ export declare function addRecord(type: string, key: string, value: string, proxied?: "proxied", staleAfter?: number): Promise<void>;
162
214
 
163
215
  }
164
216
 
@@ -167,12 +219,6 @@ declare module "sliftutils/misc/https/hostServer" {
167
219
  /** Full domain to host on (e.g. "testsite.example.com"). The HTTPS cert is created for this domain and *.domain, so using a subdomain never touches the root domain (beyond its _acme-challenge TXT record). */
168
220
  domain: string;
169
221
  port: number;
170
- /** Cloudflare API token: either the token string ({ key }) or a path to a file containing it ({ path }). Required — pass { path: "./cloudflare.json" } explicitly for the on-disk file. */
171
- cloudflareApiToken: {
172
- key: string;
173
- } | {
174
- path: string;
175
- };
176
222
  /** Creates an unproxied A record pointing domain at this machine (publicIp, or our detected external IP) */
177
223
  setDNSRecord?: boolean;
178
224
  publicIp?: string;
@@ -822,6 +868,7 @@ declare module "sliftutils/storage/ArchivesDisk" {
822
868
  };
823
869
  getDebugName(): string;
824
870
  getConfig(): Promise<ArchivesConfig>;
871
+ hasWriteAccess(): Promise<boolean>;
825
872
  private filePath;
826
873
  set(key: string, data: Buffer, config?: {
827
874
  lastModified?: number;
@@ -841,6 +888,7 @@ declare module "sliftutils/storage/ArchivesDisk" {
841
888
  }): Promise<{
842
889
  data: Buffer;
843
890
  writeTime: number;
891
+ size: number;
844
892
  } | undefined>;
845
893
  getInfo(key: string): Promise<{
846
894
  writeTime: number;
@@ -2049,6 +2097,7 @@ declare module "sliftutils/storage/IArchives" {
2049
2097
  /// <reference types="node" />
2050
2098
  /// <reference types="node" />
2051
2099
  export declare const MAX_LAST_MODIFIED_FUTURE: number;
2100
+ export declare const IMMUTABLE_CACHE_TIME: number;
2052
2101
  export declare function assertValidLastModified(lastModified: number): void;
2053
2102
  export type RemoteConfig = {
2054
2103
  version?: number;
@@ -2067,8 +2116,14 @@ declare module "sliftutils/storage/IArchives" {
2067
2116
  */
2068
2117
  export type RemoteConfigBase = string | HostedConfig | BackblazeConfig;
2069
2118
  export type CommonConfig = {
2070
- /** The default options for the first config in a list is DEFAULT_BASE_SYNC_OPTIONS. The rest default to DEFAULT_SYNC_OPTIONS. */
2071
- syncOptions?: SyncOptions;
2119
+ /** By default a server hosting this bucket eagerly copies this source's full contents onto its own disk (on top of the lazy read-through caching). Set this to be a front end for a very large database without copying the full database - reads still down-cache individual files on demand. */
2120
+ noFullSync?: boolean;
2121
+ /** Bytes of read-cache this server's disk may hold; least-recently-used files are deleted from disk to stay under it (only ever when another source verifiably holds the file - the only copy is never deleted). Requires noFullSync (a full copy can't be bounded). */
2122
+ readerDiskLimit?: number;
2123
+ /** The write times ([startMs, endMs]) this source is valid for (see ArchivesSource.validWindow for the synchronization semantics). Required on object configs: configuration changes must be SCHEDULED (a new source becomes valid at a future time while the old one's window ends), not flipped instantly. Plain URL-string sources default to FULL_VALID_WINDOW - once you're writing object configs, you're doing something complicated enough to think about when things change. */
2124
+ validWindow: [number, number];
2125
+ /** Sharding: the fraction of the key space this source handles, as [start, end) over [0, 1) (keys are routed by getRoute in remoteConfig.ts). Defaults to FULL_ROUTE (unsharded). At every point in time the sources' routes must fully cover [0, 1), or some keys could never be read. */
2126
+ route?: [number, number];
2072
2127
  };
2073
2128
  export type HostedConfig = CommonConfig & {
2074
2129
  type: "remote";
@@ -2083,34 +2138,56 @@ declare module "sliftutils/storage/IArchives" {
2083
2138
  export type BackblazeConfig = CommonConfig & {
2084
2139
  type: "backblaze";
2085
2140
  url: string;
2086
- bucketName: string;
2087
2141
  public?: boolean;
2088
2142
  immutable?: boolean;
2089
2143
  };
2090
- export declare const DEFAULT_BASE_SYNC_OPTIONS: SyncOptions;
2091
- export declare const DEFAULT_SYNC_OPTIONS: SyncOptions;
2144
+ export declare const FULL_VALID_WINDOW: [number, number];
2092
2145
  export type ArchiveFileInfo = {
2093
2146
  path: string;
2094
2147
  createTime: number;
2095
2148
  size: number;
2096
2149
  };
2150
+ export type SyncActivity = {
2151
+ type: "metadataScan" | "fullSync";
2152
+ sourceDebugName: string;
2153
+ startTime: number;
2154
+ doneFiles?: number;
2155
+ totalFiles?: number;
2156
+ doneBytes?: number;
2157
+ totalBytes?: number;
2158
+ };
2097
2159
  export type ArchivesConfig = {
2098
2160
  supportsChangesAfter?: boolean;
2099
- };
2100
- export type SyncOptions = {
2101
- copyFiles?: boolean;
2102
- noWriteBack?: boolean;
2103
- cacheReads?: boolean;
2104
- validWindow: [number, number];
2105
- required?: boolean;
2161
+ remoteConfig?: RemoteConfig;
2162
+ index?: {
2163
+ fileCount: number;
2164
+ byteCount: number;
2165
+ };
2166
+ indexSources?: {
2167
+ debugName: string;
2168
+ fileCount: number;
2169
+ byteCount: number;
2170
+ }[];
2171
+ readerDiskLimit?: number;
2172
+ syncing?: SyncActivity[];
2106
2173
  };
2107
2174
  export type ArchivesSource = {
2108
2175
  source: IArchives;
2109
- options: SyncOptions;
2110
- };
2176
+ validWindow: [number, number];
2177
+ route?: [number, number];
2178
+ noFullSync?: boolean;
2179
+ };
2180
+ export declare const WRITE_PAST_WINDOW_GRACE: number;
2181
+ export declare const STORAGE_WRONG_VALID_WINDOW = "REMOTE_STORAGE_WRONG_VALID_WINDOW_a7c1f04e";
2182
+ export declare const STORAGE_WRONG_ROUTE = "REMOTE_STORAGE_WRONG_ROUTE_c94d2e17";
2183
+ export declare const FULL_ROUTE: [number, number];
2184
+ export declare const VARIABLE_SHARD = "VARIABLE_SHARD_f0234jfah08fgyhfgyssdds83nmp";
2185
+ export declare function windowAcceptsWrites(validWindow: [number, number] | undefined): boolean;
2111
2186
  export type ArchivesSyncSourceStatus = {
2112
2187
  debugName: string;
2113
- options: SyncOptions;
2188
+ validWindow: [number, number];
2189
+ route?: [number, number];
2190
+ noFullSync?: boolean;
2114
2191
  supportsChangesAfter: boolean;
2115
2192
  initialScanComplete: boolean;
2116
2193
  scannedCount: number;
@@ -2122,13 +2199,15 @@ declare module "sliftutils/storage/IArchives" {
2122
2199
  };
2123
2200
  export interface IArchives {
2124
2201
  getDebugName(): string;
2202
+ /** Whether writes would be accepted (credentials exist, the account trusts this machine, etc).
2203
+ * Checked without writing anything. */
2204
+ hasWriteAccess(): Promise<boolean>;
2125
2205
  get(fileName: string, config?: {
2126
2206
  range?: {
2127
2207
  start: number;
2128
2208
  end: number;
2129
2209
  };
2130
2210
  }): Promise<Buffer | undefined>;
2131
- /** Like get, but also returns the last-write time of the file. get just calls get2. */
2132
2211
  get2(fileName: string, config?: {
2133
2212
  range?: {
2134
2213
  start: number;
@@ -2137,6 +2216,7 @@ declare module "sliftutils/storage/IArchives" {
2137
2216
  }): Promise<{
2138
2217
  data: Buffer;
2139
2218
  writeTime: number;
2219
+ size: number;
2140
2220
  } | undefined>;
2141
2221
  /**
2142
2222
  * lastModified stamps the write with that last-write time instead of now. If it is OLDER than
@@ -2444,6 +2524,7 @@ declare module "sliftutils/storage/backblaze" {
2444
2524
  public?: boolean;
2445
2525
  immutable?: boolean;
2446
2526
  cacheTime?: number;
2527
+ allowedOrigins?: string[];
2447
2528
  });
2448
2529
  private bucketName;
2449
2530
  private bucketId;
@@ -2452,6 +2533,7 @@ declare module "sliftutils/storage/backblaze" {
2452
2533
  private log;
2453
2534
  getDebugName(): string;
2454
2535
  private getBucketAPI;
2536
+ private currentReset;
2455
2537
  private last503Reset;
2456
2538
  private apiRetryLogic;
2457
2539
  get(fileName: string, config?: {
@@ -2469,8 +2551,10 @@ declare module "sliftutils/storage/backblaze" {
2469
2551
  }): Promise<{
2470
2552
  data: Buffer;
2471
2553
  writeTime: number;
2554
+ size: number;
2472
2555
  } | undefined>;
2473
2556
  getConfig(): Promise<ArchivesConfig>;
2557
+ hasWriteAccess(): Promise<boolean>;
2474
2558
  set(fileName: string, data: Buffer, config?: {
2475
2559
  lastModified?: number;
2476
2560
  }): Promise<void>;
@@ -2496,6 +2580,17 @@ declare module "sliftutils/storage/backblaze" {
2496
2580
  size: number;
2497
2581
  }[]>;
2498
2582
  assertPathValid(path: string): Promise<void>;
2583
+ move(config: {
2584
+ path: string;
2585
+ target: IArchives;
2586
+ targetPath: string;
2587
+ copyInstead?: boolean;
2588
+ }): Promise<void>;
2589
+ copy(config: {
2590
+ path: string;
2591
+ target: IArchives;
2592
+ targetPath: string;
2593
+ }): Promise<void>;
2499
2594
  getURL(path: string): Promise<string>;
2500
2595
  getDownloadAuthorization(config: {
2501
2596
  fileNamePrefix?: string;
@@ -2784,6 +2879,8 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
2784
2879
  private controller;
2785
2880
  private lastDeniedLog;
2786
2881
  getDebugName(): string;
2882
+ isConnected(): boolean;
2883
+ ping(): Promise<void>;
2787
2884
  private authenticate;
2788
2885
  private callAuthed;
2789
2886
  waitingForAccess(): Promise<{
@@ -2791,7 +2888,8 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
2791
2888
  machineId: string;
2792
2889
  ip: string;
2793
2890
  } | undefined>;
2794
- private onAccessDenied;
2891
+ hasWriteAccess(): Promise<boolean>;
2892
+ private registerAccessRequest;
2795
2893
  private call;
2796
2894
  get(fileName: string, config?: {
2797
2895
  range?: {
@@ -2807,6 +2905,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
2807
2905
  }): Promise<{
2808
2906
  data: Buffer;
2809
2907
  writeTime: number;
2908
+ size: number;
2810
2909
  } | undefined>;
2811
2910
  set(fileName: string, data: Buffer, config?: {
2812
2911
  lastModified?: number;
@@ -2859,6 +2958,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
2859
2958
  }): Promise<{
2860
2959
  data: Buffer;
2861
2960
  writeTime: number;
2961
+ size: number;
2862
2962
  } | undefined>;
2863
2963
  getInfo(fileName: string): Promise<{
2864
2964
  writeTime: number;
@@ -2882,6 +2982,7 @@ declare module "sliftutils/storage/remoteStorage/ArchivesUrl" {
2882
2982
  }): Promise<ArchiveFileInfo[]>;
2883
2983
  getURL(path: string): Promise<string>;
2884
2984
  getConfig(): Promise<ArchivesConfig>;
2985
+ hasWriteAccess(): Promise<boolean>;
2885
2986
  }
2886
2987
 
2887
2988
  }
@@ -2894,7 +2995,7 @@ declare module "sliftutils/storage/remoteStorage/accessPage" {
2894
2995
  declare module "sliftutils/storage/remoteStorage/blobStore" {
2895
2996
  /// <reference types="node" />
2896
2997
  /// <reference types="node" />
2897
- import { ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus } from "../IArchives";
2998
+ import { ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, SyncActivity } from "../IArchives";
2898
2999
  export declare const DEFAULT_FAST_WRITE_DELAY: number;
2899
3000
  export type WriteConfig = {
2900
3001
  fast?: boolean;
@@ -2916,6 +3017,7 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
2916
3017
  }): Promise<{
2917
3018
  data: Buffer;
2918
3019
  writeTime: number;
3020
+ size: number;
2919
3021
  } | undefined>;
2920
3022
  set(fileName: string, data: Buffer, config?: WriteConfig): Promise<void>;
2921
3023
  del(fileName: string, config?: WriteConfig): Promise<void>;
@@ -2929,6 +3031,28 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
2929
3031
  }): Promise<ArchiveFileInfo[]>;
2930
3032
  getChangesAfter?(time: number): Promise<ArchiveFileInfo[]>;
2931
3033
  getSyncStatus?(): Promise<ArchivesSyncStatus>;
3034
+ getSyncProgress?(): {
3035
+ index: {
3036
+ fileCount: number;
3037
+ byteCount: number;
3038
+ };
3039
+ sources: {
3040
+ debugName: string;
3041
+ fileCount: number;
3042
+ byteCount: number;
3043
+ }[];
3044
+ readerDiskLimit?: number;
3045
+ syncing: SyncActivity[];
3046
+ };
3047
+ computeIndexTotals?(): Promise<{
3048
+ fileCount: number;
3049
+ byteCount: number;
3050
+ sources: {
3051
+ debugName: string;
3052
+ fileCount: number;
3053
+ byteCount: number;
3054
+ }[];
3055
+ }>;
2932
3056
  startLargeUpload(): Promise<string>;
2933
3057
  appendLargeUpload(id: string, data: Buffer): Promise<void>;
2934
3058
  finishLargeUpload(id: string, key: string): Promise<void>;
@@ -2940,10 +3064,16 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
2940
3064
  private config?;
2941
3065
  constructor(folder: string, sources: ArchivesSource[], config?: {
2942
3066
  onIndexChanged?: ((key: string) => void) | undefined;
3067
+ readerDiskLimit?: number | undefined;
2943
3068
  } | undefined);
2944
3069
  private stopped;
2945
3070
  private index;
2946
3071
  private mem;
3072
+ private indexFileCount;
3073
+ private indexByteCount;
3074
+ private sourceFileCounts;
3075
+ private sourceByteCounts;
3076
+ private syncActivities;
2947
3077
  private dirty;
2948
3078
  private overlay;
2949
3079
  private sourceStates;
@@ -2954,11 +3084,38 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
2954
3084
  };
2955
3085
  dispose(): Promise<void>;
2956
3086
  private loadIndex;
3087
+ private countEntry;
2957
3088
  private setIndexEntry;
2958
3089
  private deleteIndexEntry;
3090
+ /** The cheap always-current totals plus any in-progress background synchronization. */
3091
+ getSyncProgress(): {
3092
+ index: {
3093
+ fileCount: number;
3094
+ byteCount: number;
3095
+ };
3096
+ sources: {
3097
+ debugName: string;
3098
+ fileCount: number;
3099
+ byteCount: number;
3100
+ }[];
3101
+ readerDiskLimit?: number;
3102
+ syncing: SyncActivity[];
3103
+ };
3104
+ /** Walks the whole index for exact totals - more expensive than getSyncProgress, but immune to
3105
+ * any drift in the maintained counters (and loads the index first, so it's never cold zeros). */
3106
+ computeIndexTotals(): Promise<{
3107
+ fileCount: number;
3108
+ byteCount: number;
3109
+ sources: {
3110
+ debugName: string;
3111
+ fileCount: number;
3112
+ byteCount: number;
3113
+ }[];
3114
+ }>;
2959
3115
  private flushIndex;
2960
3116
  private runSourceSync;
2961
3117
  private scanSource;
3118
+ private reconcileSource;
2962
3119
  private applyScanned;
2963
3120
  private pollChanges;
2964
3121
  private copySourceFiles;
@@ -2979,12 +3136,13 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
2979
3136
  }): Promise<{
2980
3137
  data: Buffer;
2981
3138
  writeTime: number;
3139
+ size: number;
2982
3140
  } | undefined>;
2983
3141
  private cacheRead;
2984
3142
  set(key: string, data: Buffer, config?: WriteConfig): Promise<void>;
2985
- private writeToSources;
2986
3143
  del(key: string, config?: WriteConfig): Promise<void>;
2987
- private deleteFromSources;
3144
+ private getWritableSources;
3145
+ private writeToSources;
2988
3146
  getInfo(key: string): Promise<{
2989
3147
  writeTime: number;
2990
3148
  size: number;
@@ -3001,6 +3159,9 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
3001
3159
  finishLargeUpload(id: string, key: string): Promise<void>;
3002
3160
  cancelLargeUpload(id: string): Promise<void>;
3003
3161
  private flushOverlay;
3162
+ private evicting;
3163
+ private enforceDiskLimit;
3164
+ private cleanupTombstones;
3004
3165
  }
3005
3166
 
3006
3167
  }
@@ -3016,19 +3177,23 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
3016
3177
  import { IArchives, RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
3017
3178
  export declare function createApiArchives(source: HostedConfig | BackblazeConfig): IArchives;
3018
3179
  export declare class ArchivesChain implements IArchives {
3019
- private normalized;
3020
- private adopted;
3021
- private sourcesPromise;
3180
+ private configured;
3181
+ private activeConfig;
3182
+ private statePromise;
3183
+ private initRetryDelay;
3184
+ private initRetryTimer;
3185
+ private pollTimer;
3186
+ private disposed;
3022
3187
  constructor(config: RemoteConfig | RemoteConfigBase);
3023
3188
  getDebugName(): string;
3024
- private getSourceConfigs;
3025
- private getSources;
3189
+ private getState;
3026
3190
  private init;
3027
- private ensureRouting;
3028
- private readFromSource;
3029
- private read;
3030
- private getAccessHelp;
3031
- private write;
3191
+ private buildSources;
3192
+ private startConfigPoll;
3193
+ private checkForNewConfig;
3194
+ private run;
3195
+ private runWrite;
3196
+ private request;
3032
3197
  waitingForAccess(): Promise<{
3033
3198
  link: string;
3034
3199
  machineId: string;
@@ -3048,11 +3213,14 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
3048
3213
  }): Promise<{
3049
3214
  data: Buffer;
3050
3215
  writeTime: number;
3216
+ size: number;
3051
3217
  } | undefined>;
3052
3218
  getInfo(fileName: string): Promise<{
3053
3219
  writeTime: number;
3054
3220
  size: number;
3055
3221
  } | undefined>;
3222
+ private selectCoveringSources;
3223
+ private runOnApi;
3056
3224
  find(prefix: string, config?: {
3057
3225
  shallow?: boolean;
3058
3226
  type: "files" | "folders";
@@ -3064,15 +3232,29 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
3064
3232
  getChangesAfter(time: number): Promise<ArchiveFileInfo[]>;
3065
3233
  getSyncStatus(): Promise<ArchivesSyncStatus>;
3066
3234
  getConfig(): Promise<ArchivesConfig>;
3235
+ /** True only when EVERY write-receiving source would accept our writes (partial write access
3236
+ * desynchronizes sources, so it counts as no access). */
3237
+ hasWriteAccess(): Promise<boolean>;
3238
+ private assertNotBareVariableShard;
3067
3239
  set(fileName: string, data: Buffer, config?: {
3068
3240
  lastModified?: number;
3069
3241
  }): Promise<void>;
3070
3242
  del(fileName: string): Promise<void>;
3243
+ /** Writes a key containing the VARIABLE_SHARD sentinel: picks the lowest-latency up write
3244
+ * shard, materializes the key with a random value inside that shard's route, writes it, and
3245
+ * returns the FULL key actually written (the caller needs it to ever read the value back).
3246
+ * Unlike normal writes this CAN move to another shard when the preferred one is down (error +
3247
+ * socket down, same rule as reads) - each shard receives a different key, so write
3248
+ * consistency is preserved. */
3249
+ setVariableShard(key: string, data: Buffer, config?: {
3250
+ lastModified?: number;
3251
+ }): Promise<string>;
3071
3252
  setLargeFile(config: {
3072
3253
  path: string;
3073
3254
  getNextData(): Promise<Buffer | undefined>;
3074
3255
  }): Promise<void>;
3075
3256
  getURL(path: string): Promise<string>;
3257
+ dispose(): void;
3076
3258
  }
3077
3259
  export declare function createArchives(config: RemoteConfig | RemoteConfigBase): ArchivesChain;
3078
3260
 
@@ -3088,6 +3270,14 @@ declare module "sliftutils/storage/remoteStorage/remoteConfig" {
3088
3270
  /// <reference types="node" />
3089
3271
  import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "../IArchives";
3090
3272
  export declare const ROUTING_FILE = "storage/storagerouting.json";
3273
+ /** 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. */
3274
+ export declare function parseVariableRoute(key: string): number | undefined;
3275
+ /** Where a key routes in [0, 1). A materialized variable-shard suffix completely overrides the hash. */
3276
+ export declare function getRoute(key: string): number;
3277
+ export declare function routeContains(route: [number, number] | undefined, value: number): boolean;
3278
+ export declare function routesOverlap(a: [number, number] | undefined, b: [number, number] | undefined): boolean;
3279
+ /** The overlap of two route ranges, or undefined when they don't overlap. */
3280
+ export declare function routeIntersection(a: [number, number] | undefined, b: [number, number] | undefined): [number, number] | undefined;
3091
3281
  export declare function getConfigVersion(config: RemoteConfig): number;
3092
3282
  /** Strips the routing-file suffix, leaving the bucket's public base URL (file paths append to it). */
3093
3283
  export declare function getBucketBaseUrl(url: string): string;
@@ -3108,6 +3298,51 @@ declare module "sliftutils/storage/remoteStorage/remoteConfig" {
3108
3298
 
3109
3299
  }
3110
3300
 
3301
+ declare module "sliftutils/storage/remoteStorage/sourceWrapper" {
3302
+ import { IArchives, HostedConfig, BackblazeConfig } from "../IArchives";
3303
+ import { ArchivesUrl } from "./ArchivesUrl";
3304
+ export declare const RETRY_START_DELAY: number;
3305
+ export declare const RETRY_MAX_DELAY: number;
3306
+ export declare const RETRY_GROWTH = 1.5;
3307
+ export declare class SourceWrapper {
3308
+ config: HostedConfig | BackblazeConfig;
3309
+ private background;
3310
+ api?: IArchives;
3311
+ url?: ArchivesUrl;
3312
+ writeBlocked?: string;
3313
+ private remote?;
3314
+ private disposed;
3315
+ private reconnectRunning;
3316
+ private accessCache?;
3317
+ private constructor();
3318
+ static create(config: HostedConfig | BackblazeConfig, options?: {
3319
+ background?: boolean;
3320
+ }): Promise<SourceWrapper>;
3321
+ getDebugName(): string;
3322
+ isConnected(): boolean;
3323
+ /** Call after a request failed while isConnected() was false: starts (if not already running)
3324
+ * the background reconnect loop. Never blocks - the failed request still throws. */
3325
+ noteFailure(): void;
3326
+ private reconnectLoop;
3327
+ private checkAccess;
3328
+ read<T>(run: (archives: IArchives) => Promise<T>): Promise<T>;
3329
+ hasWriteAccess(): Promise<boolean>;
3330
+ private pings;
3331
+ private pingTimer;
3332
+ /** Starts measuring this source's latency (for variable-shard target preference). Only hosted
3333
+ * remotes are pinged; our own local server counts as 0, everything else as Infinity. */
3334
+ startPinging(): void;
3335
+ /** Median of the recent pings. Sources that can't be pinged sort last (Infinity), except our
3336
+ * own in-process server, which is the best possible target (0). */
3337
+ getLatency(): number;
3338
+ /** Writes always go through the API, so a permission error throws to the caller on every write
3339
+ * (and access granted in the meantime is picked up automatically). */
3340
+ write<T>(run: (archives: IArchives) => Promise<T>): Promise<T>;
3341
+ dispose(): void;
3342
+ }
3343
+
3344
+ }
3345
+
3111
3346
  declare module "sliftutils/storage/remoteStorage/storageController" {
3112
3347
  /// <reference types="node" />
3113
3348
  /// <reference types="node" />
@@ -3142,6 +3377,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
3142
3377
  trustedMachines?: TrustRecord[];
3143
3378
  };
3144
3379
  export declare const RemoteStorageController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
3380
+ ping: () => Promise<void>;
3145
3381
  authenticate: (token: AuthToken) => Promise<{
3146
3382
  machineId: string;
3147
3383
  ip: string;
@@ -3167,6 +3403,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
3167
3403
  }) => Promise<{
3168
3404
  data: Buffer;
3169
3405
  writeTime: number;
3406
+ size: number;
3170
3407
  } | undefined>;
3171
3408
  set: (account: string, bucketName: string, path: string, data: Buffer, lastModified?: number) => Promise<void>;
3172
3409
  del: (account: string, bucketName: string, path: string) => Promise<void>;
@@ -3180,6 +3417,15 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
3180
3417
  }) => Promise<ArchiveFileInfo[]>;
3181
3418
  getChangesAfter: (account: string, bucketName: string, time: number) => Promise<ArchiveFileInfo[]>;
3182
3419
  getArchivesConfig: (account: string, bucketName: string) => Promise<ArchivesConfig>;
3420
+ getIndexInfo: (account: string, bucketName: string) => Promise<{
3421
+ fileCount: number;
3422
+ byteCount: number;
3423
+ sources: {
3424
+ debugName: string;
3425
+ fileCount: number;
3426
+ byteCount: number;
3427
+ }[];
3428
+ } | undefined>;
3183
3429
  getSyncStatus: (account: string, bucketName: string) => Promise<ArchivesSyncStatus>;
3184
3430
  startLargeFile: (account: string, bucketName: string, path: string) => Promise<string>;
3185
3431
  uploadPart: (uploadId: string, data: Buffer) => Promise<void>;
@@ -3198,11 +3444,6 @@ declare module "sliftutils/storage/remoteStorage/storageServer" {
3198
3444
  export type HostStorageServerConfig = {
3199
3445
  url: string;
3200
3446
  folder: string;
3201
- cloudflareApiToken?: {
3202
- key: string;
3203
- } | {
3204
- path: string;
3205
- };
3206
3447
  lowSpaceThresholdBytes?: number;
3207
3448
  };
3208
3449
  export declare function hostStorageServer(config: HostStorageServerConfig): Promise<void>;
@@ -3237,16 +3478,22 @@ declare module "sliftutils/storage/remoteStorage/storageServerState" {
3237
3478
  export declare function assertWritesAllowed(): void;
3238
3479
  export declare function getTrust(): Promise<IStorage<TrustRecord>>;
3239
3480
  export declare function getRequests(): Promise<IStorage<AccessRequest[]>>;
3481
+ /** Makes machineIds the complete trust list for the account: machines not in the list lose access, machines already trusted keep their existing record, and missing ones are added. */
3482
+ export declare function setTrustedMachines(config: {
3483
+ account: string;
3484
+ machineIds: string[];
3485
+ }): Promise<void>;
3240
3486
  export type LoadedBucket = {
3241
3487
  account: string;
3242
3488
  bucketName: string;
3243
3489
  routing: RemoteConfig;
3244
3490
  routingJSON: string;
3491
+ selfEntries: HostedConfig[];
3245
3492
  self: HostedConfig | undefined;
3246
3493
  store: IBucketStore;
3247
3494
  };
3248
3495
  export declare function getLoadedBucket(account: string, bucketName: string): Promise<LoadedBucket | undefined>;
3249
- export declare function assertMutable(bucket: LoadedBucket, filePath: string): Promise<void>;
3496
+ export declare function assertMutable(bucket: LoadedBucket, filePath: string, writeTime: number): Promise<void>;
3250
3497
  export declare function writeBucketFile(account: string, bucketName: string, filePath: string, data: Buffer, config?: {
3251
3498
  lastModified?: number;
3252
3499
  }): Promise<void>;