@milaboratories/pl-client 2.16.13 → 2.16.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__external/.pnpm/{@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3 → @rollup_plugin-typescript@12.3.0_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3}/__external/tslib/tslib.es6.cjs.map +1 -1
- package/dist/__external/.pnpm/{@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3 → @rollup_plugin-typescript@12.3.0_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3}/__external/tslib/tslib.es6.js.map +1 -1
- package/dist/core/client.cjs +31 -16
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +3 -2
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +31 -16
- package/dist/core/client.js.map +1 -1
- package/dist/core/default_client.cjs +1 -1
- package/dist/core/default_client.cjs.map +1 -1
- package/dist/core/default_client.js +1 -1
- package/dist/core/default_client.js.map +1 -1
- package/dist/core/errors.cjs +15 -4
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +15 -4
- package/dist/core/errors.js.map +1 -1
- package/dist/core/ll_client.cjs +44 -14
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +12 -3
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +45 -15
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/transaction.cjs +1 -1
- package/dist/core/transaction.js +1 -1
- package/dist/core/unauth_client.cjs +6 -2
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +2 -1
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +6 -2
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/core/websocket_stream.cjs +23 -2
- package/dist/core/websocket_stream.cjs.map +1 -1
- package/dist/core/websocket_stream.d.ts.map +1 -1
- package/dist/core/websocket_stream.js +23 -2
- package/dist/core/websocket_stream.js.map +1 -1
- package/dist/test/test_config.cjs +13 -3
- package/dist/test/test_config.cjs.map +1 -1
- package/dist/test/test_config.d.ts +4 -0
- package/dist/test/test_config.d.ts.map +1 -1
- package/dist/test/test_config.js +12 -4
- package/dist/test/test_config.js.map +1 -1
- package/package.json +4 -4
- package/src/core/client.ts +40 -21
- package/src/core/default_client.ts +1 -1
- package/src/core/errors.ts +14 -4
- package/src/core/ll_client.test.ts +9 -3
- package/src/core/ll_client.ts +56 -18
- package/src/core/unauth_client.test.ts +4 -4
- package/src/core/unauth_client.ts +7 -2
- package/src/core/websocket_stream.ts +22 -1
- package/src/test/test_config.ts +13 -4
- /package/dist/__external/.pnpm/{@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3 → @rollup_plugin-typescript@12.3.0_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3}/__external/tslib/tslib.es6.cjs +0 -0
- /package/dist/__external/.pnpm/{@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3 → @rollup_plugin-typescript@12.3.0_rollup@4.52.4_tslib@2.8.1_typescript@5.6.3}/__external/tslib/tslib.es6.js +0 -0
package/src/core/ll_client.ts
CHANGED
|
@@ -26,7 +26,7 @@ import type { WireClientProvider, WireClientProviderFactory, WireConnection } fr
|
|
|
26
26
|
import { parseHttpAuth } from '@milaboratories/pl-model-common';
|
|
27
27
|
import type * as grpcTypes from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
28
28
|
import { type PlApiPaths, type PlRestClientType, createClient, parseResponseError } from '../proto-rest';
|
|
29
|
-
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
29
|
+
import { notEmpty, retry, withTimeout, type RetryOptions } from '@milaboratories/ts-helpers';
|
|
30
30
|
import { Code } from '../proto-grpc/google/rpc/code';
|
|
31
31
|
import { WebSocketBiDiStream } from './websocket_stream';
|
|
32
32
|
import { TxAPI_ClientMessage, TxAPI_ServerMessage } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
@@ -54,8 +54,6 @@ class WireClientProviderImpl<Client> implements WireClientProvider<Client> {
|
|
|
54
54
|
|
|
55
55
|
/** Abstract out low level networking and authorization details */
|
|
56
56
|
export class LLPlClient implements WireClientProviderFactory {
|
|
57
|
-
public readonly conf: PlClientConfig;
|
|
58
|
-
|
|
59
57
|
/** Initial authorization information */
|
|
60
58
|
private authInformation?: AuthInformation;
|
|
61
59
|
/** Will be executed by the client when it is required */
|
|
@@ -70,7 +68,7 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
70
68
|
private _status: PlConnectionStatus = 'OK';
|
|
71
69
|
private readonly statusListener?: PlConnectionStatusListener;
|
|
72
70
|
|
|
73
|
-
private _wireProto: wireProtocol
|
|
71
|
+
private _wireProto: wireProtocol = 'grpc';
|
|
74
72
|
private _wireConn!: WireConnection;
|
|
75
73
|
|
|
76
74
|
private readonly _restInterceptors: Dispatcher.DispatcherComposeInterceptor[];
|
|
@@ -82,18 +80,29 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
82
80
|
|
|
83
81
|
public readonly httpDispatcher: Dispatcher;
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
public static async build(
|
|
86
84
|
configOrAddress: PlClientConfig | string,
|
|
87
|
-
|
|
85
|
+
ops: {
|
|
88
86
|
auth?: AuthOps;
|
|
89
87
|
statusListener?: PlConnectionStatusListener;
|
|
90
88
|
shouldUseGzip?: boolean;
|
|
91
89
|
} = {},
|
|
92
90
|
) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const conf = typeof configOrAddress === 'string' ? plAddressToConfig(configOrAddress) : configOrAddress;
|
|
92
|
+
|
|
93
|
+
const pl = new LLPlClient(conf, ops);
|
|
94
|
+
await pl.detectOptimalWireProtocol();
|
|
95
|
+
return pl;
|
|
96
|
+
}
|
|
96
97
|
|
|
98
|
+
private constructor(
|
|
99
|
+
public readonly conf: PlClientConfig,
|
|
100
|
+
private readonly ops: {
|
|
101
|
+
auth?: AuthOps;
|
|
102
|
+
statusListener?: PlConnectionStatusListener;
|
|
103
|
+
shouldUseGzip?: boolean;
|
|
104
|
+
} = {},
|
|
105
|
+
) {
|
|
97
106
|
const { auth, statusListener } = ops;
|
|
98
107
|
|
|
99
108
|
if (auth !== undefined) {
|
|
@@ -120,8 +129,11 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
120
129
|
this._grpcInterceptors.push(this.createGrpcErrorInterceptor());
|
|
121
130
|
|
|
122
131
|
this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);
|
|
132
|
+
if (this.conf.wireProtocol) {
|
|
133
|
+
this._wireProto = this.conf.wireProtocol;
|
|
134
|
+
}
|
|
123
135
|
|
|
124
|
-
this.initWireConnection();
|
|
136
|
+
this.initWireConnection(this._wireProto);
|
|
125
137
|
|
|
126
138
|
if (statusListener !== undefined) {
|
|
127
139
|
this.statusListener = statusListener;
|
|
@@ -142,13 +154,8 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
142
154
|
});
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
private initWireConnection() {
|
|
146
|
-
|
|
147
|
-
// TODO: implement automatic server mode detection
|
|
148
|
-
this._wireProto = this.conf.wireProtocol ?? 'grpc';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
switch (this._wireProto) {
|
|
157
|
+
private initWireConnection(protocol: wireProtocol) {
|
|
158
|
+
switch (protocol) {
|
|
152
159
|
case 'rest':
|
|
153
160
|
this.initRestConnection();
|
|
154
161
|
return;
|
|
@@ -158,7 +165,7 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
158
165
|
default:
|
|
159
166
|
((v: never) => {
|
|
160
167
|
throw new Error(`Unsupported wire protocol '${v as string}'. Use one of: ${SUPPORTED_WIRE_PROTOCOLS.join(', ')}`);
|
|
161
|
-
})(
|
|
168
|
+
})(protocol);
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
|
|
@@ -221,6 +228,7 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
221
228
|
private _replaceWireConnection(newConn: WireConnection): void {
|
|
222
229
|
const oldConn = this._wireConn;
|
|
223
230
|
this._wireConn = newConn;
|
|
231
|
+
this._wireProto = newConn.type;
|
|
224
232
|
|
|
225
233
|
// Reset all providers to let them reinitialize their clients
|
|
226
234
|
for (let i = 0; i < this.providers.length; i++) {
|
|
@@ -269,6 +277,10 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
269
277
|
return this._wireConn;
|
|
270
278
|
}
|
|
271
279
|
|
|
280
|
+
public get wireProtocol(): wireProtocol | undefined {
|
|
281
|
+
return this._wireProto;
|
|
282
|
+
}
|
|
283
|
+
|
|
272
284
|
/** Returns true if client is authenticated. Even with anonymous auth information
|
|
273
285
|
* connection is considered authenticated. Unauthenticated clients are used for
|
|
274
286
|
* login and similar tasks, see {@link UnauthenticatedPlClient}. */
|
|
@@ -444,6 +456,32 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
444
456
|
}
|
|
445
457
|
}
|
|
446
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Detects the best available wire protocol.
|
|
461
|
+
* If wireProtocol is explicitly configured, does nothing.
|
|
462
|
+
* Otherwise probes the current protocol via ping; if it fails, switches to the alternative.
|
|
463
|
+
*/
|
|
464
|
+
private async detectOptimalWireProtocol() {
|
|
465
|
+
if (this.conf.wireProtocol) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const retryOptions: RetryOptions = {
|
|
470
|
+
type: 'exponentialBackoff',
|
|
471
|
+
maxAttempts: 80,
|
|
472
|
+
initialDelay: 30,
|
|
473
|
+
backoffMultiplier: 1.3,
|
|
474
|
+
jitter: 0.2,
|
|
475
|
+
maxDelay: 500,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
await retry(() => withTimeout(this.ping(), 500), retryOptions, () => {
|
|
479
|
+
const protocol = this._wireProto === 'grpc' ? 'rest' : 'grpc';
|
|
480
|
+
this.initWireConnection(protocol);
|
|
481
|
+
return true;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
447
485
|
public async license(): Promise<grpcTypes.MaintenanceAPI_License_Response> {
|
|
448
486
|
const cl = this.clientProvider.get();
|
|
449
487
|
if (cl instanceof GrpcPlApiClient) {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { UnauthenticatedPlClient } from './unauth_client';
|
|
2
|
-
import { getTestConfig } from '../test/test_config';
|
|
2
|
+
import { getTestConfig, plAddressToTestConfig } from '../test/test_config';
|
|
3
3
|
import { UnauthenticatedError } from './errors';
|
|
4
4
|
import { test, expect } from 'vitest';
|
|
5
5
|
|
|
6
6
|
test('ping test', async () => {
|
|
7
|
-
const client =
|
|
7
|
+
const client = await UnauthenticatedPlClient.build(plAddressToTestConfig(getTestConfig().address));
|
|
8
8
|
const response = await client.ping();
|
|
9
9
|
expect(response).toHaveProperty('coreVersion');
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
test('get auth methods', async () => {
|
|
13
|
-
const client =
|
|
13
|
+
const client = await UnauthenticatedPlClient.build(plAddressToTestConfig(getTestConfig().address));
|
|
14
14
|
const response = await client.authMethods();
|
|
15
15
|
expect(response).toHaveProperty('methods');
|
|
16
16
|
});
|
|
@@ -21,7 +21,7 @@ test('wrong login', async () => {
|
|
|
21
21
|
console.log('skipped');
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
const client =
|
|
24
|
+
const client = await UnauthenticatedPlClient.build(plAddressToTestConfig(testConfig.address));
|
|
25
25
|
await expect(client.login(testConfig.test_user, testConfig.test_password + 'A')).rejects.toThrow(
|
|
26
26
|
UnauthenticatedError
|
|
27
27
|
);
|
|
@@ -11,8 +11,13 @@ import { UnauthenticatedError } from './errors';
|
|
|
11
11
|
export class UnauthenticatedPlClient {
|
|
12
12
|
public readonly ll: LLPlClient;
|
|
13
13
|
|
|
14
|
-
constructor(
|
|
15
|
-
this.ll =
|
|
14
|
+
private constructor(ll: LLPlClient) {
|
|
15
|
+
this.ll = ll;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static async build(configOrAddress: PlClientConfig | string): Promise<UnauthenticatedPlClient> {
|
|
19
|
+
const ll = await LLPlClient.build(configOrAddress);
|
|
20
|
+
return new UnauthenticatedPlClient(ll);
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
public async ping(): Promise<MaintenanceAPI_Ping_Response> {
|
|
@@ -3,6 +3,7 @@ import type { BiDiStream } from './abstract_stream';
|
|
|
3
3
|
import Denque from 'denque';
|
|
4
4
|
import type { RetryConfig } from '../helpers/retry_strategy';
|
|
5
5
|
import { RetryStrategy } from '../helpers/retry_strategy';
|
|
6
|
+
import { DisconnectedError } from './errors';
|
|
6
7
|
|
|
7
8
|
interface QueuedMessage<InType extends object> {
|
|
8
9
|
message: InType;
|
|
@@ -183,6 +184,18 @@ export class WebSocketBiDiStream<ClientMsg extends object, ServerMsg extends obj
|
|
|
183
184
|
private onClose(): void {
|
|
184
185
|
this.progressConnectionState(ConnectionState.CLOSED);
|
|
185
186
|
|
|
187
|
+
// If abort signal was triggered, use that as the error source
|
|
188
|
+
if (this.options.abortSignal?.aborted && !this.lastError) {
|
|
189
|
+
const reason = this.options.abortSignal.reason;
|
|
190
|
+
if (reason instanceof Error) {
|
|
191
|
+
this.lastError = reason;
|
|
192
|
+
} else if (reason !== undefined) {
|
|
193
|
+
this.lastError = new Error(String(reason), { cause: reason });
|
|
194
|
+
} else {
|
|
195
|
+
this.lastError = this.createStreamClosedError();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
186
199
|
if (!this.lastError) {
|
|
187
200
|
this.rejectAllSendOperations(this.createStreamClosedError());
|
|
188
201
|
this.resolveAllPendingResponses(); // unblock active async iterator
|
|
@@ -380,7 +393,15 @@ export class WebSocketBiDiStream<ClientMsg extends object, ServerMsg extends obj
|
|
|
380
393
|
|
|
381
394
|
private toError(error: unknown): Error {
|
|
382
395
|
if (error instanceof Error) return error;
|
|
383
|
-
if (error instanceof ErrorEvent)
|
|
396
|
+
if (error instanceof ErrorEvent) {
|
|
397
|
+
const err = error.error;
|
|
398
|
+
// undici WebSocket throws TypeError with empty message on socket close
|
|
399
|
+
// (e.g., when connection is lost or server disconnects)
|
|
400
|
+
if (err instanceof TypeError && !err.message) {
|
|
401
|
+
return new DisconnectedError('WebSocket connection closed unexpectedly');
|
|
402
|
+
}
|
|
403
|
+
return err instanceof Error ? err : new Error('WebSocket error', { cause: error });
|
|
404
|
+
}
|
|
384
405
|
return new Error(String(error));
|
|
385
406
|
}
|
|
386
407
|
|
package/src/test/test_config.ts
CHANGED
|
@@ -54,6 +54,16 @@ export function getTestConfig(): TestConfig {
|
|
|
54
54
|
return conf as TestConfig;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Default request timeout for tests (ms) */
|
|
58
|
+
export const TEST_REQUEST_TIMEOUT = 500;
|
|
59
|
+
|
|
60
|
+
/** Returns PlClientConfig with reduced timeout for tests */
|
|
61
|
+
export function plAddressToTestConfig(address: string): PlClientConfig {
|
|
62
|
+
const plConf = plAddressToConfig(address);
|
|
63
|
+
plConf.defaultRequestTimeout = TEST_REQUEST_TIMEOUT;
|
|
64
|
+
return plConf;
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
interface AuthCache {
|
|
58
68
|
/** To check if config changed */
|
|
59
69
|
conf: TestConfig;
|
|
@@ -109,9 +119,8 @@ export async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth:
|
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
const plConf =
|
|
113
|
-
|
|
114
|
-
const uClient = new UnauthenticatedPlClient(plConf);
|
|
122
|
+
const plConf = plAddressToTestConfig(tConf.address);
|
|
123
|
+
const uClient = await UnauthenticatedPlClient.build(plConf);
|
|
115
124
|
|
|
116
125
|
const requireAuth = await uClient.requireAuth();
|
|
117
126
|
|
|
@@ -147,7 +156,7 @@ export async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth:
|
|
|
147
156
|
|
|
148
157
|
export async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {
|
|
149
158
|
const { conf, auth } = await getTestClientConf();
|
|
150
|
-
return
|
|
159
|
+
return await LLPlClient.build({ ...conf, ...confOverrides }, { auth });
|
|
151
160
|
}
|
|
152
161
|
|
|
153
162
|
export async function getTestClient(
|
|
File without changes
|