sliftutils 1.7.95 → 1.7.97
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 +129 -27
- package/package.json +1 -1
- package/storage/remoteStorage/chainStartup.d.ts +123 -0
- package/storage/remoteStorage/chainStartup.ts +482 -0
- package/storage/remoteStorage/createArchives.d.ts +2 -27
- package/storage/remoteStorage/createArchives.ts +57 -358
- package/storage/remoteStorage/dist/chainStartup.ts.cache +466 -0
- package/storage/remoteStorage/dist/createArchives.ts.cache +52 -361
- package/storage/remoteStorage/dist/storageServerState.ts.cache +3 -3
- package/storage/remoteStorage/storageServerState.ts +1 -1
package/index.d.ts
CHANGED
|
@@ -3572,6 +3572,133 @@ declare module "sliftutils/storage/remoteStorage/certTrustModal" {
|
|
|
3572
3572
|
|
|
3573
3573
|
}
|
|
3574
3574
|
|
|
3575
|
+
declare module "sliftutils/storage/remoteStorage/chainStartup" {
|
|
3576
|
+
import { RemoteConfig, SourceConfig } from "../IArchives";
|
|
3577
|
+
import { SourceWrapper } from "./sourceWrapper";
|
|
3578
|
+
export declare const CONFIG_WRITE_RETRY_INTERVAL: number;
|
|
3579
|
+
export declare const CONFIG_WRITE_REFRESH_INTERVAL: number;
|
|
3580
|
+
export type ChainState = {
|
|
3581
|
+
config: RemoteConfig;
|
|
3582
|
+
sources: SourceWrapper[];
|
|
3583
|
+
};
|
|
3584
|
+
/** The chain's state and everything about HAVING one: init (with its retry), the config poll, availability rechecks, and the routing rewrite loop. The chain constructs one, asks it getState() on every call, and disposes it. */
|
|
3585
|
+
export declare class ChainStateManager {
|
|
3586
|
+
private config;
|
|
3587
|
+
readonly configured: RemoteConfig;
|
|
3588
|
+
/** The newest adopted config - what getDebugName and dispatch decisions read. Starts as the configured one and moves with every adoption. */
|
|
3589
|
+
activeConfig: RemoteConfig;
|
|
3590
|
+
private statePromise;
|
|
3591
|
+
private latestState;
|
|
3592
|
+
private initRetryDelay;
|
|
3593
|
+
private initRetryTimer;
|
|
3594
|
+
private pollTimer;
|
|
3595
|
+
private disposed;
|
|
3596
|
+
private unsubscribeRoutingPush;
|
|
3597
|
+
private routingRewriter;
|
|
3598
|
+
constructor(config: {
|
|
3599
|
+
configured: RemoteConfig;
|
|
3600
|
+
debugName: () => string;
|
|
3601
|
+
/** See ArchivesChainOptions.directConnect. */
|
|
3602
|
+
directConnect?: boolean;
|
|
3603
|
+
});
|
|
3604
|
+
/** The newest adopted state, synchronously - undefined until the first init finishes. */
|
|
3605
|
+
latest(): ChainState | undefined;
|
|
3606
|
+
getState(): Promise<ChainState>;
|
|
3607
|
+
private init;
|
|
3608
|
+
/** Clientside, a config with public sources is served entirely over plain URL downloads - no API connection, no access grant, and no writing. directConnect opts out of that. */
|
|
3609
|
+
private isReadOnly;
|
|
3610
|
+
private createChainSource;
|
|
3611
|
+
private buildSources;
|
|
3612
|
+
private startConfigPoll;
|
|
3613
|
+
private configRefreshInFlight;
|
|
3614
|
+
refreshActiveConfig(): Promise<void>;
|
|
3615
|
+
private fetchLatestConfig;
|
|
3616
|
+
private checkForNewConfig;
|
|
3617
|
+
private adoptNewConfig;
|
|
3618
|
+
private lastAvailabilityRecheck;
|
|
3619
|
+
private availabilityRecheckInFlight;
|
|
3620
|
+
/** Every source failed: re-contact all of them (routing re-read + connection re-attempt) and adopt whatever config comes back. Throttled, and deduplicated across concurrent callers. */
|
|
3621
|
+
recheckAvailability(): Promise<void>;
|
|
3622
|
+
private recheckAvailabilityNow;
|
|
3623
|
+
dispose(): void;
|
|
3624
|
+
}
|
|
3625
|
+
export type SourceProbe = {
|
|
3626
|
+
probe: SourceWrapper;
|
|
3627
|
+
sourceConfig: SourceConfig;
|
|
3628
|
+
responded: boolean;
|
|
3629
|
+
latency: number;
|
|
3630
|
+
existing: RemoteConfig | undefined;
|
|
3631
|
+
error: string;
|
|
3632
|
+
};
|
|
3633
|
+
/** One throwaway SourceWrapper per configured source, each asked for its stored routing config - which also measures first-contact latency, seeded into the real sources afterwards. The probes MUST be disposed (disposeProbes) once the caller is done with them. */
|
|
3634
|
+
export declare function probeSources(configs: SourceConfig[], readOnly: boolean): Promise<SourceProbe[]>;
|
|
3635
|
+
export declare function disposeProbes(probes: SourceProbe[]): void;
|
|
3636
|
+
/**
|
|
3637
|
+
* Which routing config the chain should RUN: the newest of ours and every stored one. needsWrite
|
|
3638
|
+
* when ours is strictly the newest, meaning the stores have to be told about it. A stored config
|
|
3639
|
+
* with our exact version but DIFFERENT content wins without a write, loudly: config updates must
|
|
3640
|
+
* bump the version, or they are ignored - silently taking the changed one would make "what is the
|
|
3641
|
+
* bucket running" depend on which process started last.
|
|
3642
|
+
*
|
|
3643
|
+
* Throws when no source answered at all: with nothing stored and nobody to write to, there is no
|
|
3644
|
+
* config to run.
|
|
3645
|
+
*/
|
|
3646
|
+
export declare function chooseStartupConfig(config: {
|
|
3647
|
+
configured: RemoteConfig;
|
|
3648
|
+
probes: SourceProbe[];
|
|
3649
|
+
debugName: string;
|
|
3650
|
+
}): {
|
|
3651
|
+
active: RemoteConfig;
|
|
3652
|
+
needsWrite: boolean;
|
|
3653
|
+
existing: RemoteConfig | undefined;
|
|
3654
|
+
};
|
|
3655
|
+
/**
|
|
3656
|
+
* Writes the given routing config to every configured store, one write per url+name: the write
|
|
3657
|
+
* lands in the store the entry NAMES, so two entries sharing a URL but naming different stores are
|
|
3658
|
+
* two separate deliveries - deduping by URL alone leaves the second store unconfigured forever. All
|
|
3659
|
+
* in parallel, every failure tolerated (no-write-access included - the attempt classifies it,
|
|
3660
|
+
* nothing pre-checks it): a down node must not stop the others from getting the config - they would
|
|
3661
|
+
* then reject writes as unconfigured precisely BECAUSE it never arrived - and it must never stop
|
|
3662
|
+
* the chain from starting. A store that missed it pulls it off its peers, and the rewrite loop
|
|
3663
|
+
* tries again (see RoutingRewriteLoop).
|
|
3664
|
+
*/
|
|
3665
|
+
export declare function writeRoutingToAllStores(config: {
|
|
3666
|
+
configured: RemoteConfig;
|
|
3667
|
+
sources: SourceWrapper[];
|
|
3668
|
+
debugName: string;
|
|
3669
|
+
}): Promise<{
|
|
3670
|
+
failures: string[];
|
|
3671
|
+
total: number;
|
|
3672
|
+
}>;
|
|
3673
|
+
/**
|
|
3674
|
+
* The periodic re-write of the chain's in-code config: failures retried on the short interval - a
|
|
3675
|
+
* store without the config rejects every write aimed at it, so this not landing is a big deal,
|
|
3676
|
+
* logged on every attempt - and even success repeated hourly, in case a store lost it. The failure
|
|
3677
|
+
* this exists for: a server whose trust was only granted AFTER startup, so the startup write was
|
|
3678
|
+
* rejected and nothing else would ever retry it.
|
|
3679
|
+
*/
|
|
3680
|
+
export declare class RoutingRewriteLoop {
|
|
3681
|
+
private config;
|
|
3682
|
+
constructor(config: {
|
|
3683
|
+
configured: RemoteConfig;
|
|
3684
|
+
debugName: () => string;
|
|
3685
|
+
/** The chain's newest adopted state: what decides whether ours is still the config to write, and the sources it is written through. Undefined until the first init finishes. */
|
|
3686
|
+
latest: () => {
|
|
3687
|
+
config: RemoteConfig;
|
|
3688
|
+
sources: SourceWrapper[];
|
|
3689
|
+
} | undefined;
|
|
3690
|
+
});
|
|
3691
|
+
private timer;
|
|
3692
|
+
private disposed;
|
|
3693
|
+
/** (Re)arms the loop - called at the end of every init, with whether that init's write failed (which picks the short retry interval). */
|
|
3694
|
+
start(failedAtStartup: boolean): void;
|
|
3695
|
+
dispose(): void;
|
|
3696
|
+
private schedule;
|
|
3697
|
+
private rewrite;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3575
3702
|
declare module "sliftutils/storage/remoteStorage/cliArgs" {
|
|
3576
3703
|
export declare function getArg(name: string): string | undefined;
|
|
3577
3704
|
/** A valueless boolean flag: true when --name is present (with nothing, or a following flag). */
|
|
@@ -3593,34 +3720,9 @@ declare module "sliftutils/storage/remoteStorage/createArchives" {
|
|
|
3593
3720
|
directConnect?: boolean;
|
|
3594
3721
|
};
|
|
3595
3722
|
export declare class ArchivesChain implements IArchives {
|
|
3596
|
-
private
|
|
3597
|
-
|
|
3598
|
-
private activeConfig;
|
|
3599
|
-
private statePromise;
|
|
3600
|
-
private latestState;
|
|
3601
|
-
private initRetryDelay;
|
|
3602
|
-
private initRetryTimer;
|
|
3603
|
-
private pollTimer;
|
|
3604
|
-
private disposed;
|
|
3605
|
-
private unsubscribeRoutingPush;
|
|
3606
|
-
constructor(config: RemoteConfig | RemoteConfigBase, options?: ArchivesChainOptions | undefined);
|
|
3723
|
+
private state;
|
|
3724
|
+
constructor(config: RemoteConfig | RemoteConfigBase, options?: ArchivesChainOptions);
|
|
3607
3725
|
getDebugName(): string;
|
|
3608
|
-
private getState;
|
|
3609
|
-
private init;
|
|
3610
|
-
/** Clientside, a config with public sources is served entirely over plain URL downloads - no API connection, no access grant, and no writing. directConnect opts out of that. */
|
|
3611
|
-
private isReadOnly;
|
|
3612
|
-
private createChainSource;
|
|
3613
|
-
private buildSources;
|
|
3614
|
-
private startConfigPoll;
|
|
3615
|
-
private configRefreshInFlight;
|
|
3616
|
-
private refreshActiveConfig;
|
|
3617
|
-
private fetchLatestConfig;
|
|
3618
|
-
private checkForNewConfig;
|
|
3619
|
-
private adoptNewConfig;
|
|
3620
|
-
private lastAvailabilityRecheck;
|
|
3621
|
-
private availabilityRecheckInFlight;
|
|
3622
|
-
private recheckAvailability;
|
|
3623
|
-
private recheckAvailabilityNow;
|
|
3624
3726
|
private run;
|
|
3625
3727
|
private runPrimary;
|
|
3626
3728
|
/** Races call against a size-based deadline. Uploads know their size upfront; gets are given SMART_TIMEOUT_PROBE to produce anything, and only then is the file's info fetched (from the same source, itself time-limited) to size the deadline - measured from the call's start, so a source that was slow before the probe doesn't get the full allowance again. Timed-out calls keep running in the background (they cannot be cancelled) but their eventual result is ignored. */
|
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { RemoteConfig, SourceConfig } from "../IArchives";
|
|
2
|
+
import { SourceWrapper } from "./sourceWrapper";
|
|
3
|
+
export declare const CONFIG_WRITE_RETRY_INTERVAL: number;
|
|
4
|
+
export declare const CONFIG_WRITE_REFRESH_INTERVAL: number;
|
|
5
|
+
export type ChainState = {
|
|
6
|
+
config: RemoteConfig;
|
|
7
|
+
sources: SourceWrapper[];
|
|
8
|
+
};
|
|
9
|
+
/** The chain's state and everything about HAVING one: init (with its retry), the config poll, availability rechecks, and the routing rewrite loop. The chain constructs one, asks it getState() on every call, and disposes it. */
|
|
10
|
+
export declare class ChainStateManager {
|
|
11
|
+
private config;
|
|
12
|
+
readonly configured: RemoteConfig;
|
|
13
|
+
/** The newest adopted config - what getDebugName and dispatch decisions read. Starts as the configured one and moves with every adoption. */
|
|
14
|
+
activeConfig: RemoteConfig;
|
|
15
|
+
private statePromise;
|
|
16
|
+
private latestState;
|
|
17
|
+
private initRetryDelay;
|
|
18
|
+
private initRetryTimer;
|
|
19
|
+
private pollTimer;
|
|
20
|
+
private disposed;
|
|
21
|
+
private unsubscribeRoutingPush;
|
|
22
|
+
private routingRewriter;
|
|
23
|
+
constructor(config: {
|
|
24
|
+
configured: RemoteConfig;
|
|
25
|
+
debugName: () => string;
|
|
26
|
+
/** See ArchivesChainOptions.directConnect. */
|
|
27
|
+
directConnect?: boolean;
|
|
28
|
+
});
|
|
29
|
+
/** The newest adopted state, synchronously - undefined until the first init finishes. */
|
|
30
|
+
latest(): ChainState | undefined;
|
|
31
|
+
getState(): Promise<ChainState>;
|
|
32
|
+
private init;
|
|
33
|
+
/** Clientside, a config with public sources is served entirely over plain URL downloads - no API connection, no access grant, and no writing. directConnect opts out of that. */
|
|
34
|
+
private isReadOnly;
|
|
35
|
+
private createChainSource;
|
|
36
|
+
private buildSources;
|
|
37
|
+
private startConfigPoll;
|
|
38
|
+
private configRefreshInFlight;
|
|
39
|
+
refreshActiveConfig(): Promise<void>;
|
|
40
|
+
private fetchLatestConfig;
|
|
41
|
+
private checkForNewConfig;
|
|
42
|
+
private adoptNewConfig;
|
|
43
|
+
private lastAvailabilityRecheck;
|
|
44
|
+
private availabilityRecheckInFlight;
|
|
45
|
+
/** Every source failed: re-contact all of them (routing re-read + connection re-attempt) and adopt whatever config comes back. Throttled, and deduplicated across concurrent callers. */
|
|
46
|
+
recheckAvailability(): Promise<void>;
|
|
47
|
+
private recheckAvailabilityNow;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
}
|
|
50
|
+
export type SourceProbe = {
|
|
51
|
+
probe: SourceWrapper;
|
|
52
|
+
sourceConfig: SourceConfig;
|
|
53
|
+
responded: boolean;
|
|
54
|
+
latency: number;
|
|
55
|
+
existing: RemoteConfig | undefined;
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
/** One throwaway SourceWrapper per configured source, each asked for its stored routing config - which also measures first-contact latency, seeded into the real sources afterwards. The probes MUST be disposed (disposeProbes) once the caller is done with them. */
|
|
59
|
+
export declare function probeSources(configs: SourceConfig[], readOnly: boolean): Promise<SourceProbe[]>;
|
|
60
|
+
export declare function disposeProbes(probes: SourceProbe[]): void;
|
|
61
|
+
/**
|
|
62
|
+
* Which routing config the chain should RUN: the newest of ours and every stored one. needsWrite
|
|
63
|
+
* when ours is strictly the newest, meaning the stores have to be told about it. A stored config
|
|
64
|
+
* with our exact version but DIFFERENT content wins without a write, loudly: config updates must
|
|
65
|
+
* bump the version, or they are ignored - silently taking the changed one would make "what is the
|
|
66
|
+
* bucket running" depend on which process started last.
|
|
67
|
+
*
|
|
68
|
+
* Throws when no source answered at all: with nothing stored and nobody to write to, there is no
|
|
69
|
+
* config to run.
|
|
70
|
+
*/
|
|
71
|
+
export declare function chooseStartupConfig(config: {
|
|
72
|
+
configured: RemoteConfig;
|
|
73
|
+
probes: SourceProbe[];
|
|
74
|
+
debugName: string;
|
|
75
|
+
}): {
|
|
76
|
+
active: RemoteConfig;
|
|
77
|
+
needsWrite: boolean;
|
|
78
|
+
existing: RemoteConfig | undefined;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Writes the given routing config to every configured store, one write per url+name: the write
|
|
82
|
+
* lands in the store the entry NAMES, so two entries sharing a URL but naming different stores are
|
|
83
|
+
* two separate deliveries - deduping by URL alone leaves the second store unconfigured forever. All
|
|
84
|
+
* in parallel, every failure tolerated (no-write-access included - the attempt classifies it,
|
|
85
|
+
* nothing pre-checks it): a down node must not stop the others from getting the config - they would
|
|
86
|
+
* then reject writes as unconfigured precisely BECAUSE it never arrived - and it must never stop
|
|
87
|
+
* the chain from starting. A store that missed it pulls it off its peers, and the rewrite loop
|
|
88
|
+
* tries again (see RoutingRewriteLoop).
|
|
89
|
+
*/
|
|
90
|
+
export declare function writeRoutingToAllStores(config: {
|
|
91
|
+
configured: RemoteConfig;
|
|
92
|
+
sources: SourceWrapper[];
|
|
93
|
+
debugName: string;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
failures: string[];
|
|
96
|
+
total: number;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* The periodic re-write of the chain's in-code config: failures retried on the short interval - a
|
|
100
|
+
* store without the config rejects every write aimed at it, so this not landing is a big deal,
|
|
101
|
+
* logged on every attempt - and even success repeated hourly, in case a store lost it. The failure
|
|
102
|
+
* this exists for: a server whose trust was only granted AFTER startup, so the startup write was
|
|
103
|
+
* rejected and nothing else would ever retry it.
|
|
104
|
+
*/
|
|
105
|
+
export declare class RoutingRewriteLoop {
|
|
106
|
+
private config;
|
|
107
|
+
constructor(config: {
|
|
108
|
+
configured: RemoteConfig;
|
|
109
|
+
debugName: () => string;
|
|
110
|
+
/** The chain's newest adopted state: what decides whether ours is still the config to write, and the sources it is written through. Undefined until the first init finishes. */
|
|
111
|
+
latest: () => {
|
|
112
|
+
config: RemoteConfig;
|
|
113
|
+
sources: SourceWrapper[];
|
|
114
|
+
} | undefined;
|
|
115
|
+
});
|
|
116
|
+
private timer;
|
|
117
|
+
private disposed;
|
|
118
|
+
/** (Re)arms the loop - called at the end of every init, with whether that init's write failed (which picks the short retry interval). */
|
|
119
|
+
start(failedAtStartup: boolean): void;
|
|
120
|
+
dispose(): void;
|
|
121
|
+
private schedule;
|
|
122
|
+
private rewrite;
|
|
123
|
+
}
|