sliftutils 1.7.96 → 1.7.98
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/dist/treeSummary.ts.cache +2 -2
- package/index.d.ts +18 -1
- package/misc/dist/environment.ts.cache +2 -2
- package/misc/dist/getSecret.ts.cache +2 -2
- package/misc/dist/strings.ts.cache +2 -2
- package/misc/dist/zip.ts.cache +2 -2
- package/misc/https/dist/certs.ts.cache +2 -2
- package/misc/https/dist/persistentLocalStorage.ts.cache +2 -2
- package/package.json +1 -1
- package/render-utils/dist/observer.tsx.cache +2 -2
- package/storage/dist/ArchivesDisk.ts.cache +2 -2
- package/storage/dist/FileFolderAPI.tsx.cache +2 -2
- package/storage/dist/IndexedDBFileFolderAPI.ts.cache +2 -2
- package/storage/dist/JSONStorage.ts.cache +2 -2
- package/storage/dist/LogMap.ts.cache +2 -2
- package/storage/dist/PendingManager.tsx.cache +2 -2
- package/storage/dist/TransactionStorage.ts.cache +2 -2
- package/storage/dist/archiveHelpers.ts.cache +2 -2
- package/storage/dist/backblaze.ts.cache +2 -2
- package/storage/dist/fileSystemPointer.ts.cache +2 -2
- package/storage/dist/remoteFileStorage.ts.cache +2 -2
- 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 +4 -3
- package/storage/remoteStorage/dist/ArchivesDelayed.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +2 -2
- package/storage/remoteStorage/dist/accessStats.ts.cache +2 -2
- package/storage/remoteStorage/dist/blobStore.ts.cache +21 -2
- package/storage/remoteStorage/dist/bucketDisk.ts.cache +2 -2
- package/storage/remoteStorage/dist/certTrustModal.tsx.cache +2 -2
- package/storage/remoteStorage/dist/chainStartup.ts.cache +469 -0
- package/storage/remoteStorage/dist/cliArgs.ts.cache +2 -2
- package/storage/remoteStorage/dist/createArchives.ts.cache +52 -361
- package/storage/remoteStorage/dist/deployTakeover.ts.cache +2 -2
- package/storage/remoteStorage/dist/intermediateManagement.ts.cache +2 -2
- package/storage/remoteStorage/dist/intermediateSources.ts.cache +2 -2
- package/storage/remoteStorage/dist/remoteConfig.ts.cache +2 -2
- package/storage/remoteStorage/dist/serverConfig.ts.cache +2 -2
- package/storage/remoteStorage/dist/sourceWrapper.ts.cache +2 -2
- package/storage/remoteStorage/dist/sourcesList.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageClientController.ts.cache +45 -4
- package/storage/remoteStorage/dist/storageServerState.ts.cache +16 -5
- package/storage/remoteStorage/dist/storeConfig.ts.cache +7 -2
- package/storage/remoteStorage/dist/storePlan.ts.cache +2 -2
- package/storage/remoteStorage/dist/storeSources.ts.cache +2 -2
- package/storage/remoteStorage/dist/storeSync.ts.cache +2 -2
- package/storage/remoteStorage/dist/validation.ts.cache +2 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
3
|
//exports.assertValidArgs = exports.assertValidPath = exports.assertValidSourceName = exports.assertValidName = void 0;
|
|
4
4
|
function assertValidName(value, kind) {
|
|
@@ -47,4 +47,4 @@ function assertValidArgs(target, key, descriptor) {
|
|
|
47
47
|
}
|
|
48
48
|
exports.assertValidArgs = assertValidArgs;
|
|
49
49
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInZhbGlkYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsU0FBZ0IsZUFBZSxDQUFDLEtBQWEsRUFBRSxJQUFZO0lBQ3ZELElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDL0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBQzdILENBQUM7QUFDTCxDQUFDO0FBSkQsMENBSUM7QUFFRCx1VEFBdVQ7QUFDdlQsU0FBZ0IscUJBQXFCLENBQUMsS0FBYTtJQUMvQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsbUVBQW1FLENBQUMsQ0FBQztJQUNySSxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDdEIsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsb0VBQW9FLENBQUMsQ0FBQztJQUN0SSxDQUFDO0FBQ0wsQ0FBQztBQVBELHNEQU9DO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLElBQVk7SUFDeEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDMUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxDQUFDLE1BQU0sNkJBQTZCLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNwRyxDQUFDO0lBQ0QsSUFBSSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUM3SCxNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLDhGQUE4RixDQUFDLENBQUM7SUFDdEssQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sSUFBSSxLQUFLLENBQUMsZ0JBQWdCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMseUNBQXlDLENBQUMsQ0FBQztJQUNqSCxDQUFDO0FBQ0wsQ0FBQztBQVZELDBDQVVDO0FBRUQsNFdBQTRXO0FBQzVXLFNBQWdCLGVBQWUsQ0FBQyxNQUFlLEVBQUUsR0FBVyxFQUFFLFVBQThCO0lBQ3hGLElBQUksUUFBUSxHQUFHLFVBQVUsQ0FBQyxLQUF3QyxDQUFDO0lBQ25FLFVBQVUsQ0FBQyxLQUFLLEdBQUcsVUFBVSxHQUFHLElBQWU7UUFDM0MsSUFBSSxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBeUUsQ0FBQztRQUM3RixJQUFJLE1BQU0sSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN2QyxJQUFJLFNBQVMsSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsT0FBaUIsRUFBRSxTQUFTLENBQUMsQ0FBQztZQUM5RSxJQUFJLFlBQVksSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsVUFBb0IsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUN4RixJQUFJLE1BQU0sSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsSUFBYyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsQ0FBQyxDQUFDO0FBQ04sQ0FBQztBQVhELDBDQVdDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkTmFtZSh2YWx1ZTogc3RyaW5nLCBraW5kOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAoIS9eW1xcdy1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7a2luZH0gJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9LCBleHBlY3RlZCAxLTY0IGNoYXJhY3RlcnMgb2YgbGV0dGVycy9udW1iZXJzL3VuZGVyc2NvcmUvZGFzaGApO1xuICAgIH1cbn1cblxuLyoqIEEgc3RvcmUncyBuYW1lIChzZWUgQ29tbW9uQ29uZmlnLm5hbWUpLCB3aGljaCBhbHNvIGFsbG93cyBkb3RzIC0gYSBuYW1lIGlzIG9mdGVuIGEgaG9zdCBvciBhIHZlcnNpb24sIGFuZCBib3RoIHJlYWQgd3Jvbmcgd2l0aG91dCB0aGVtLiBJdCBpcyBvbmUgcGF0aCBzZWdtZW50IG9mIHRoZSBzdG9yZSdzIGZvbGRlciwgc28gdGhlIHR3byBuYW1lcyB0aGF0IHdvdWxkIG1lYW4gYSBkaWZmZXJlbnQgZm9sZGVyIGVudGlyZWx5IGFyZSByZWplY3RlZDogZXZlcnl0aGluZyBlbHNlIGNvbnRhaW5pbmcgZG90cyBpcyBqdXN0IGEgbmFtZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnRWYWxpZFNvdXJjZU5hbWUodmFsdWU6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghL15bXFx3Li1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHNvdXJjZSBuYW1lICR7SlNPTi5zdHJpbmdpZnkodmFsdWUpfSwgZXhwZWN0ZWQgMS02NCBjaGFyYWN0ZXJzIG9mIGxldHRlcnMvbnVtYmVycy91bmRlcnNjb3JlL2Rhc2gvZG90YCk7XG4gICAgfVxuICAgIGlmICgvXlxcLiskLy50ZXN0KHZhbHVlKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc291cmNlIG5hbWUgJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9OiBhIG5hbWUgb2Ygb25seSBkb3RzIHJlZmVycyB0byBhIGRpcmVjdG9yeSByYXRoZXIgdGhhbiBuYW1pbmcgb25lYCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0VmFsaWRQYXRoKHBhdGg6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmIChCdWZmZXIuZnJvbShwYXRoLCBcInV0ZjhcIikubGVuZ3RoID4gMTAwMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFBhdGggdG9vIGxvbmc6ICR7cGF0aC5sZW5ndGh9IGNoYXJhY3RlcnMgPiAxMDAwLiBQYXRoOiAke3BhdGguc2xpY2UoMCwgMjAwKX1gKTtcbiAgICB9XG4gICAgaWYgKCFwYXRoIHx8IHBhdGguc3RhcnRzV2l0aChcIi9cIikgfHwgcGF0aC5lbmRzV2l0aChcIi9cIikgfHwgcGF0aC5pbmNsdWRlcyhcIi8vXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXFxcXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXHgwMFwiKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcGF0aCAke0pTT04uc3RyaW5naWZ5KHBhdGguc2xpY2UoMCwgMjAwKSl9LCBwYXRocyBjYW5ub3QgYmUgZW1wdHksIHN0YXJ0IG9yIGVuZCB3aXRoIC8sIG9yIGNvbnRhaW4gLy8sIGJhY2tzbGFzaGVzLCBvciBudWxsIGNoYXJhY3RlcnNgKTtcbiAgICB9XG4gICAgaWYgKHBhdGguc3BsaXQoXCIvXCIpLnNvbWUocGFydCA9PiBwYXJ0ID09PSBcIi5cIiB8fCBwYXJ0ID09PSBcIi4uXCIpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwYXRoICR7SlNPTi5zdHJpbmdpZnkocGF0aC5zbGljZSgwLCAyMDApKX0sIHBhdGhzIGNhbm5vdCBjb250YWluIC4gb3IgLi4gc2VnbWVudHNgKTtcbiAgICB9XG59XG5cbi8qKiBNZXRob2QgZGVjb3JhdG9yOiB2YWxpZGF0ZXMgdGhlIHdlbGwta25vd24gZmllbGRzIG9mIHRoZSBtZXRob2QncyBzaW5nbGUgY29uZmlnLW9iamVjdCBhcmd1bWVudCAtIGFjY291bnQvYnVja2V0TmFtZSBhcyBuYW1lcywgcGF0aCBhcyBhIHBhdGggLSBiZWZvcmUgdGhlIG1ldGhvZCBydW5zLiBGaWVsZHMgdGhlIGNvbmZpZyBkb2Vzbid0IGhhdmUgYXJlIHNraXBwZWQsIHNvIGl0IGFwcGxpZXMgdG8gZXZlcnkgQVBJIG1ldGhvZCB1bmlmb3JtbHkuIHByZWZpeCBpcyBkZWxpYmVyYXRlbHkgTk9UIHZhbGlkYXRlZDogcHJlZml4ZXMgbWF5IGJlIGVtcHR5IG9yIGVuZCB3aXRoIFwiL1wiLCBib3RoIGludmFsaWQgZm9yIHBhdGhzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkQXJncyh0YXJnZXQ6IHVua25vd24sIGtleTogc3RyaW5nLCBkZXNjcmlwdG9yOiBQcm9wZXJ0eURlc2NyaXB0b3IpOiB2b2lkIHtcbiAgICBsZXQgb3JpZ2luYWwgPSBkZXNjcmlwdG9yLnZhbHVlIGFzICguLi5hcmdzOiB1bmtub3duW10pID0+IHVua25vd247XG4gICAgZGVzY3JpcHRvci52YWx1ZSA9IGZ1bmN0aW9uICguLi5hcmdzOiB1bmtub3duW10pOiB1bmtub3duIHtcbiAgICAgICAgbGV0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgeyBhY2NvdW50Pzogc3RyaW5nOyBidWNrZXROYW1lPzogc3RyaW5nOyBwYXRoPzogc3RyaW5nIH0gfCB1bmRlZmluZWQ7XG4gICAgICAgIGlmIChjb25maWcgJiYgdHlwZW9mIGNvbmZpZyA9PT0gXCJvYmplY3RcIikge1xuICAgICAgICAgICAgaWYgKFwiYWNjb3VudFwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5hY2NvdW50IGFzIHN0cmluZywgXCJhY2NvdW50XCIpO1xuICAgICAgICAgICAgaWYgKFwiYnVja2V0TmFtZVwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5idWNrZXROYW1lIGFzIHN0cmluZywgXCJidWNrZXQgbmFtZVwiKTtcbiAgICAgICAgICAgIGlmIChcInBhdGhcIiBpbiBjb25maWcpIGFzc2VydFZhbGlkUGF0aChjb25maWcucGF0aCBhcyBzdHJpbmcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvcmlnaW5hbC5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xufVxuIl19
|
|
50
|
-
/* _JS_SOURCE_HASH = "
|
|
50
|
+
/* _JS_SOURCE_HASH = "52f73d0ef7e2f65848acc68373c50e34bd39c017c3ff908220fc66a6f3d0296f"; */
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { RemoteConfig } from "../IArchives";
|
|
1
2
|
/** Subscribe to server-pushed routing change notifications. Returns the unsubscribe function. */
|
|
2
3
|
export declare function onServerRoutingChanged(listener: () => void): () => void;
|
|
4
|
+
/** 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). */
|
|
5
|
+
type TrackedChainConfig = {
|
|
6
|
+
configured: RemoteConfig;
|
|
7
|
+
active: () => RemoteConfig;
|
|
8
|
+
};
|
|
9
|
+
/** 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. */
|
|
10
|
+
export declare function trackChainConfig(entry: TrackedChainConfig): () => void;
|
|
3
11
|
export declare const StorageClientController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
|
|
4
12
|
routingConfigChanged: () => Promise<void>;
|
|
13
|
+
getRoutingConfigForName: (config: {
|
|
14
|
+
account: string;
|
|
15
|
+
bucketName: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}) => Promise<RemoteConfig | undefined>;
|
|
5
18
|
}>;
|
|
19
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { RemoteConfig } from "../IArchives";
|
|
3
|
+
import { parseHostedUrl } from "./remoteConfig";
|
|
2
4
|
|
|
3
|
-
// The client half of
|
|
5
|
+
// The client half of the storage protocol: the storage server tracks its connected clients (see trackCaller in storageController) and calls back into them - routingConfigChanged the moment a routing config changes (clients react immediately instead of waiting for a poll), and getRoutingConfigForName when a store one of our requests created needs the config we intended for it.
|
|
4
6
|
|
|
5
7
|
const listeners = new Set<() => void>();
|
|
6
8
|
|
|
@@ -12,6 +14,32 @@ export function onServerRoutingChanged(listener: () => void): () => void {
|
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
/** 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). */
|
|
18
|
+
type TrackedChainConfig = { configured: RemoteConfig; active: () => RemoteConfig };
|
|
19
|
+
const chainConfigs = new Set<TrackedChainConfig>();
|
|
20
|
+
|
|
21
|
+
/** 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. */
|
|
22
|
+
export function trackChainConfig(entry: TrackedChainConfig): () => void {
|
|
23
|
+
chainConfigs.add(entry);
|
|
24
|
+
return () => {
|
|
25
|
+
chainConfigs.delete(entry);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function configNamesStore(config: RemoteConfig, account: string, bucketName: string, name: string): boolean {
|
|
30
|
+
for (let source of config.sources) {
|
|
31
|
+
if (typeof source === "string" || source.type !== "remote") continue;
|
|
32
|
+
if (source.name !== name) continue;
|
|
33
|
+
try {
|
|
34
|
+
let parsed = parseHostedUrl(source.url);
|
|
35
|
+
if (parsed.account === account && parsed.bucketName === bucketName) return true;
|
|
36
|
+
} catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
class StorageClientControllerBase {
|
|
16
44
|
async routingConfigChanged(): Promise<void> {
|
|
17
45
|
for (let listener of [...listeners]) {
|
|
@@ -22,6 +50,18 @@ class StorageClientControllerBase {
|
|
|
22
50
|
}
|
|
23
51
|
}
|
|
24
52
|
}
|
|
53
|
+
|
|
54
|
+
/** A storage server asks: what routing config did this client INTEND for this store name? This is how a store our requests created - before any config ever reached its folder - configures itself: the same information as passing the config on every call, fetched lazily instead (the config is kilobytes and the calls are many). Preference: the synchronized config (adopted from the stored routing files); when that no longer names the store, the initial in-code config - older, but it MATCHES the name, which is what the asking store needs. */
|
|
55
|
+
async getRoutingConfigForName(config: { account: string; bucketName: string; name: string }): Promise<RemoteConfig | undefined> {
|
|
56
|
+
for (let entry of [...chainConfigs]) {
|
|
57
|
+
let active = entry.active();
|
|
58
|
+
if (configNamesStore(active, config.account, config.bucketName, config.name)) return active;
|
|
59
|
+
}
|
|
60
|
+
for (let entry of [...chainConfigs]) {
|
|
61
|
+
if (configNamesStore(entry.configured, config.account, config.bucketName, config.name)) return entry.configured;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
25
65
|
}
|
|
26
66
|
|
|
27
67
|
export const StorageClientController = SocketFunction.register(
|
|
@@ -29,5 +69,6 @@ export const StorageClientController = SocketFunction.register(
|
|
|
29
69
|
new StorageClientControllerBase(),
|
|
30
70
|
() => ({
|
|
31
71
|
routingConfigChanged: {},
|
|
72
|
+
getRoutingConfigForName: {},
|
|
32
73
|
}),
|
|
33
74
|
);
|
|
@@ -4,7 +4,7 @@ import { BlobStore } from "./blobStore";
|
|
|
4
4
|
import { RemoteConfig, SourceConfig, IArchives, ArchivesConfig, ArchivesSyncStatus } from "../IArchives";
|
|
5
5
|
import { BucketDiskInfo } from "./bucketDisk";
|
|
6
6
|
import { BucketWriteStats } from "./accessStats";
|
|
7
|
-
export declare function getStore(account: string, bucketName: string, name: string): BlobStore;
|
|
7
|
+
export declare function getStore(account: string, bucketName: string, name: string, callerNodeId?: string): BlobStore;
|
|
8
8
|
/** 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. */
|
|
9
9
|
export declare function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined): BlobStore;
|
|
10
10
|
/** 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. */
|
|
@@ -9,6 +9,8 @@ import { assertWritesAllowed } from "./serverConfig";
|
|
|
9
9
|
import { assertValidSourceName } from "./validation";
|
|
10
10
|
import { getBucketFolder, listAccountStoreFolders, listBucketStoreFolders, readRoutingFromDisk, getDiskInfo, BucketDiskInfo } from "./bucketDisk";
|
|
11
11
|
import { scheduleBoundaryWork, reinjectIntermediates } from "./intermediateManagement";
|
|
12
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
13
|
+
import { StorageClientController } from "./storageClientController";
|
|
12
14
|
import { isSelfSource } from "./storePlan";
|
|
13
15
|
import { countBucketWrite, getBucketWriteStats, BucketWriteStats } from "./accessStats";
|
|
14
16
|
|
|
@@ -28,7 +30,7 @@ const stores = new Map<string, BlobStore>();
|
|
|
28
30
|
// The opened stores of each `${account}/${bucketName}`, by name - which buckets are ACTIVE (something touched them), for deploy handoffs and the debug pages. Purely a registry of what getStore already made.
|
|
29
31
|
const activeStores = new Map<string, Map<string, BlobStore>>();
|
|
30
32
|
|
|
31
|
-
export function getStore(account: string, bucketName: string, name: string): BlobStore {
|
|
33
|
+
export function getStore(account: string, bucketName: string, name: string, callerNodeId?: string): BlobStore {
|
|
32
34
|
// The one place a name becomes a folder, so the one place it is validated (it can come straight off the wire)
|
|
33
35
|
assertValidSourceName(name);
|
|
34
36
|
let folder = getBucketFolder(name, account, bucketName);
|
|
@@ -44,6 +46,8 @@ export function getStore(account: string, bucketName: string, name: string): Blo
|
|
|
44
46
|
resolveSourceUrl: resolveSourceArchives,
|
|
45
47
|
// The store is the one that knows when a config landed, and a config with upcoming windows is what boundary scans are armed from
|
|
46
48
|
onRoutingApplied: routing => scheduleBoundaryWork(account, bucketName, routing),
|
|
49
|
+
// The store was created by this caller's request, so the caller knows exactly what config it intended for this name - the store asks it when its own folder holds no configuration (see BlobStore.init)
|
|
50
|
+
requestRoutingConfig: callerNodeId && (async () => await StorageClientController.nodes[callerNodeId].getRoutingConfigForName({ account, bucketName, name })) || undefined,
|
|
47
51
|
});
|
|
48
52
|
stores.set(folder, store);
|
|
49
53
|
let key = `${account}/${bucketName}`;
|
|
@@ -63,7 +67,12 @@ export function findBucketStore(account: string, bucketName: string, sourceConfi
|
|
|
63
67
|
if (!sourceConfig) {
|
|
64
68
|
throw new Error(`No remote source configuration was provided for bucket ${account}/${bucketName}: every request must say which configured source it selected`);
|
|
65
69
|
}
|
|
66
|
-
|
|
70
|
+
// The caller's id, so a store this request CREATES can ask that caller for the config it intended (a store only ever exists because a config names it, and the requester holds that config)
|
|
71
|
+
let callerNodeId: string | undefined;
|
|
72
|
+
try {
|
|
73
|
+
callerNodeId = SocketFunction.getCaller()?.nodeId;
|
|
74
|
+
} catch { }
|
|
75
|
+
return getStore(account, bucketName, sourceConfig.name, callerNodeId);
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
/** 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. */
|
|
@@ -17,6 +17,11 @@ export class StoreConfig {
|
|
|
17
17
|
/** The routing config changed. Same store either way: its name did not change, so neither did its folder, its index, or the data in it. */
|
|
18
18
|
/** No entries is a real state, not an error: a store exists as soon as its folder does, and it may never have heard of a configuration (see StorePolicy). */
|
|
19
19
|
public update(entries: HostedConfig[]): void {
|
|
20
|
+
// Never go from configured back to UNCONFIGURED: keep the last configuration that worked. There is no point in emptying it - if this store was genuinely removed from the config, nobody will request it again, and the stale entries are not worth cleaning up (they are tiny, named stores are rarely removed, and a service restart clears them eventually). Keeping them is REQUIRED for the out-of-sync cases: a bad config that stops naming us would otherwise leave the store rejecting every write, including the ones that would let the configuration converge - it just won't update until a restart, and a restart can't complete against an out-of-date configuration, which is an infinite loop.
|
|
21
|
+
if (!entries.length && this.entries.length) {
|
|
22
|
+
console.warn(`Ignoring a routing config update that would leave store ${JSON.stringify(this.name)} with NO entries: keeping its current ${this.entries.length} entr${this.entries.length === 1 && "y" || "ies"} ${JSON.stringify(this.entries.map(x => ({ validWindow: x.validWindow, route: x.route })))}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
20
25
|
let ordered = [...entries];
|
|
21
26
|
sort(ordered, x => x.validWindow[0]);
|
|
22
27
|
this.entries = ordered;
|