@cratis/chronicle 1.0.2 → 2.1.0
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/ChronicleOptions.ts +5 -6
- package/README.md +1 -1
- package/connection/ChronicleConnection.ts +37 -17
- package/connection/ChronicleConnectionString.spec.ts +227 -0
- package/connection/ChronicleConnectionString.ts +289 -35
- 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.ts +8 -3
- 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/ChronicleOptions.d.ts +4 -2
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +5 -3
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +4 -5
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +34 -12
- 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 +250 -32
- 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 +2 -1
- package/dist/connection/TokenProvider.d.ts.map +1 -1
- package/dist/connection/TokenProvider.js +9 -3
- package/dist/connection/TokenProvider.js.map +1 -1
- 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,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"}
|
|
@@ -27,10 +27,11 @@ export declare class OAuthTokenProvider implements ITokenProvider {
|
|
|
27
27
|
private readonly _tokenEndpoint;
|
|
28
28
|
private readonly _clientId;
|
|
29
29
|
private readonly _clientSecret;
|
|
30
|
+
private readonly _skipTlsValidation;
|
|
30
31
|
private _accessToken?;
|
|
31
32
|
private _tokenExpiry;
|
|
32
33
|
private _refreshPromise?;
|
|
33
|
-
constructor(_tokenEndpoint: string, _clientId: string, _clientSecret: string);
|
|
34
|
+
constructor(_tokenEndpoint: string, _clientId: string, _clientSecret: string, _skipTlsValidation?: boolean);
|
|
34
35
|
getAccessToken(): Promise<string | undefined>;
|
|
35
36
|
refresh(): Promise<string | undefined>;
|
|
36
37
|
private fetchAccessToken;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;OAGG;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;AAOD;;GAEG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAMjD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;OAGG;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;AAOD;;GAEG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAMjD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IARvC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAC,CAA8B;gBAGjC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,GAAE,OAAc;IAGjD,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAiB7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;YAM9B,gBAAgB;CAuDjC;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
|
|
@@ -22,13 +22,15 @@ export class OAuthTokenProvider {
|
|
|
22
22
|
_tokenEndpoint;
|
|
23
23
|
_clientId;
|
|
24
24
|
_clientSecret;
|
|
25
|
+
_skipTlsValidation;
|
|
25
26
|
_accessToken;
|
|
26
27
|
_tokenExpiry = new Date(0);
|
|
27
28
|
_refreshPromise;
|
|
28
|
-
constructor(_tokenEndpoint, _clientId, _clientSecret) {
|
|
29
|
+
constructor(_tokenEndpoint, _clientId, _clientSecret, _skipTlsValidation = true) {
|
|
29
30
|
this._tokenEndpoint = _tokenEndpoint;
|
|
30
31
|
this._clientId = _clientId;
|
|
31
32
|
this._clientSecret = _clientSecret;
|
|
33
|
+
this._skipTlsValidation = _skipTlsValidation;
|
|
32
34
|
}
|
|
33
35
|
async getAccessToken() {
|
|
34
36
|
if (this._accessToken && new Date() < this._tokenExpiry) {
|
|
@@ -58,13 +60,17 @@ export class OAuthTokenProvider {
|
|
|
58
60
|
const body = params.toString();
|
|
59
61
|
return new Promise((resolve, reject) => {
|
|
60
62
|
const url = new URL(this._tokenEndpoint);
|
|
61
|
-
const
|
|
63
|
+
const isHttps = url.protocol === 'https:';
|
|
64
|
+
const httpModule = isHttps ? https : http;
|
|
62
65
|
const req = httpModule.request(url, {
|
|
63
66
|
method: 'POST',
|
|
64
67
|
headers: {
|
|
65
68
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
66
69
|
'Content-Length': Buffer.byteLength(body)
|
|
67
|
-
}
|
|
70
|
+
},
|
|
71
|
+
// Chain validation is skipped only when skipTlsValidation is explicitly set,
|
|
72
|
+
// matching the gRPC channel's credentials for the same connection string.
|
|
73
|
+
...(isHttps && this._skipTlsValidation ? { rejectUnauthorized: false } : {})
|
|
68
74
|
}, response => {
|
|
69
75
|
let data = '';
|
|
70
76
|
response.on('data', chunk => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenProvider.js","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAmB1C;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAC1B,KAAK,CAAC,cAAc;QAChB,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,OAAO;QACT,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AAOD;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAMN;IACA;IACA;
|
|
1
|
+
{"version":3,"file":"TokenProvider.js","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"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"}
|