sliftutils 1.7.97 → 1.7.99
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 +18 -1
- package/package.json +1 -1
- package/storage/remoteStorage/blobStore.d.ts +2 -0
- package/storage/remoteStorage/blobStore.ts +19 -1
- package/storage/remoteStorage/chainStartup.d.ts +1 -0
- package/storage/remoteStorage/chainStartup.ts +6 -1
- package/storage/remoteStorage/createArchives.ts +1 -1
- package/storage/remoteStorage/dist/blobStore.ts.cache +21 -2
- package/storage/remoteStorage/dist/chainStartup.ts.cache +5 -2
- package/storage/remoteStorage/dist/createArchives.ts.cache +3 -3
- package/storage/remoteStorage/dist/storageClientController.ts.cache +45 -4
- package/storage/remoteStorage/dist/storageServerState.ts.cache +15 -4
- package/storage/remoteStorage/dist/storeConfig.ts.cache +7 -2
- package/storage/remoteStorage/storageClientController.d.ts +14 -0
- package/storage/remoteStorage/storageClientController.ts +42 -1
- package/storage/remoteStorage/storageServerState.d.ts +1 -1
- package/storage/remoteStorage/storageServerState.ts +11 -2
- package/storage/remoteStorage/storeConfig.ts +5 -0
package/index.d.ts
CHANGED
|
@@ -3296,6 +3296,8 @@ declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
|
3296
3296
|
onIndexChanged?: ((key: string) => void) | undefined;
|
|
3297
3297
|
/** Called every time this store applies a routing config to itself (startup, an operator's write, a peer's copy arriving) - the store is the one that knows when a config landed, and the server arms window-boundary scans from it. */
|
|
3298
3298
|
onRoutingApplied?: ((routing: RemoteConfig) => void) | undefined;
|
|
3299
|
+
/** Asks the client whose request created this store what routing config it intended for our name. Only used when init finds NO configuration in our folder: a store only ever exists because a config names it, so the requester has that config - asking for it lazily is the same information as passing the config on every call, without the per-call kilobytes. */
|
|
3300
|
+
requestRoutingConfig?: (() => Promise<RemoteConfig | undefined>) | undefined;
|
|
3299
3301
|
onWriteCounted?: ((kind: "original" | "flushed", bytes: number) => void) | undefined;
|
|
3300
3302
|
resolveSourceUrl?: ((url: string) => IArchives) | undefined;
|
|
3301
3303
|
} | undefined);
|
|
@@ -3601,6 +3603,7 @@ declare module "sliftutils/storage/remoteStorage/chainStartup" {
|
|
|
3601
3603
|
/** See ArchivesChainOptions.directConnect. */
|
|
3602
3604
|
directConnect?: boolean;
|
|
3603
3605
|
});
|
|
3606
|
+
private untrackConfig;
|
|
3604
3607
|
/** The newest adopted state, synchronously - undefined until the first init finishes. */
|
|
3605
3608
|
latest(): ChainState | undefined;
|
|
3606
3609
|
getState(): Promise<ChainState>;
|
|
@@ -4071,11 +4074,25 @@ declare module "sliftutils/storage/remoteStorage/sourcesList" {
|
|
|
4071
4074
|
}
|
|
4072
4075
|
|
|
4073
4076
|
declare module "sliftutils/storage/remoteStorage/storageClientController" {
|
|
4077
|
+
import { RemoteConfig } from "../IArchives";
|
|
4074
4078
|
/** Subscribe to server-pushed routing change notifications. Returns the unsubscribe function. */
|
|
4075
4079
|
export declare function onServerRoutingChanged(listener: () => void): () => void;
|
|
4080
|
+
/** One chain's configs, as getRoutingConfigForName answers from them: the initial in-code config, and a getter for the synchronized one (adopted from the stored routing files, so it changes over time). */
|
|
4081
|
+
type TrackedChainConfig = {
|
|
4082
|
+
configured: RemoteConfig;
|
|
4083
|
+
active: () => RemoteConfig;
|
|
4084
|
+
};
|
|
4085
|
+
/** Every chain tracks its configs here (see ChainStateManager), so a server can ask what config was intended for a store name. Returns the untrack function. */
|
|
4086
|
+
export declare function trackChainConfig(entry: TrackedChainConfig): () => void;
|
|
4076
4087
|
export declare const StorageClientController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
|
|
4077
4088
|
routingConfigChanged: () => Promise<void>;
|
|
4089
|
+
getRoutingConfigForName: (config: {
|
|
4090
|
+
account: string;
|
|
4091
|
+
bucketName: string;
|
|
4092
|
+
name: string;
|
|
4093
|
+
}) => Promise<RemoteConfig | undefined>;
|
|
4078
4094
|
}>;
|
|
4095
|
+
export {};
|
|
4079
4096
|
|
|
4080
4097
|
}
|
|
4081
4098
|
|
|
@@ -4317,7 +4334,7 @@ declare module "sliftutils/storage/remoteStorage/storageServerState" {
|
|
|
4317
4334
|
import { RemoteConfig, SourceConfig, IArchives, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
4318
4335
|
import { BucketDiskInfo } from "./bucketDisk";
|
|
4319
4336
|
import { BucketWriteStats } from "./accessStats";
|
|
4320
|
-
export declare function getStore(account: string, bucketName: string, name: string): BlobStore;
|
|
4337
|
+
export declare function getStore(account: string, bucketName: string, name: string, callerNodeId?: string): BlobStore;
|
|
4321
4338
|
/** The store serving a request: the one the client's selected entry NAMES. Account, name, and bucket ARE the folder, so this is a direct lookup, never a search of what exists - and a name this server has never seen is CREATED, never rejected, because asking for a name is the instruction to have that store (one name, one folder, one index; it configures itself once the routing config lands in it). Nothing else about the request is compared - not the window, not the route, not the flags - which is the whole point of naming it: a client a config version behind on some flag still reaches the right store. */
|
|
4322
4339
|
export declare function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined): BlobStore;
|
|
4323
4340
|
/** The stores of a bucket as the DISK records them (a bucket is nothing more than the store folders sharing its name), opened - so the ones that weren't running yet start synchronizing. Empty when the bucket does not exist here. */
|
package/package.json
CHANGED
|
@@ -59,6 +59,8 @@ export declare class BlobStore {
|
|
|
59
59
|
onIndexChanged?: ((key: string) => void) | undefined;
|
|
60
60
|
/** Called every time this store applies a routing config to itself (startup, an operator's write, a peer's copy arriving) - the store is the one that knows when a config landed, and the server arms window-boundary scans from it. */
|
|
61
61
|
onRoutingApplied?: ((routing: RemoteConfig) => void) | undefined;
|
|
62
|
+
/** Asks the client whose request created this store what routing config it intended for our name. Only used when init finds NO configuration in our folder: a store only ever exists because a config names it, so the requester has that config - asking for it lazily is the same information as passing the config on every call, without the per-call kilobytes. */
|
|
63
|
+
requestRoutingConfig?: (() => Promise<RemoteConfig | undefined>) | undefined;
|
|
62
64
|
onWriteCounted?: ((kind: "original" | "flushed", bytes: number) => void) | undefined;
|
|
63
65
|
resolveSourceUrl?: ((url: string) => IArchives) | undefined;
|
|
64
66
|
} | undefined);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
windowsAcceptWrites, SyncActivity, FULL_ROUTE, FULL_VALID_WINDOW, STORAGE_WRONG_VALID_WINDOW, STORAGE_WRONG_ROUTE, STORAGE_NOT_CONFIGURED,
|
|
7
7
|
} from "../IArchives";
|
|
8
8
|
import { ArchivesDisk, applyFindInfoShape } from "../ArchivesDisk";
|
|
9
|
-
import { ROUTING_FILE, getRoute, routeContains, routeIntersection, parseRoutingData, assertValidRemoteConfig, getConfigVersion, sourceIdentity, sourcePersistentUrl } from "./remoteConfig";
|
|
9
|
+
import { ROUTING_FILE, getRoute, routeContains, routeIntersection, parseRoutingData, serializeRemoteConfig, assertValidRemoteConfig, getConfigVersion, sourceIdentity, sourcePersistentUrl } from "./remoteConfig";
|
|
10
10
|
import { selectEntryAt } from "./storePlan";
|
|
11
11
|
import { sourceWriteDelay } from "./ArchivesDelayed";
|
|
12
12
|
import { LogMap } from "../LogMap";
|
|
@@ -102,6 +102,8 @@ export class BlobStore {
|
|
|
102
102
|
onIndexChanged?: (key: string) => void;
|
|
103
103
|
/** Called every time this store applies a routing config to itself (startup, an operator's write, a peer's copy arriving) - the store is the one that knows when a config landed, and the server arms window-boundary scans from it. */
|
|
104
104
|
onRoutingApplied?: (routing: RemoteConfig) => void;
|
|
105
|
+
/** Asks the client whose request created this store what routing config it intended for our name. Only used when init finds NO configuration in our folder: a store only ever exists because a config names it, so the requester has that config - asking for it lazily is the same information as passing the config on every call, without the per-call kilobytes. */
|
|
106
|
+
requestRoutingConfig?: () => Promise<RemoteConfig | undefined>;
|
|
105
107
|
// Every accepted write ("original") and every write that actually reached the sources ("flushed"). Fast writes coalesce, so the two counts differ.
|
|
106
108
|
onWriteCounted?: (kind: "original" | "flushed", bytes: number) => void;
|
|
107
109
|
// Resolves a persisted source URL (see ArchivesSource.url) to a cached IArchives, so entries whose holder is no longer configured can still be read
|
|
@@ -130,6 +132,22 @@ export class BlobStore {
|
|
|
130
132
|
public init = lazy(async () => {
|
|
131
133
|
// Configure first: the config decides what the sources ARE, and everything below is per source. With no config at all that is just our own disk, which is enough to serve and to be written to.
|
|
132
134
|
await this.applyRoutingConfig();
|
|
135
|
+
// No configuration in our folder at all - which happens when this store was just CREATED by a request, before any config write reached it. The requester knows exactly what config it intended for our name, so ask it.
|
|
136
|
+
if (!this.storeConfig.all().length && this.config?.requestRoutingConfig) {
|
|
137
|
+
try {
|
|
138
|
+
let provided = await this.config.requestRoutingConfig();
|
|
139
|
+
if (provided) {
|
|
140
|
+
console.log(`Store ${JSON.stringify(this.storeName)} (folder ${this.folder}) initialized with no configuration; the requesting client provided routing config version ${getConfigVersion(provided)} - adopting it`);
|
|
141
|
+
// Straight onto the disk, not through set(): set waits for init, which is what is running right now
|
|
142
|
+
await this.ownDisk.set(ROUTING_FILE, Buffer.from(serializeRemoteConfig(provided)), { lastModified: Date.now() });
|
|
143
|
+
await this.applyRoutingConfig();
|
|
144
|
+
} else {
|
|
145
|
+
console.warn(`Store ${JSON.stringify(this.storeName)} (folder ${this.folder}) initialized with no configuration, and the requesting client had no routing config naming it either - running unconfigured`);
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(`Asking the requesting client for the routing config of store ${JSON.stringify(this.storeName)} (folder ${this.folder}) failed - running unconfigured: ${(e as Error).stack ?? e}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
133
151
|
for (let i = 0; i < this.sources.length; i++) {
|
|
134
152
|
await this.registerSlot(i);
|
|
135
153
|
}
|
|
@@ -26,6 +26,7 @@ export declare class ChainStateManager {
|
|
|
26
26
|
/** See ArchivesChainOptions.directConnect. */
|
|
27
27
|
directConnect?: boolean;
|
|
28
28
|
});
|
|
29
|
+
private untrackConfig;
|
|
29
30
|
/** The newest adopted state, synchronously - undefined until the first init finishes. */
|
|
30
31
|
latest(): ChainState | undefined;
|
|
31
32
|
getState(): Promise<ChainState>;
|
|
@@ -3,7 +3,7 @@ import { RemoteConfig, SourceConfig } from "../IArchives";
|
|
|
3
3
|
import { ROUTING_FILE, getConfigVersion, serializeRemoteConfig, normalizeRemoteConfig, normalizeSource } from "./remoteConfig";
|
|
4
4
|
import { SourceWrapper, RETRY_START_DELAY, RETRY_MAX_DELAY, RETRY_GROWTH } from "./sourceWrapper";
|
|
5
5
|
import { resolveIntermediateSources } from "./intermediateSources";
|
|
6
|
-
import { onServerRoutingChanged } from "./storageClientController";
|
|
6
|
+
import { onServerRoutingChanged, trackChainConfig } from "./storageClientController";
|
|
7
7
|
|
|
8
8
|
// The LIFECYCLE of an ArchivesChain's state, and nothing else: initializing it (probing every configured source, deciding which routing config to run, writing ours out when it is the newest), the retry when initialization fails, the poll that adopts newer configs, the availability recheck, and the loop that keeps re-writing our config. None of this is request dispatch - the chain (createArchives.ts) asks the ChainStateManager for the current state and dispatches over it.
|
|
9
9
|
|
|
@@ -50,8 +50,12 @@ export class ChainStateManager {
|
|
|
50
50
|
this.unsubscribeRoutingPush = onServerRoutingChanged(() => {
|
|
51
51
|
void this.refreshActiveConfig().catch((e: Error) => console.error(`Config refresh failed for ${this.config.debugName()}: ${e.stack ?? e}`));
|
|
52
52
|
});
|
|
53
|
+
// So a server can ask what config we intended for a store name (see StorageClientController.getRoutingConfigForName)
|
|
54
|
+
this.untrackConfig = trackChainConfig({ configured: config.configured, active: () => this.activeConfig });
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
private untrackConfig: () => void;
|
|
58
|
+
|
|
55
59
|
/** The newest adopted state, synchronously - undefined until the first init finishes. */
|
|
56
60
|
public latest(): ChainState | undefined {
|
|
57
61
|
return this.latestState;
|
|
@@ -275,6 +279,7 @@ export class ChainStateManager {
|
|
|
275
279
|
|
|
276
280
|
public dispose(): void {
|
|
277
281
|
this.disposed = true;
|
|
282
|
+
this.untrackConfig();
|
|
278
283
|
this.unsubscribeRoutingPush?.();
|
|
279
284
|
if (this.pollTimer) {
|
|
280
285
|
clearInterval(this.pollTimer);
|
|
@@ -28,7 +28,7 @@ const PRIMARY_RETRY_DELAY = 2 * 1000;
|
|
|
28
28
|
const COVERING_RETRY_TIMEOUT = 30 * 1000;
|
|
29
29
|
const COVERING_RETRY_DELAY = 5 * 1000;
|
|
30
30
|
// Smart timeouts: an attempt gets this long to produce anything before we probe getInfo for the file's size (and the probe itself gets the same window)
|
|
31
|
-
const SMART_TIMEOUT_PROBE =
|
|
31
|
+
const SMART_TIMEOUT_PROBE = 60 * 1000;
|
|
32
32
|
// Very generous assumed transfer rates - the resulting deadline exists to catch stuck sources, not slow ones
|
|
33
33
|
const SMART_TIMEOUT_DOWNLOAD_BYTES_PER_SECOND = 1024 * 1024;
|
|
34
34
|
const SMART_TIMEOUT_UPLOAD_BYTES_PER_SECOND = 512 * 1024;
|