@cratis/chronicle 2.0.0 → 2.1.1
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/ChronicleClient.ts +69 -60
- package/connection/ChronicleConnection.ts +32 -9
- package/connection/ChronicleConnectionString.spec.ts +227 -0
- package/connection/ChronicleConnectionString.ts +290 -40
- package/connection/ChronicleServerAddressResolver.spec.ts +41 -0
- package/connection/ChronicleServerAddressResolver.ts +33 -0
- package/connection/ChronicleSrvResolutionError.ts +13 -0
- package/connection/ChronicleSrvResolver.spec.ts +103 -0
- package/connection/ChronicleSrvResolver.ts +57 -0
- package/connection/ConnectionLifecycle.spec.ts +109 -0
- package/connection/ILoadBalancerStrategy.ts +16 -0
- package/connection/KernelKeepAlive.spec.ts +175 -0
- package/connection/KernelKeepAlive.ts +161 -0
- package/connection/LeastConnectionsLoadBalancerStrategy.spec.ts +159 -0
- package/connection/LeastConnectionsLoadBalancerStrategy.ts +117 -0
- package/connection/LoadBalancerMode.ts +25 -0
- package/connection/LoadBalancerStrategyFactory.ts +28 -0
- package/connection/RandomLoadBalancerStrategy.spec.ts +50 -0
- package/connection/RandomLoadBalancerStrategy.ts +19 -0
- package/connection/RoundRobinLoadBalancerStrategy.spec.ts +61 -0
- package/connection/RoundRobinLoadBalancerStrategy.ts +29 -0
- package/connection/TokenProvider.spec.ts +159 -0
- package/connection/TokenProvider.ts +80 -68
- package/connection/fetchOAuthAccessToken.ts +86 -0
- package/connection/formatServerAddress.ts +16 -0
- package/dist/ChronicleClient.d.ts +8 -1
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +63 -56
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +4 -1
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +29 -9
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/ChronicleConnectionString.d.ts +77 -3
- package/dist/connection/ChronicleConnectionString.d.ts.map +1 -1
- package/dist/connection/ChronicleConnectionString.js +251 -37
- package/dist/connection/ChronicleConnectionString.js.map +1 -1
- package/dist/connection/ChronicleServerAddressResolver.d.ts +23 -0
- package/dist/connection/ChronicleServerAddressResolver.d.ts.map +1 -0
- package/dist/connection/ChronicleServerAddressResolver.js +32 -0
- package/dist/connection/ChronicleServerAddressResolver.js.map +1 -0
- package/dist/connection/ChronicleSrvResolutionError.d.ts +8 -0
- package/dist/connection/ChronicleSrvResolutionError.d.ts.map +1 -0
- package/dist/connection/ChronicleSrvResolutionError.js +13 -0
- package/dist/connection/ChronicleSrvResolutionError.js.map +1 -0
- package/dist/connection/ChronicleSrvResolver.d.ts +28 -0
- package/dist/connection/ChronicleSrvResolver.d.ts.map +1 -0
- package/dist/connection/ChronicleSrvResolver.js +51 -0
- package/dist/connection/ChronicleSrvResolver.js.map +1 -0
- package/dist/connection/ILoadBalancerStrategy.d.ts +13 -0
- package/dist/connection/ILoadBalancerStrategy.d.ts.map +1 -0
- package/dist/connection/ILoadBalancerStrategy.js +4 -0
- package/dist/connection/ILoadBalancerStrategy.js.map +1 -0
- package/dist/connection/KernelKeepAlive.d.ts +73 -0
- package/dist/connection/KernelKeepAlive.d.ts.map +1 -0
- package/dist/connection/KernelKeepAlive.js +115 -0
- package/dist/connection/KernelKeepAlive.js.map +1 -0
- package/dist/connection/LeastConnectionsLoadBalancerStrategy.d.ts +30 -0
- package/dist/connection/LeastConnectionsLoadBalancerStrategy.d.ts.map +1 -0
- package/dist/connection/LeastConnectionsLoadBalancerStrategy.js +95 -0
- package/dist/connection/LeastConnectionsLoadBalancerStrategy.js.map +1 -0
- package/dist/connection/LoadBalancerMode.d.ts +21 -0
- package/dist/connection/LoadBalancerMode.d.ts.map +1 -0
- package/dist/connection/LoadBalancerMode.js +24 -0
- package/dist/connection/LoadBalancerMode.js.map +1 -0
- package/dist/connection/LoadBalancerStrategyFactory.d.ts +11 -0
- package/dist/connection/LoadBalancerStrategyFactory.d.ts.map +1 -0
- package/dist/connection/LoadBalancerStrategyFactory.js +26 -0
- package/dist/connection/LoadBalancerStrategyFactory.js.map +1 -0
- package/dist/connection/RandomLoadBalancerStrategy.d.ts +9 -0
- package/dist/connection/RandomLoadBalancerStrategy.d.ts.map +1 -0
- package/dist/connection/RandomLoadBalancerStrategy.js +15 -0
- package/dist/connection/RandomLoadBalancerStrategy.js.map +1 -0
- package/dist/connection/RoundRobinLoadBalancerStrategy.d.ts +12 -0
- package/dist/connection/RoundRobinLoadBalancerStrategy.d.ts.map +1 -0
- package/dist/connection/RoundRobinLoadBalancerStrategy.js +22 -0
- package/dist/connection/RoundRobinLoadBalancerStrategy.js.map +1 -0
- package/dist/connection/TokenProvider.d.ts +32 -7
- package/dist/connection/TokenProvider.d.ts.map +1 -1
- package/dist/connection/TokenProvider.js +69 -61
- package/dist/connection/TokenProvider.js.map +1 -1
- package/dist/connection/fetchOAuthAccessToken.d.ts +18 -0
- package/dist/connection/fetchOAuthAccessToken.d.ts.map +1 -0
- package/dist/connection/fetchOAuthAccessToken.js +62 -0
- package/dist/connection/fetchOAuthAccessToken.js.map +1 -0
- package/dist/connection/formatServerAddress.d.ts +10 -0
- package/dist/connection/formatServerAddress.d.ts.map +1 -0
- package/dist/connection/formatServerAddress.js +14 -0
- package/dist/connection/formatServerAddress.js.map +1 -0
- package/dist/reactors/Reactors.d.ts +13 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +42 -4
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/reducers/Reducers.d.ts +14 -0
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +43 -8
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -5
- package/reactors/Reactors.ts +53 -4
- package/reducers/Reducers.ts +55 -8
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChronicleSrvResolver.js","sourceRoot":"","sources":["../../connection/ChronicleSrvResolver.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,OAAO,oBAAoB;IAMA;IAL7B;;;;OAIG;IACH,YAA6B,kBAAkC,GAAG,EAAE,CAAC,IAAI,QAAQ,EAAE;QAAtD,oBAAe,GAAf,eAAe,CAAuC;IAAG,CAAC;IAEvF;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,UAAmB;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAExC,IAAI,UAAU,EAAE,CAAC;YACb,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,IAAI,GAAG,mBAAmB,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAEhD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,OAAO;aACT,KAAK,EAAE;aACP,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YAC1F,yEAAyE;aACxE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtF,CAAC;IAEO,eAAe,CAAC,UAAkB;QACtC,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,IAAI,gBAAgB,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IAChF,CAAC;CACJ"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChronicleServerAddress } from './ChronicleConnectionString';
|
|
2
|
+
/**
|
|
3
|
+
* Selects one server address from a list of candidates for a connect/reconnect attempt.
|
|
4
|
+
*/
|
|
5
|
+
export interface ILoadBalancerStrategy {
|
|
6
|
+
/**
|
|
7
|
+
* Selects one address from the given candidates.
|
|
8
|
+
* @param candidates - The addresses to select from. Never empty.
|
|
9
|
+
* @returns A promise resolving to the selected address.
|
|
10
|
+
*/
|
|
11
|
+
select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ILoadBalancerStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ILoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/ILoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACjF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ILoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/ILoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ConnectRequest } from '@cratis/chronicle.contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Overrides for how long {@link KernelKeepAlive} waits before giving up on the kernel.
|
|
4
|
+
*/
|
|
5
|
+
export interface KeepAliveTimeouts {
|
|
6
|
+
/** How long to wait for a keep-alive before treating the connection as dead. */
|
|
7
|
+
idleTimeoutMs?: number;
|
|
8
|
+
/** How long a single answer may take before the connection is considered dead. */
|
|
9
|
+
answerTimeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The subset of the connection service {@link KernelKeepAlive} needs, so it can be
|
|
13
|
+
* exercised without a live kernel.
|
|
14
|
+
*/
|
|
15
|
+
export interface IKeepAliveConnections {
|
|
16
|
+
connect(request: ConnectRequest, options?: {
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}): AsyncIterable<unknown>;
|
|
19
|
+
connectionKeepAlive(request: object, options?: {
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drives the client half of Chronicle's keep-alive ping-pong.
|
|
25
|
+
*
|
|
26
|
+
* The kernel pushes a `ConnectionKeepAlive` down the `Connect` server stream once
|
|
27
|
+
* per second, and for each one the client must call the separate unary
|
|
28
|
+
* `ConnectionKeepAlive` RPC back — that is what bumps `LastSeen` on the kernel and
|
|
29
|
+
* keeps the client registered. A client that stops answering is evicted, its
|
|
30
|
+
* observers are unsubscribed, and reactors and reducers go silent while appends
|
|
31
|
+
* keep working.
|
|
32
|
+
*
|
|
33
|
+
* Eviction does not close the `Connect` stream, so a dead session usually presents
|
|
34
|
+
* as a stream that simply goes quiet rather than one that errors. Both a gap
|
|
35
|
+
* between keep-alives and an ended stream therefore mean the same thing: the
|
|
36
|
+
* connection is gone and the client must reconnect.
|
|
37
|
+
*/
|
|
38
|
+
export declare class KernelKeepAlive {
|
|
39
|
+
private readonly connections;
|
|
40
|
+
private readonly onConnectionLost;
|
|
41
|
+
/**
|
|
42
|
+
* How long to wait for a keep-alive before treating the connection as dead.
|
|
43
|
+
* The kernel emits one per second and evicts clients whose `LastSeen` falls
|
|
44
|
+
* more than five seconds behind, which is the threshold the C# client uses too.
|
|
45
|
+
*/
|
|
46
|
+
static readonly defaultIdleTimeoutMs = 5000;
|
|
47
|
+
/** How long a single answer may take before the connection is considered dead. */
|
|
48
|
+
static readonly defaultAnswerTimeoutMs = 5000;
|
|
49
|
+
private readonly _logger;
|
|
50
|
+
private readonly _idleTimeoutMs;
|
|
51
|
+
private readonly _answerTimeoutMs;
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new {@link KernelKeepAlive}.
|
|
54
|
+
* @param connections - The connection service to ping-pong with.
|
|
55
|
+
* @param onConnectionLost - Called once when the connection is found to be dead.
|
|
56
|
+
* @param timeouts - Overrides for the default timeouts.
|
|
57
|
+
*/
|
|
58
|
+
constructor(connections: IKeepAliveConnections, onConnectionLost: (reason: string, error: unknown) => void, timeouts?: KeepAliveTimeouts);
|
|
59
|
+
/**
|
|
60
|
+
* Opens the `Connect` stream and answers the first keep-alive, establishing the
|
|
61
|
+
* client's registration with the kernel. Resolves once the kernel has been heard
|
|
62
|
+
* from; the ongoing ping-pong then continues in the background until the
|
|
63
|
+
* connection is lost or `signal` is aborted.
|
|
64
|
+
* @param request - The connect request identifying this client.
|
|
65
|
+
* @param signal - Aborted to stop the keep-alive entirely.
|
|
66
|
+
*/
|
|
67
|
+
start(request: ConnectRequest, signal: AbortSignal): Promise<void>;
|
|
68
|
+
private run;
|
|
69
|
+
private answer;
|
|
70
|
+
private connectionLost;
|
|
71
|
+
private withTimeout;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=KernelKeepAlive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KernelKeepAlive.d.ts","sourceRoot":"","sources":["../../connection/KernelKeepAlive.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7F,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9F;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAyBpB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAzBrC;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,oBAAoB,QAAQ;IAE5C,kFAAkF;IAClF,MAAM,CAAC,QAAQ,CAAC,sBAAsB,QAAQ;IAE9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAErB;IAEH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C;;;;;OAKG;gBAEkB,WAAW,EAAE,qBAAqB,EAClC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,EAC3E,QAAQ,GAAE,iBAAsB;IAMpC;;;;;;;OAOG;IACG,KAAK,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAqB1D,GAAG;YAuBH,MAAM;IAUpB,OAAO,CAAC,cAAc;YAQR,WAAW;CAiB5B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
import { diag } from '@opentelemetry/api';
|
|
4
|
+
/**
|
|
5
|
+
* Drives the client half of Chronicle's keep-alive ping-pong.
|
|
6
|
+
*
|
|
7
|
+
* The kernel pushes a `ConnectionKeepAlive` down the `Connect` server stream once
|
|
8
|
+
* per second, and for each one the client must call the separate unary
|
|
9
|
+
* `ConnectionKeepAlive` RPC back — that is what bumps `LastSeen` on the kernel and
|
|
10
|
+
* keeps the client registered. A client that stops answering is evicted, its
|
|
11
|
+
* observers are unsubscribed, and reactors and reducers go silent while appends
|
|
12
|
+
* keep working.
|
|
13
|
+
*
|
|
14
|
+
* Eviction does not close the `Connect` stream, so a dead session usually presents
|
|
15
|
+
* as a stream that simply goes quiet rather than one that errors. Both a gap
|
|
16
|
+
* between keep-alives and an ended stream therefore mean the same thing: the
|
|
17
|
+
* connection is gone and the client must reconnect.
|
|
18
|
+
*/
|
|
19
|
+
export class KernelKeepAlive {
|
|
20
|
+
connections;
|
|
21
|
+
onConnectionLost;
|
|
22
|
+
/**
|
|
23
|
+
* How long to wait for a keep-alive before treating the connection as dead.
|
|
24
|
+
* The kernel emits one per second and evicts clients whose `LastSeen` falls
|
|
25
|
+
* more than five seconds behind, which is the threshold the C# client uses too.
|
|
26
|
+
*/
|
|
27
|
+
static defaultIdleTimeoutMs = 5000;
|
|
28
|
+
/** How long a single answer may take before the connection is considered dead. */
|
|
29
|
+
static defaultAnswerTimeoutMs = 5000;
|
|
30
|
+
_logger = diag.createComponentLogger({
|
|
31
|
+
namespace: '@cratis/chronicle/KernelKeepAlive'
|
|
32
|
+
});
|
|
33
|
+
_idleTimeoutMs;
|
|
34
|
+
_answerTimeoutMs;
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new {@link KernelKeepAlive}.
|
|
37
|
+
* @param connections - The connection service to ping-pong with.
|
|
38
|
+
* @param onConnectionLost - Called once when the connection is found to be dead.
|
|
39
|
+
* @param timeouts - Overrides for the default timeouts.
|
|
40
|
+
*/
|
|
41
|
+
constructor(connections, onConnectionLost, timeouts = {}) {
|
|
42
|
+
this.connections = connections;
|
|
43
|
+
this.onConnectionLost = onConnectionLost;
|
|
44
|
+
this._idleTimeoutMs = timeouts.idleTimeoutMs ?? KernelKeepAlive.defaultIdleTimeoutMs;
|
|
45
|
+
this._answerTimeoutMs = timeouts.answerTimeoutMs ?? KernelKeepAlive.defaultAnswerTimeoutMs;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Opens the `Connect` stream and answers the first keep-alive, establishing the
|
|
49
|
+
* client's registration with the kernel. Resolves once the kernel has been heard
|
|
50
|
+
* from; the ongoing ping-pong then continues in the background until the
|
|
51
|
+
* connection is lost or `signal` is aborted.
|
|
52
|
+
* @param request - The connect request identifying this client.
|
|
53
|
+
* @param signal - Aborted to stop the keep-alive entirely.
|
|
54
|
+
*/
|
|
55
|
+
async start(request, signal) {
|
|
56
|
+
const stream = this.connections.connect(request, { signal });
|
|
57
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
58
|
+
// A kernel that accepts the stream but never sends anything must not hang
|
|
59
|
+
// the connect indefinitely — every caller awaiting the connection would
|
|
60
|
+
// block forever behind it.
|
|
61
|
+
const first = await this.withTimeout(iterator.next(), this._idleTimeoutMs, 'Timed out waiting for the first keep-alive from the kernel');
|
|
62
|
+
if (first.done) {
|
|
63
|
+
throw new Error('Connection service stream ended before sending first keep-alive');
|
|
64
|
+
}
|
|
65
|
+
await this.answer(first.value, signal);
|
|
66
|
+
void this.run(iterator, signal);
|
|
67
|
+
}
|
|
68
|
+
async run(iterator, signal) {
|
|
69
|
+
try {
|
|
70
|
+
while (!signal.aborted) {
|
|
71
|
+
const result = await this.withTimeout(iterator.next(), this._idleTimeoutMs, 'No keep-alive received from the kernel within the idle timeout');
|
|
72
|
+
if (result.done) {
|
|
73
|
+
this.connectionLost('keep-alive-stream-ended', new Error('Keep-alive stream ended'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await this.answer(result.value, signal);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!signal.aborted) {
|
|
81
|
+
this.connectionLost('keep-alive-failed', error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async answer(keepAlive, signal) {
|
|
86
|
+
// An answer that never returns would stop the loop draining the stream, which
|
|
87
|
+
// gets the client evicted just as surely as never answering at all.
|
|
88
|
+
await this.withTimeout(this.connections.connectionKeepAlive(keepAlive, { signal }), this._answerTimeoutMs, 'Timed out answering a kernel keep-alive');
|
|
89
|
+
}
|
|
90
|
+
connectionLost(reason, error) {
|
|
91
|
+
this._logger.warn('Kernel keep-alive lost the connection', {
|
|
92
|
+
reason,
|
|
93
|
+
error: error instanceof Error ? error.message : String(error)
|
|
94
|
+
});
|
|
95
|
+
this.onConnectionLost(reason, error);
|
|
96
|
+
}
|
|
97
|
+
async withTimeout(promise, timeoutMs, message) {
|
|
98
|
+
let handle;
|
|
99
|
+
try {
|
|
100
|
+
return await Promise.race([
|
|
101
|
+
promise,
|
|
102
|
+
new Promise((_, reject) => {
|
|
103
|
+
handle = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
104
|
+
handle.unref?.();
|
|
105
|
+
})
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
if (handle) {
|
|
110
|
+
clearTimeout(handle);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=KernelKeepAlive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KernelKeepAlive.js","sourceRoot":"","sources":["../../connection/KernelKeepAlive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChronicleServerAddress } from './ChronicleConnectionString';
|
|
2
|
+
import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
|
|
3
|
+
/**
|
|
4
|
+
* Selects the least-loaded candidate by probing each one's current connection count over
|
|
5
|
+
* HTTP and picking the minimum, breaking ties randomly. This is the default strategy.
|
|
6
|
+
*
|
|
7
|
+
* Before every probe attempt (not just the first), waits a random jitter to avoid a
|
|
8
|
+
* thundering herd of clients probing every candidate at the exact same instant. A single
|
|
9
|
+
* candidate is returned immediately without probing, since there is nothing to choose
|
|
10
|
+
* between.
|
|
11
|
+
*/
|
|
12
|
+
export declare class LeastConnectionsLoadBalancerStrategy implements ILoadBalancerStrategy {
|
|
13
|
+
private readonly _skipTlsValidation;
|
|
14
|
+
private readonly _maxJitterMs;
|
|
15
|
+
private readonly _dispatcher;
|
|
16
|
+
/**
|
|
17
|
+
* Initializes a new instance of {@link LeastConnectionsLoadBalancerStrategy}.
|
|
18
|
+
* @param skipTlsValidation - Whether to skip TLS certificate validation on the
|
|
19
|
+
* `/connections/count` and `/connections/reserve` probe requests, matching the toggle
|
|
20
|
+
* used for the gRPC channel itself.
|
|
21
|
+
* @param maxJitterMs - The maximum jitter, in milliseconds, to wait before every probe
|
|
22
|
+
* attempt. Defaults to 250ms. 0 disables jitter entirely.
|
|
23
|
+
*/
|
|
24
|
+
constructor(_skipTlsValidation: boolean, _maxJitterMs?: number);
|
|
25
|
+
select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
|
|
26
|
+
private jitter;
|
|
27
|
+
private probeConnectionCount;
|
|
28
|
+
private reserve;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=LeastConnectionsLoadBalancerStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeastConnectionsLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/LeastConnectionsLoadBalancerStrategy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAWrE;;;;;;;;GAQG;AACH,qBAAa,oCAAqC,YAAW,qBAAqB;IAY1E,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC;;;;;;;OAOG;gBAEkB,kBAAkB,EAAE,OAAO,EAC3B,YAAY,GAAE,MAA8B;IAK3D,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;YA2BrE,MAAM;YAaN,oBAAoB;YAqBpB,OAAO;CAWxB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
import { Agent } from 'undici';
|
|
4
|
+
import { formatServerAddress } from './formatServerAddress';
|
|
5
|
+
const DEFAULT_MAX_JITTER_MS = 250;
|
|
6
|
+
const PROBE_TIMEOUT_MS = 2000;
|
|
7
|
+
/**
|
|
8
|
+
* Selects the least-loaded candidate by probing each one's current connection count over
|
|
9
|
+
* HTTP and picking the minimum, breaking ties randomly. This is the default strategy.
|
|
10
|
+
*
|
|
11
|
+
* Before every probe attempt (not just the first), waits a random jitter to avoid a
|
|
12
|
+
* thundering herd of clients probing every candidate at the exact same instant. A single
|
|
13
|
+
* candidate is returned immediately without probing, since there is nothing to choose
|
|
14
|
+
* between.
|
|
15
|
+
*/
|
|
16
|
+
export class LeastConnectionsLoadBalancerStrategy {
|
|
17
|
+
_skipTlsValidation;
|
|
18
|
+
_maxJitterMs;
|
|
19
|
+
_dispatcher;
|
|
20
|
+
/**
|
|
21
|
+
* Initializes a new instance of {@link LeastConnectionsLoadBalancerStrategy}.
|
|
22
|
+
* @param skipTlsValidation - Whether to skip TLS certificate validation on the
|
|
23
|
+
* `/connections/count` and `/connections/reserve` probe requests, matching the toggle
|
|
24
|
+
* used for the gRPC channel itself.
|
|
25
|
+
* @param maxJitterMs - The maximum jitter, in milliseconds, to wait before every probe
|
|
26
|
+
* attempt. Defaults to 250ms. 0 disables jitter entirely.
|
|
27
|
+
*/
|
|
28
|
+
constructor(_skipTlsValidation, _maxJitterMs = DEFAULT_MAX_JITTER_MS) {
|
|
29
|
+
this._skipTlsValidation = _skipTlsValidation;
|
|
30
|
+
this._maxJitterMs = _maxJitterMs;
|
|
31
|
+
this._dispatcher = new Agent({ connect: { rejectUnauthorized: !this._skipTlsValidation } });
|
|
32
|
+
}
|
|
33
|
+
async select(candidates) {
|
|
34
|
+
if (candidates.length === 0) {
|
|
35
|
+
throw new Error('Cannot select a server address from an empty candidate list');
|
|
36
|
+
}
|
|
37
|
+
if (candidates.length === 1) {
|
|
38
|
+
return candidates[0];
|
|
39
|
+
}
|
|
40
|
+
await this.jitter();
|
|
41
|
+
const connectionCounts = await Promise.all(candidates.map(candidate => this.probeConnectionCount(candidate)));
|
|
42
|
+
const minimumCount = Math.min(...connectionCounts);
|
|
43
|
+
const minimumIndices = connectionCounts.reduce((indices, count, index) => {
|
|
44
|
+
if (count === minimumCount) {
|
|
45
|
+
indices.push(index);
|
|
46
|
+
}
|
|
47
|
+
return indices;
|
|
48
|
+
}, []);
|
|
49
|
+
const selected = candidates[minimumIndices[Math.floor(Math.random() * minimumIndices.length)]];
|
|
50
|
+
await this.reserve(selected);
|
|
51
|
+
return selected;
|
|
52
|
+
}
|
|
53
|
+
async jitter() {
|
|
54
|
+
if (this._maxJitterMs <= 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const delayMs = Math.floor(Math.random() * this._maxJitterMs);
|
|
58
|
+
if (delayMs <= 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
62
|
+
}
|
|
63
|
+
async probeConnectionCount(candidate) {
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(`https://${formatServerAddress(candidate)}/connections/count`, {
|
|
66
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
67
|
+
dispatcher: this._dispatcher
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
return Number.MAX_SAFE_INTEGER;
|
|
71
|
+
}
|
|
72
|
+
const body = (await response.text()).trim();
|
|
73
|
+
const count = Number(body);
|
|
74
|
+
return body.length > 0 && Number.isFinite(count) ? count : Number.MAX_SAFE_INTEGER;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// A candidate that cannot be reached or answers with garbage is never preferred
|
|
78
|
+
// over one that responds normally, but must not fail the whole selection round.
|
|
79
|
+
return Number.MAX_SAFE_INTEGER;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async reserve(candidate) {
|
|
83
|
+
try {
|
|
84
|
+
await fetch(`https://${formatServerAddress(candidate)}/connections/reserve`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
87
|
+
dispatcher: this._dispatcher
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Best-effort reservation; the selected server is still used even if this fails.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=LeastConnectionsLoadBalancerStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeastConnectionsLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/LeastConnectionsLoadBalancerStrategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strategy used to select one server address from a multi-host connection string
|
|
3
|
+
* (or a resolved set of `chronicle+srv://` addresses) for each connect/reconnect attempt.
|
|
4
|
+
*/
|
|
5
|
+
export declare enum LoadBalancerMode {
|
|
6
|
+
/**
|
|
7
|
+
* Probes every candidate's current connection count and picks the least-loaded one,
|
|
8
|
+
* breaking ties randomly. The default strategy.
|
|
9
|
+
*/
|
|
10
|
+
LeastConnections = "least-connections",
|
|
11
|
+
/**
|
|
12
|
+
* Cycles through candidates in order, starting from a random offset chosen once per
|
|
13
|
+
* strategy instance.
|
|
14
|
+
*/
|
|
15
|
+
RoundRobin = "round-robin",
|
|
16
|
+
/**
|
|
17
|
+
* Picks a uniformly random candidate on every call.
|
|
18
|
+
*/
|
|
19
|
+
Random = "random"
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=LoadBalancerMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadBalancerMode.d.ts","sourceRoot":"","sources":["../../connection/LoadBalancerMode.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oBAAY,gBAAgB;IACxB;;;OAGG;IACH,gBAAgB,sBAAsB;IAEtC;;;OAGG;IACH,UAAU,gBAAgB;IAE1B;;OAEG;IACH,MAAM,WAAW;CACpB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
/**
|
|
4
|
+
* The strategy used to select one server address from a multi-host connection string
|
|
5
|
+
* (or a resolved set of `chronicle+srv://` addresses) for each connect/reconnect attempt.
|
|
6
|
+
*/
|
|
7
|
+
export var LoadBalancerMode;
|
|
8
|
+
(function (LoadBalancerMode) {
|
|
9
|
+
/**
|
|
10
|
+
* Probes every candidate's current connection count and picks the least-loaded one,
|
|
11
|
+
* breaking ties randomly. The default strategy.
|
|
12
|
+
*/
|
|
13
|
+
LoadBalancerMode["LeastConnections"] = "least-connections";
|
|
14
|
+
/**
|
|
15
|
+
* Cycles through candidates in order, starting from a random offset chosen once per
|
|
16
|
+
* strategy instance.
|
|
17
|
+
*/
|
|
18
|
+
LoadBalancerMode["RoundRobin"] = "round-robin";
|
|
19
|
+
/**
|
|
20
|
+
* Picks a uniformly random candidate on every call.
|
|
21
|
+
*/
|
|
22
|
+
LoadBalancerMode["Random"] = "random";
|
|
23
|
+
})(LoadBalancerMode || (LoadBalancerMode = {}));
|
|
24
|
+
//# sourceMappingURL=LoadBalancerMode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadBalancerMode.js","sourceRoot":"","sources":["../../connection/LoadBalancerMode.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG;;;GAGG;AACH,MAAM,CAAN,IAAY,gBAiBX;AAjBD,WAAY,gBAAgB;IACxB;;;OAGG;IACH,0DAAsC,CAAA;IAEtC;;;OAGG;IACH,8CAA0B,CAAA;IAE1B;;OAEG;IACH,qCAAiB,CAAA;AACrB,CAAC,EAjBW,gBAAgB,KAAhB,gBAAgB,QAiB3B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
|
|
2
|
+
import { LoadBalancerMode } from './LoadBalancerMode';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the {@link ILoadBalancerStrategy} instance matching a {@link LoadBalancerMode}.
|
|
5
|
+
* @param mode - The load balancer mode to create a strategy for.
|
|
6
|
+
* @param skipTlsValidation - Whether to skip TLS certificate validation for strategies that
|
|
7
|
+
* probe candidates over HTTP (currently only {@link LoadBalancerMode.LeastConnections}).
|
|
8
|
+
* @returns The load balancer strategy instance.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createLoadBalancerStrategy(mode: LoadBalancerMode, skipTlsValidation: boolean): ILoadBalancerStrategy;
|
|
11
|
+
//# sourceMappingURL=LoadBalancerStrategyFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadBalancerStrategyFactory.d.ts","sourceRoot":"","sources":["../../connection/LoadBalancerStrategyFactory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,OAAO,GAAG,qBAAqB,CAWpH"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
import { LeastConnectionsLoadBalancerStrategy } from './LeastConnectionsLoadBalancerStrategy';
|
|
4
|
+
import { LoadBalancerMode } from './LoadBalancerMode';
|
|
5
|
+
import { RandomLoadBalancerStrategy } from './RandomLoadBalancerStrategy';
|
|
6
|
+
import { RoundRobinLoadBalancerStrategy } from './RoundRobinLoadBalancerStrategy';
|
|
7
|
+
/**
|
|
8
|
+
* Creates the {@link ILoadBalancerStrategy} instance matching a {@link LoadBalancerMode}.
|
|
9
|
+
* @param mode - The load balancer mode to create a strategy for.
|
|
10
|
+
* @param skipTlsValidation - Whether to skip TLS certificate validation for strategies that
|
|
11
|
+
* probe candidates over HTTP (currently only {@link LoadBalancerMode.LeastConnections}).
|
|
12
|
+
* @returns The load balancer strategy instance.
|
|
13
|
+
*/
|
|
14
|
+
export function createLoadBalancerStrategy(mode, skipTlsValidation) {
|
|
15
|
+
switch (mode) {
|
|
16
|
+
case LoadBalancerMode.RoundRobin:
|
|
17
|
+
return new RoundRobinLoadBalancerStrategy();
|
|
18
|
+
case LoadBalancerMode.Random:
|
|
19
|
+
return new RandomLoadBalancerStrategy();
|
|
20
|
+
case LoadBalancerMode.LeastConnections:
|
|
21
|
+
return new LeastConnectionsLoadBalancerStrategy(skipTlsValidation);
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unknown load balancer mode: ${mode}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=LoadBalancerStrategyFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadBalancerStrategyFactory.js","sourceRoot":"","sources":["../../connection/LoadBalancerStrategyFactory.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAGrG,OAAO,EAAE,oCAAoC,EAAE,MAAM,wCAAwC,CAAC;AAC9F,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAsB,EAAE,iBAA0B;IACzF,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,gBAAgB,CAAC,UAAU;YAC5B,OAAO,IAAI,8BAA8B,EAAE,CAAC;QAChD,KAAK,gBAAgB,CAAC,MAAM;YACxB,OAAO,IAAI,0BAA0B,EAAE,CAAC;QAC5C,KAAK,gBAAgB,CAAC,gBAAgB;YAClC,OAAO,IAAI,oCAAoC,CAAC,iBAAiB,CAAC,CAAC;QACvE;YACI,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChronicleServerAddress } from './ChronicleConnectionString';
|
|
2
|
+
import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
|
|
3
|
+
/**
|
|
4
|
+
* Selects a uniformly random candidate on every call.
|
|
5
|
+
*/
|
|
6
|
+
export declare class RandomLoadBalancerStrategy implements ILoadBalancerStrategy {
|
|
7
|
+
select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=RandomLoadBalancerStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RandomLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/RandomLoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE;;GAEG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IAC9D,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;CAQtF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
/**
|
|
4
|
+
* Selects a uniformly random candidate on every call.
|
|
5
|
+
*/
|
|
6
|
+
export class RandomLoadBalancerStrategy {
|
|
7
|
+
async select(candidates) {
|
|
8
|
+
if (candidates.length === 0) {
|
|
9
|
+
throw new Error('Cannot select a server address from an empty candidate list');
|
|
10
|
+
}
|
|
11
|
+
const index = Math.floor(Math.random() * candidates.length);
|
|
12
|
+
return candidates[index];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=RandomLoadBalancerStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RandomLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/RandomLoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAKrG;;GAEG;AACH,MAAM,OAAO,0BAA0B;IACnC,KAAK,CAAC,MAAM,CAAC,UAAoC;QAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACJ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChronicleServerAddress } from './ChronicleConnectionString';
|
|
2
|
+
import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
|
|
3
|
+
/**
|
|
4
|
+
* Cycles through candidates in order, starting from a random offset chosen once per
|
|
5
|
+
* strategy instance so that multiple clients don't all start at the same candidate.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RoundRobinLoadBalancerStrategy implements ILoadBalancerStrategy {
|
|
8
|
+
private _startOffset?;
|
|
9
|
+
private _callCount;
|
|
10
|
+
select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=RoundRobinLoadBalancerStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoundRobinLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/RoundRobinLoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE;;;GAGG;AACH,qBAAa,8BAA+B,YAAW,qBAAqB;IACxE,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAK;IAEjB,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;CActF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
/**
|
|
4
|
+
* Cycles through candidates in order, starting from a random offset chosen once per
|
|
5
|
+
* strategy instance so that multiple clients don't all start at the same candidate.
|
|
6
|
+
*/
|
|
7
|
+
export class RoundRobinLoadBalancerStrategy {
|
|
8
|
+
_startOffset;
|
|
9
|
+
_callCount = 0;
|
|
10
|
+
async select(candidates) {
|
|
11
|
+
if (candidates.length === 0) {
|
|
12
|
+
throw new Error('Cannot select a server address from an empty candidate list');
|
|
13
|
+
}
|
|
14
|
+
if (this._startOffset === undefined) {
|
|
15
|
+
this._startOffset = Math.floor(Math.random() * candidates.length);
|
|
16
|
+
}
|
|
17
|
+
const index = (this._startOffset + this._callCount) % candidates.length;
|
|
18
|
+
this._callCount++;
|
|
19
|
+
return candidates[index];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=RoundRobinLoadBalancerStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoundRobinLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/RoundRobinLoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAKrG;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IAC/B,YAAY,CAAU;IACtB,UAAU,GAAG,CAAC,CAAC;IAEvB,KAAK,CAAC,MAAM,CAAC,UAAoC;QAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACxE,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACJ"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { type OAuthTokenResponse } from './fetchOAuthAccessToken';
|
|
1
2
|
/**
|
|
2
3
|
* Interface for providing authentication tokens.
|
|
3
4
|
*/
|
|
4
5
|
export interface ITokenProvider {
|
|
5
6
|
/**
|
|
6
7
|
* Gets the current access token.
|
|
8
|
+
*
|
|
9
|
+
* Never rejects — when no token can be obtained the result is undefined, the RPC
|
|
10
|
+
* proceeds without authorization and fails with the server's rejection, which the
|
|
11
|
+
* session machinery recovers from.
|
|
7
12
|
* @returns Promise resolving to the access token or undefined if not available.
|
|
8
13
|
*/
|
|
9
14
|
getAccessToken(): Promise<string | undefined>;
|
|
@@ -21,19 +26,39 @@ export declare class NoOpTokenProvider implements ITokenProvider {
|
|
|
21
26
|
refresh(): Promise<string | undefined>;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
|
-
* OAuth token provider using client credentials flow.
|
|
29
|
+
* OAuth token provider using the client credentials flow.
|
|
30
|
+
*
|
|
31
|
+
* Owns the access token so it can be attached to every RPC individually instead of
|
|
32
|
+
* being baked into the channel at connect time: the token is fetched lazily on first
|
|
33
|
+
* use, cached, and refreshed once it enters the refresh margin ahead of expiry, so
|
|
34
|
+
* token expiry never invalidates the channel. A failed refresh falls back to the
|
|
35
|
+
* cached token while it is still actually valid — only the refresh margin has been
|
|
36
|
+
* crossed, not the expiry — and further attempts are throttled so an unreachable
|
|
37
|
+
* auth endpoint does not turn every RPC into a fetch attempt.
|
|
25
38
|
*/
|
|
26
39
|
export declare class OAuthTokenProvider implements ITokenProvider {
|
|
27
|
-
private readonly
|
|
28
|
-
private readonly
|
|
29
|
-
private readonly _clientSecret;
|
|
40
|
+
private readonly _fetchToken;
|
|
41
|
+
private readonly _logger;
|
|
30
42
|
private _accessToken?;
|
|
31
|
-
private
|
|
43
|
+
private _expiresAt;
|
|
44
|
+
private _lastFailedFetch?;
|
|
32
45
|
private _refreshPromise?;
|
|
33
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new {@link OAuthTokenProvider}.
|
|
48
|
+
* @param tokenEndpoint - The OAuth2 token endpoint to request tokens from.
|
|
49
|
+
* @param clientId - The client identifier.
|
|
50
|
+
* @param clientSecret - The client secret.
|
|
51
|
+
* @param skipTlsValidation - Whether to skip TLS certificate chain validation.
|
|
52
|
+
* @param _fetchToken - Test-only seam replacing the OAuth2 token request.
|
|
53
|
+
*/
|
|
54
|
+
constructor(tokenEndpoint: string, clientId: string, clientSecret: string, skipTlsValidation?: boolean, _fetchToken?: () => Promise<OAuthTokenResponse>);
|
|
34
55
|
getAccessToken(): Promise<string | undefined>;
|
|
35
56
|
refresh(): Promise<string | undefined>;
|
|
36
|
-
private
|
|
57
|
+
private hasFreshToken;
|
|
58
|
+
private isThrottled;
|
|
59
|
+
private cachedTokenWhileValid;
|
|
60
|
+
private fetchAndCacheAccessToken;
|
|
61
|
+
private lifetimeSecondsFrom;
|
|
37
62
|
}
|
|
38
63
|
export type TokenProvider = ITokenProvider;
|
|
39
64
|
//# sourceMappingURL=TokenProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAUzF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;;;;;OAOG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAC9C,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAG/C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAuBjD,OAAO,CAAC,QAAQ,CAAC,WAAW;IAtBhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAErB;IAEH,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,eAAe,CAAC,CAA8B;IAEtD;;;;;;;OAOG;gBAEC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,iBAAiB,GAAE,OAAc,EAChB,WAAW,GAAE,MAAM,OAAO,CAAC,kBAAkB,CACqB;IAGjF,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAqB7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAO5C,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,qBAAqB;YAIf,wBAAwB;IAkBtC,OAAO,CAAC,mBAAmB;CAI9B;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
|