@milaboratories/pl-client 2.16.26 → 2.16.28
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/README.md +2 -1
- package/dist/core/PromiseTracker.cjs +1 -3
- package/dist/core/PromiseTracker.cjs.map +1 -1
- package/dist/core/PromiseTracker.d.ts.map +1 -1
- package/dist/core/PromiseTracker.js +1 -3
- package/dist/core/PromiseTracker.js.map +1 -1
- package/dist/core/StatefulPromise.cjs +4 -4
- package/dist/core/StatefulPromise.cjs.map +1 -1
- package/dist/core/StatefulPromise.d.ts +1 -1
- package/dist/core/StatefulPromise.d.ts.map +1 -1
- package/dist/core/StatefulPromise.js +4 -4
- package/dist/core/StatefulPromise.js.map +1 -1
- package/dist/core/advisory_locks.cjs +1 -1
- package/dist/core/advisory_locks.cjs.map +1 -1
- package/dist/core/advisory_locks.js +1 -1
- package/dist/core/advisory_locks.js.map +1 -1
- package/dist/core/auth.cjs.map +1 -1
- package/dist/core/auth.d.ts +1 -1
- package/dist/core/auth.js.map +1 -1
- package/dist/core/cache.d.ts +1 -1
- package/dist/core/client.cjs +14 -14
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +11 -11
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +14 -14
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.cjs +39 -39
- package/dist/core/config.cjs.map +1 -1
- package/dist/core/config.d.ts +5 -5
- package/dist/core/config.js +39 -39
- package/dist/core/config.js.map +1 -1
- package/dist/core/default_client.cjs +23 -23
- package/dist/core/default_client.cjs.map +1 -1
- package/dist/core/default_client.d.ts +3 -3
- package/dist/core/default_client.js +23 -23
- package/dist/core/default_client.js.map +1 -1
- package/dist/core/driver.cjs +1 -1
- package/dist/core/driver.cjs.map +1 -1
- package/dist/core/driver.d.ts +5 -5
- package/dist/core/driver.js +1 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/error_resource.cjs +2 -2
- package/dist/core/error_resource.cjs.map +1 -1
- package/dist/core/error_resource.d.ts +1 -1
- package/dist/core/error_resource.js +2 -2
- package/dist/core/error_resource.js.map +1 -1
- package/dist/core/errors.cjs +24 -24
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +24 -24
- package/dist/core/errors.js.map +1 -1
- package/dist/core/final.cjs +43 -43
- package/dist/core/final.cjs.map +1 -1
- package/dist/core/final.d.ts +3 -3
- package/dist/core/final.d.ts.map +1 -1
- package/dist/core/final.js +43 -43
- package/dist/core/final.js.map +1 -1
- package/dist/core/ll_client.cjs +50 -43
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +9 -9
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +50 -43
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/ll_transaction.cjs +9 -9
- package/dist/core/ll_transaction.cjs.map +1 -1
- package/dist/core/ll_transaction.d.ts +7 -7
- package/dist/core/ll_transaction.d.ts.map +1 -1
- package/dist/core/ll_transaction.js +9 -9
- package/dist/core/ll_transaction.js.map +1 -1
- package/dist/core/stat.cjs.map +1 -1
- package/dist/core/stat.d.ts +1 -1
- package/dist/core/stat.js.map +1 -1
- package/dist/core/transaction.cjs +46 -46
- package/dist/core/transaction.cjs.map +1 -1
- package/dist/core/transaction.d.ts +7 -7
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +46 -46
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/type_conversion.cjs +22 -22
- package/dist/core/type_conversion.cjs.map +1 -1
- package/dist/core/type_conversion.d.ts +3 -3
- package/dist/core/type_conversion.d.ts.map +1 -1
- package/dist/core/type_conversion.js +22 -22
- package/dist/core/type_conversion.js.map +1 -1
- package/dist/core/types.cjs +25 -25
- package/dist/core/types.cjs.map +1 -1
- package/dist/core/types.d.ts +7 -7
- package/dist/core/types.js +25 -25
- package/dist/core/types.js.map +1 -1
- package/dist/core/unauth_client.cjs +6 -4
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +4 -4
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +6 -4
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/core/websocket_stream.cjs +22 -20
- package/dist/core/websocket_stream.cjs.map +1 -1
- package/dist/core/websocket_stream.d.ts +3 -3
- package/dist/core/websocket_stream.d.ts.map +1 -1
- package/dist/core/websocket_stream.js +22 -20
- package/dist/core/websocket_stream.js.map +1 -1
- package/dist/core/wire.d.ts +6 -6
- package/dist/core/wire.d.ts.map +1 -1
- package/dist/helpers/pl.cjs +19 -19
- package/dist/helpers/pl.cjs.map +1 -1
- package/dist/helpers/pl.d.ts +2 -2
- package/dist/helpers/pl.js +19 -19
- package/dist/helpers/pl.js.map +1 -1
- package/dist/helpers/poll.cjs +6 -6
- package/dist/helpers/poll.cjs.map +1 -1
- package/dist/helpers/poll.d.ts +4 -4
- package/dist/helpers/poll.d.ts.map +1 -1
- package/dist/helpers/poll.js +6 -6
- package/dist/helpers/poll.js.map +1 -1
- package/dist/helpers/retry_strategy.cjs +1 -1
- package/dist/helpers/retry_strategy.cjs.map +1 -1
- package/dist/helpers/retry_strategy.d.ts.map +1 -1
- package/dist/helpers/retry_strategy.js +1 -1
- package/dist/helpers/retry_strategy.js.map +1 -1
- package/dist/helpers/state_helpers.d.ts +2 -2
- package/dist/helpers/tx_helpers.cjs +2 -2
- package/dist/helpers/tx_helpers.cjs.map +1 -1
- package/dist/helpers/tx_helpers.d.ts +2 -2
- package/dist/helpers/tx_helpers.d.ts.map +1 -1
- package/dist/helpers/tx_helpers.js +2 -2
- package/dist/helpers/tx_helpers.js.map +1 -1
- package/dist/index.d.ts +16 -16
- package/dist/proto-grpc/google/protobuf/struct.d.ts +1 -1
- package/dist/proto-grpc/google/protobuf/struct.d.ts.map +1 -1
- package/dist/proto-rest/index.cjs +4 -5
- package/dist/proto-rest/index.cjs.map +1 -1
- package/dist/proto-rest/index.d.ts +4 -4
- package/dist/proto-rest/index.d.ts.map +1 -1
- package/dist/proto-rest/index.js +4 -5
- package/dist/proto-rest/index.js.map +1 -1
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/dist/test/tcp-proxy.cjs +11 -10
- package/dist/test/tcp-proxy.cjs.map +1 -1
- package/dist/test/tcp-proxy.d.ts +1 -1
- package/dist/test/tcp-proxy.d.ts.map +1 -1
- package/dist/test/tcp-proxy.js +11 -10
- package/dist/test/tcp-proxy.js.map +1 -1
- package/dist/test/test_config.cjs +21 -17
- package/dist/test/test_config.cjs.map +1 -1
- package/dist/test/test_config.d.ts +6 -6
- package/dist/test/test_config.d.ts.map +1 -1
- package/dist/test/test_config.js +21 -17
- package/dist/test/test_config.js.map +1 -1
- package/dist/util/pl.cjs +1 -1
- package/dist/util/pl.cjs.map +1 -1
- package/dist/util/pl.js +1 -1
- package/dist/util/pl.js.map +1 -1
- package/dist/util/util.cjs +1 -1
- package/dist/util/util.cjs.map +1 -1
- package/dist/util/util.js +1 -1
- package/dist/util/util.js.map +1 -1
- package/package.json +23 -23
- package/src/core/PromiseTracker.ts +3 -4
- package/src/core/StatefulPromise.ts +17 -8
- package/src/core/abstract_stream.ts +3 -4
- package/src/core/advisory_locks.ts +1 -1
- package/src/core/auth.ts +2 -2
- package/src/core/cache.ts +1 -1
- package/src/core/client.test.ts +25 -21
- package/src/core/client.ts +54 -45
- package/src/core/config.test.ts +44 -44
- package/src/core/config.ts +49 -49
- package/src/core/connectivity.test.ts +69 -63
- package/src/core/default_client.ts +46 -46
- package/src/core/driver.ts +6 -6
- package/src/core/error.test.ts +5 -5
- package/src/core/error_resource.ts +3 -3
- package/src/core/errors.ts +39 -31
- package/src/core/final.ts +48 -55
- package/src/core/ll_client.test.ts +53 -36
- package/src/core/ll_client.ts +125 -81
- package/src/core/ll_transaction.test.ts +75 -49
- package/src/core/ll_transaction.ts +37 -35
- package/src/core/stat.ts +1 -1
- package/src/core/transaction.test.ts +65 -65
- package/src/core/transaction.ts +91 -84
- package/src/core/type_conversion.ts +30 -31
- package/src/core/types.test.ts +6 -6
- package/src/core/types.ts +35 -35
- package/src/core/unauth_client.test.ts +18 -14
- package/src/core/unauth_client.ts +14 -12
- package/src/core/websocket_stream.test.ts +52 -52
- package/src/core/websocket_stream.ts +41 -37
- package/src/core/wire.ts +10 -8
- package/src/helpers/pl.ts +22 -22
- package/src/helpers/poll.ts +13 -27
- package/src/helpers/retry_strategy.ts +2 -4
- package/src/helpers/rich_resource_types.test.ts +2 -2
- package/src/helpers/state_helpers.ts +3 -3
- package/src/helpers/tx_helpers.ts +9 -7
- package/src/index.ts +16 -16
- package/src/proto-grpc/google/protobuf/struct.ts +1 -1
- package/src/proto-rest/index.ts +17 -18
- package/src/proto-rest/plapi.ts +1472 -1472
- package/src/test/tcp-proxy.ts +55 -54
- package/src/test/test_config.test.ts +3 -3
- package/src/test/test_config.ts +51 -46
- package/src/util/pl.ts +1 -1
- package/src/util/util.test.ts +5 -5
- package/src/util/util.ts +1 -1
- package/dist/helpers/rich_resource_types.d.ts +0 -2
- package/dist/helpers/rich_resource_types.d.ts.map +0 -1
- package/dist/helpers/smart_accessors.d.ts +0 -2
- package/dist/helpers/smart_accessors.d.ts.map +0 -1
- package/src/helpers/rich_resource_types.ts +0 -84
- package/src/helpers/smart_accessors.ts +0 -146
package/src/core/types.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { expect, test } from
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
createGlobalResourceId,
|
|
4
4
|
createLocalResourceId,
|
|
5
5
|
NullResourceId,
|
|
6
6
|
resourceIdFromString,
|
|
7
|
-
resourceIdToString
|
|
8
|
-
} from
|
|
7
|
+
resourceIdToString,
|
|
8
|
+
} from "./types";
|
|
9
9
|
|
|
10
10
|
test.each(
|
|
11
11
|
[
|
|
@@ -13,11 +13,11 @@ test.each(
|
|
|
13
13
|
createGlobalResourceId(true, 0x3457748574857n),
|
|
14
14
|
createGlobalResourceId(false, 0x3457748574857n),
|
|
15
15
|
createLocalResourceId(true, 1234, 34423),
|
|
16
|
-
createLocalResourceId(false, 1234, 34423)
|
|
16
|
+
createLocalResourceId(false, 1234, 34423),
|
|
17
17
|
].map((rid) => ({
|
|
18
18
|
name: resourceIdToString(rid),
|
|
19
|
-
rid
|
|
20
|
-
}))
|
|
19
|
+
rid,
|
|
20
|
+
})),
|
|
21
21
|
)(`resource id to and from string: $name`, ({ rid }) => {
|
|
22
22
|
expect(resourceIdFromString(resourceIdToString(rid))).toEqual(rid);
|
|
23
23
|
});
|
package/src/core/types.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { cachedDeserialize, notEmpty } from
|
|
1
|
+
import { cachedDeserialize, notEmpty } from "@milaboratories/ts-helpers";
|
|
2
2
|
|
|
3
3
|
// more details here: https://egghead.io/blog/using-branded-types-in-typescript
|
|
4
4
|
declare const __resource_id_type__: unique symbol;
|
|
5
5
|
type BrandResourceId<B> = bigint & { [__resource_id_type__]: B };
|
|
6
6
|
|
|
7
7
|
/** Global resource id */
|
|
8
|
-
export type ResourceId = BrandResourceId<
|
|
8
|
+
export type ResourceId = BrandResourceId<"global">;
|
|
9
9
|
|
|
10
10
|
/** Null resource id */
|
|
11
|
-
export type NullResourceId = BrandResourceId<
|
|
11
|
+
export type NullResourceId = BrandResourceId<"null">;
|
|
12
12
|
|
|
13
13
|
/** Local resource id */
|
|
14
|
-
export type LocalResourceId = BrandResourceId<
|
|
14
|
+
export type LocalResourceId = BrandResourceId<"local">;
|
|
15
15
|
|
|
16
16
|
/** Any non-null resource id */
|
|
17
17
|
export type AnyResourceId = ResourceId | LocalResourceId;
|
|
@@ -33,7 +33,7 @@ export function isNotNullResourceId(resourceId: OptionalResourceId): resourceId
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function ensureResourceIdNotNull(resourceId: OptionalResourceId): ResourceId {
|
|
36
|
-
if (!isNotNullResourceId(resourceId)) throw new Error(
|
|
36
|
+
if (!isNotNullResourceId(resourceId)) throw new Error("null resource id");
|
|
37
37
|
return resourceId;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -43,13 +43,13 @@ export function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId
|
|
|
43
43
|
|
|
44
44
|
// see local / global resource logic below...
|
|
45
45
|
|
|
46
|
-
export type ResourceKind =
|
|
46
|
+
export type ResourceKind = "Structural" | "Value";
|
|
47
47
|
|
|
48
|
-
export type FieldType =
|
|
48
|
+
export type FieldType = "Input" | "Output" | "Service" | "OTW" | "Dynamic" | "MTW";
|
|
49
49
|
|
|
50
|
-
export type FutureFieldType =
|
|
50
|
+
export type FutureFieldType = "Output" | "Input" | "Service";
|
|
51
51
|
|
|
52
|
-
export type FieldStatus =
|
|
52
|
+
export type FieldStatus = "Empty" | "Assigned" | "Resolved";
|
|
53
53
|
|
|
54
54
|
export interface ResourceType {
|
|
55
55
|
readonly name: string;
|
|
@@ -174,16 +174,16 @@ export function createLocalResourceId(
|
|
|
174
174
|
localTxId: number,
|
|
175
175
|
): LocalResourceId {
|
|
176
176
|
if (
|
|
177
|
-
localCounterValue > MaxLocalId
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
localCounterValue > MaxLocalId ||
|
|
178
|
+
localTxId > MaxTxId ||
|
|
179
|
+
localCounterValue < 0 ||
|
|
180
|
+
localTxId <= 0
|
|
181
181
|
)
|
|
182
|
-
throw Error(
|
|
183
|
-
return ((isRoot ? ResourceIdRootMask : 0n)
|
|
184
|
-
|
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
throw Error("wrong local id or tx id");
|
|
183
|
+
return ((isRoot ? ResourceIdRootMask : 0n) |
|
|
184
|
+
ResourceIdLocalMask |
|
|
185
|
+
BigInt(localCounterValue) |
|
|
186
|
+
(BigInt(localTxId) << LocalResourceIdTxIdOffset)) as LocalResourceId;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
export function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): ResourceId {
|
|
@@ -198,40 +198,40 @@ export function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxI
|
|
|
198
198
|
if (!isLocalResourceId(resourceId)) return;
|
|
199
199
|
if (extractTxId(resourceId) !== expectedTxId)
|
|
200
200
|
throw Error(
|
|
201
|
-
|
|
201
|
+
"local id from another transaction, globalize id before leaking it from the transaction",
|
|
202
202
|
);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
export function resourceIdToString(resourceId: OptionalAnyResourceId): string {
|
|
206
|
-
if (isNullResourceId(resourceId)) return
|
|
206
|
+
if (isNullResourceId(resourceId)) return "XX:0x0";
|
|
207
207
|
if (isLocalResourceId(resourceId))
|
|
208
208
|
return (
|
|
209
|
-
(isRootResourceId(resourceId) ?
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
(isRootResourceId(resourceId) ? "R" : "N") +
|
|
210
|
+
"L:0x" +
|
|
211
|
+
(LocalIdMask & resourceId).toString(16) +
|
|
212
|
+
"[0x" +
|
|
213
|
+
extractTxId(resourceId).toString(16) +
|
|
214
|
+
"]"
|
|
215
215
|
);
|
|
216
216
|
else
|
|
217
217
|
return (
|
|
218
|
-
(isRootResourceId(resourceId) ?
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
(isRootResourceId(resourceId) ? "R" : "N") +
|
|
219
|
+
"G:0x" +
|
|
220
|
+
(NoFlagsIdMask & resourceId).toString(16)
|
|
221
221
|
);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
const resourceIdRegexp
|
|
225
|
-
|
|
224
|
+
const resourceIdRegexp =
|
|
225
|
+
/^(?:(?<xx>XX)|(?<rn>[XRN])(?<lg>[XLG])):0x(?<rid>[0-9a-fA-F]+)(?:\[0x(?<txid>[0-9a-fA-F]+)])?$/;
|
|
226
226
|
|
|
227
227
|
export function resourceIdFromString(str: string): OptionalAnyResourceId | undefined {
|
|
228
228
|
const match = str.match(resourceIdRegexp);
|
|
229
229
|
if (match === null) return undefined;
|
|
230
230
|
const { xx, rn, lg, rid, txid } = match.groups!;
|
|
231
231
|
if (xx) return NullResourceId;
|
|
232
|
-
if (lg ===
|
|
233
|
-
return createLocalResourceId(rn ===
|
|
234
|
-
else return createGlobalResourceId(rn ===
|
|
232
|
+
if (lg === "L")
|
|
233
|
+
return createLocalResourceId(rn === "R", Number.parseInt(rid, 16), Number.parseInt(txid, 16));
|
|
234
|
+
else return createGlobalResourceId(rn === "R", BigInt("0x" + rid));
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/** Converts bigint to global resource id */
|
|
@@ -244,6 +244,6 @@ export function bigintToResourceId(resourceId: bigint): ResourceId {
|
|
|
244
244
|
|
|
245
245
|
export function stringifyWithResourceId(object: unknown): string {
|
|
246
246
|
return JSON.stringify(object, (key, value) =>
|
|
247
|
-
typeof value ===
|
|
247
|
+
typeof value === "bigint" ? resourceIdToString(value as OptionalAnyResourceId) : value,
|
|
248
248
|
);
|
|
249
249
|
}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import { UnauthenticatedPlClient } from
|
|
2
|
-
import { getTestConfig, plAddressToTestConfig } from
|
|
3
|
-
import { UnauthenticatedError } from
|
|
4
|
-
import { test, expect } from
|
|
1
|
+
import { UnauthenticatedPlClient } from "./unauth_client";
|
|
2
|
+
import { getTestConfig, plAddressToTestConfig } from "../test/test_config";
|
|
3
|
+
import { UnauthenticatedError } from "./errors";
|
|
4
|
+
import { test, expect } from "vitest";
|
|
5
5
|
|
|
6
|
-
test(
|
|
7
|
-
const client = await UnauthenticatedPlClient.build(
|
|
6
|
+
test("ping test", async () => {
|
|
7
|
+
const client = await UnauthenticatedPlClient.build(
|
|
8
|
+
plAddressToTestConfig(getTestConfig().address),
|
|
9
|
+
);
|
|
8
10
|
const response = await client.ping();
|
|
9
|
-
expect(response).toHaveProperty(
|
|
11
|
+
expect(response).toHaveProperty("coreVersion");
|
|
10
12
|
});
|
|
11
13
|
|
|
12
|
-
test(
|
|
13
|
-
const client = await UnauthenticatedPlClient.build(
|
|
14
|
+
test("get auth methods", async () => {
|
|
15
|
+
const client = await UnauthenticatedPlClient.build(
|
|
16
|
+
plAddressToTestConfig(getTestConfig().address),
|
|
17
|
+
);
|
|
14
18
|
const response = await client.authMethods();
|
|
15
|
-
expect(response).toHaveProperty(
|
|
19
|
+
expect(response).toHaveProperty("methods");
|
|
16
20
|
});
|
|
17
21
|
|
|
18
|
-
test(
|
|
22
|
+
test("wrong login", async () => {
|
|
19
23
|
const testConfig = getTestConfig();
|
|
20
24
|
if (testConfig.test_user === undefined || testConfig.test_password === undefined) {
|
|
21
|
-
console.log(
|
|
25
|
+
console.log("skipped");
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
24
28
|
const client = await UnauthenticatedPlClient.build(plAddressToTestConfig(testConfig.address));
|
|
25
|
-
await expect(client.login(testConfig.test_user, testConfig.test_password +
|
|
26
|
-
UnauthenticatedError
|
|
29
|
+
await expect(client.login(testConfig.test_user, testConfig.test_password + "A")).rejects.toThrow(
|
|
30
|
+
UnauthenticatedError,
|
|
27
31
|
);
|
|
28
32
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AuthInformation, PlClientConfig } from
|
|
1
|
+
import type { AuthInformation, PlClientConfig } from "./config";
|
|
2
2
|
import type {
|
|
3
3
|
AuthAPI_ListMethods_Response,
|
|
4
4
|
MaintenanceAPI_Ping_Response,
|
|
5
|
-
} from
|
|
6
|
-
import { LLPlClient } from
|
|
7
|
-
import { type MiLogger, notEmpty } from
|
|
8
|
-
import { UnauthenticatedError } from
|
|
5
|
+
} from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api";
|
|
6
|
+
import { LLPlClient } from "./ll_client";
|
|
7
|
+
import { type MiLogger, notEmpty } from "@milaboratories/ts-helpers";
|
|
8
|
+
import { UnauthenticatedError } from "./errors";
|
|
9
9
|
|
|
10
10
|
/** Primarily used for initial authentication (login) */
|
|
11
11
|
export class UnauthenticatedPlClient {
|
|
@@ -15,7 +15,10 @@ export class UnauthenticatedPlClient {
|
|
|
15
15
|
this.ll = ll;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
public static async build(
|
|
18
|
+
public static async build(
|
|
19
|
+
configOrAddress: PlClientConfig | string,
|
|
20
|
+
ops?: { logger?: MiLogger },
|
|
21
|
+
): Promise<UnauthenticatedPlClient> {
|
|
19
22
|
const ll = await LLPlClient.build(configOrAddress, ops);
|
|
20
23
|
return new UnauthenticatedPlClient(ll);
|
|
21
24
|
}
|
|
@@ -34,15 +37,14 @@ export class UnauthenticatedPlClient {
|
|
|
34
37
|
|
|
35
38
|
public async login(user: string, password: string): Promise<AuthInformation> {
|
|
36
39
|
try {
|
|
37
|
-
const token = await this.ll.getJwtToken(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
40
|
+
const token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {
|
|
41
|
+
authorization: "Basic " + Buffer.from(user + ":" + password).toString("base64"),
|
|
42
|
+
});
|
|
41
43
|
const jwtToken = notEmpty(token);
|
|
42
|
-
if (jwtToken ===
|
|
44
|
+
if (jwtToken === "") throw new Error("empty token");
|
|
43
45
|
return { jwtToken };
|
|
44
46
|
} catch (e: any) {
|
|
45
|
-
if (e.code ===
|
|
47
|
+
if (e.code === "UNAUTHENTICATED") throw new UnauthenticatedError(e.message);
|
|
46
48
|
throw new Error(e);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe, test, expect, vi, beforeEach, afterEach } from
|
|
1
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
TxAPI_ClientMessage as ClientMessageType,
|
|
4
4
|
TxAPI_ServerMessage as ServerMessageType,
|
|
5
|
-
} from
|
|
5
|
+
} from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api";
|
|
6
6
|
|
|
7
7
|
// Mock WebSocket - must be hoisted for vi.mock
|
|
8
8
|
const MockWebSocket = vi.hoisted(() => {
|
|
@@ -13,7 +13,7 @@ const MockWebSocket = vi.hoisted(() => {
|
|
|
13
13
|
static CLOSED = 3;
|
|
14
14
|
|
|
15
15
|
readyState = 0;
|
|
16
|
-
binaryType =
|
|
16
|
+
binaryType = "blob";
|
|
17
17
|
|
|
18
18
|
private listeners: Map<string, Set<Function>> = new Map();
|
|
19
19
|
|
|
@@ -48,36 +48,36 @@ const MockWebSocket = vi.hoisted(() => {
|
|
|
48
48
|
send = vi.fn();
|
|
49
49
|
close = vi.fn(() => {
|
|
50
50
|
this.readyState = MockWS.CLOSED;
|
|
51
|
-
this.emit(
|
|
51
|
+
this.emit("close");
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
simulateOpen() {
|
|
55
55
|
this.readyState = MockWS.OPEN;
|
|
56
|
-
this.emit(
|
|
56
|
+
this.emit("open");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
simulateMessage(data: ArrayBuffer) {
|
|
60
|
-
this.emit(
|
|
60
|
+
this.emit("message", { data });
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
simulateError(error: Error) {
|
|
64
|
-
this.emit(
|
|
64
|
+
this.emit("error", error);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
simulateClose() {
|
|
68
68
|
this.readyState = MockWS.CLOSED;
|
|
69
|
-
this.emit(
|
|
69
|
+
this.emit("close");
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
return MockWS;
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
vi.mock(
|
|
75
|
+
vi.mock("undici", () => ({
|
|
76
76
|
WebSocket: MockWebSocket,
|
|
77
77
|
}));
|
|
78
78
|
|
|
79
|
-
import { WebSocketBiDiStream } from
|
|
80
|
-
import type { RetryConfig } from
|
|
79
|
+
import { WebSocketBiDiStream } from "./websocket_stream";
|
|
80
|
+
import type { RetryConfig } from "../helpers/retry_strategy";
|
|
81
81
|
|
|
82
82
|
type MockWS = InstanceType<typeof MockWebSocket>;
|
|
83
83
|
|
|
@@ -90,7 +90,7 @@ interface StreamContext {
|
|
|
90
90
|
function createStream(token?: string, retryConfig?: Partial<RetryConfig>): StreamContext {
|
|
91
91
|
const controller = new AbortController();
|
|
92
92
|
const stream = new WebSocketBiDiStream(
|
|
93
|
-
|
|
93
|
+
"ws://localhost:8080",
|
|
94
94
|
(message: ClientMessageType) => ClientMessageType.toBinary(message),
|
|
95
95
|
(data) => ServerMessageType.fromBinary(data),
|
|
96
96
|
{
|
|
@@ -111,7 +111,10 @@ async function openConnection(ws: MockWS): Promise<void> {
|
|
|
111
111
|
function createServerMessageBuffer(): ArrayBuffer {
|
|
112
112
|
const message = ServerMessageType.create({});
|
|
113
113
|
const binary = ServerMessageType.toBinary(message);
|
|
114
|
-
return binary.buffer.slice(
|
|
114
|
+
return binary.buffer.slice(
|
|
115
|
+
binary.byteOffset,
|
|
116
|
+
binary.byteOffset + binary.byteLength,
|
|
117
|
+
) as ArrayBuffer;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
function createClientMessage(): ClientMessageType {
|
|
@@ -130,7 +133,7 @@ async function collectMessages(
|
|
|
130
133
|
return messages;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
describe(
|
|
136
|
+
describe("WebSocketBiDiStream", () => {
|
|
134
137
|
beforeEach(() => {
|
|
135
138
|
vi.useFakeTimers();
|
|
136
139
|
MockWebSocket.reset();
|
|
@@ -140,21 +143,19 @@ describe('WebSocketBiDiStream', () => {
|
|
|
140
143
|
vi.useRealTimers();
|
|
141
144
|
});
|
|
142
145
|
|
|
143
|
-
describe(
|
|
144
|
-
test(
|
|
145
|
-
createStream(
|
|
146
|
+
describe("constructor", () => {
|
|
147
|
+
test("should pass JWT token in authorization header", () => {
|
|
148
|
+
createStream("test-token");
|
|
146
149
|
|
|
147
|
-
expect(MockWebSocket.instances[0].options?.headers?.authorization).toBe(
|
|
148
|
-
'Bearer test-token',
|
|
149
|
-
);
|
|
150
|
+
expect(MockWebSocket.instances[0].options?.headers?.authorization).toBe("Bearer test-token");
|
|
150
151
|
});
|
|
151
152
|
|
|
152
|
-
test(
|
|
153
|
+
test("should not create WebSocket if already aborted", () => {
|
|
153
154
|
const controller = new AbortController();
|
|
154
155
|
controller.abort();
|
|
155
156
|
|
|
156
157
|
new WebSocketBiDiStream(
|
|
157
|
-
|
|
158
|
+
"ws://localhost:8080",
|
|
158
159
|
(message: ClientMessageType) => ClientMessageType.toBinary(message),
|
|
159
160
|
(data) => ServerMessageType.fromBinary(data),
|
|
160
161
|
{
|
|
@@ -166,8 +167,8 @@ describe('WebSocketBiDiStream', () => {
|
|
|
166
167
|
});
|
|
167
168
|
});
|
|
168
169
|
|
|
169
|
-
describe(
|
|
170
|
-
test(
|
|
170
|
+
describe("send messages", () => {
|
|
171
|
+
test("should queue message and send when connected", async () => {
|
|
171
172
|
const { stream, ws } = createStream();
|
|
172
173
|
|
|
173
174
|
const sendPromise = stream.requests.send(createClientMessage());
|
|
@@ -179,31 +180,31 @@ describe('WebSocketBiDiStream', () => {
|
|
|
179
180
|
expect(ws.send).toHaveBeenCalledTimes(1);
|
|
180
181
|
});
|
|
181
182
|
|
|
182
|
-
test(
|
|
183
|
+
test("should throw error when sending after complete", async () => {
|
|
183
184
|
const { stream, ws } = createStream();
|
|
184
185
|
|
|
185
186
|
await openConnection(ws);
|
|
186
187
|
await stream.requests.complete();
|
|
187
188
|
|
|
188
189
|
await expect(stream.requests.send(createClientMessage())).rejects.toThrow(
|
|
189
|
-
|
|
190
|
+
"Cannot send: stream already completed",
|
|
190
191
|
);
|
|
191
192
|
});
|
|
192
193
|
|
|
193
|
-
test(
|
|
194
|
+
test("should throw error when sending after abort", async () => {
|
|
194
195
|
const { stream, ws, controller } = createStream();
|
|
195
196
|
|
|
196
197
|
await openConnection(ws);
|
|
197
198
|
controller.abort();
|
|
198
199
|
|
|
199
200
|
await expect(stream.requests.send(createClientMessage())).rejects.toThrow(
|
|
200
|
-
|
|
201
|
+
"Cannot send: stream aborted",
|
|
201
202
|
);
|
|
202
203
|
});
|
|
203
204
|
});
|
|
204
205
|
|
|
205
|
-
describe(
|
|
206
|
-
test(
|
|
206
|
+
describe("receive messages", () => {
|
|
207
|
+
test("should receive messages via async iterator", async () => {
|
|
207
208
|
const { stream, ws } = createStream();
|
|
208
209
|
|
|
209
210
|
await openConnection(ws);
|
|
@@ -218,7 +219,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
218
219
|
expect(messages).toHaveLength(2);
|
|
219
220
|
});
|
|
220
221
|
|
|
221
|
-
test(
|
|
222
|
+
test("should buffer messages when no consumer", async () => {
|
|
222
223
|
const { stream, ws } = createStream();
|
|
223
224
|
|
|
224
225
|
await openConnection(ws);
|
|
@@ -231,7 +232,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
231
232
|
expect(messages).toHaveLength(2);
|
|
232
233
|
});
|
|
233
234
|
|
|
234
|
-
test(
|
|
235
|
+
test("should end iterator when stream completes", async () => {
|
|
235
236
|
const { stream, ws } = createStream();
|
|
236
237
|
|
|
237
238
|
await openConnection(ws);
|
|
@@ -251,8 +252,8 @@ describe('WebSocketBiDiStream', () => {
|
|
|
251
252
|
});
|
|
252
253
|
});
|
|
253
254
|
|
|
254
|
-
describe(
|
|
255
|
-
test(
|
|
255
|
+
describe("complete", () => {
|
|
256
|
+
test("should close WebSocket after complete", async () => {
|
|
256
257
|
const { stream, ws } = createStream();
|
|
257
258
|
|
|
258
259
|
await openConnection(ws);
|
|
@@ -261,7 +262,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
261
262
|
expect(ws.close).toHaveBeenCalled();
|
|
262
263
|
});
|
|
263
264
|
|
|
264
|
-
test(
|
|
265
|
+
test("should be idempotent", async () => {
|
|
265
266
|
const { stream, ws } = createStream();
|
|
266
267
|
|
|
267
268
|
await openConnection(ws);
|
|
@@ -272,7 +273,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
272
273
|
expect(ws.close).toHaveBeenCalledTimes(1);
|
|
273
274
|
});
|
|
274
275
|
|
|
275
|
-
test(
|
|
276
|
+
test("should drain send queue before closing", async () => {
|
|
276
277
|
const { stream, ws } = createStream();
|
|
277
278
|
|
|
278
279
|
const sendPromise1 = stream.requests.send(createClientMessage());
|
|
@@ -289,8 +290,8 @@ describe('WebSocketBiDiStream', () => {
|
|
|
289
290
|
});
|
|
290
291
|
});
|
|
291
292
|
|
|
292
|
-
describe(
|
|
293
|
-
test(
|
|
293
|
+
describe("abort signal", () => {
|
|
294
|
+
test("should close stream when aborted", async () => {
|
|
294
295
|
const { ws, controller } = createStream();
|
|
295
296
|
|
|
296
297
|
await openConnection(ws);
|
|
@@ -299,7 +300,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
299
300
|
expect(ws.close).toHaveBeenCalled();
|
|
300
301
|
});
|
|
301
302
|
|
|
302
|
-
test(
|
|
303
|
+
test("should reject pending sends when aborted", async () => {
|
|
303
304
|
const { stream, controller } = createStream();
|
|
304
305
|
|
|
305
306
|
const sendPromise = stream.requests.send(createClientMessage());
|
|
@@ -311,7 +312,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
311
312
|
await expect(sendPromise).rejects.toThrow();
|
|
312
313
|
});
|
|
313
314
|
|
|
314
|
-
test(
|
|
315
|
+
test("should end response iterator when aborted", async () => {
|
|
315
316
|
const { stream, ws, controller } = createStream();
|
|
316
317
|
|
|
317
318
|
await openConnection(ws);
|
|
@@ -336,38 +337,38 @@ describe('WebSocketBiDiStream', () => {
|
|
|
336
337
|
});
|
|
337
338
|
});
|
|
338
339
|
|
|
339
|
-
describe(
|
|
340
|
+
describe("reconnection", () => {
|
|
340
341
|
const retryConfig: Partial<RetryConfig> = {
|
|
341
342
|
initialDelay: 50,
|
|
342
343
|
maxDelay: 100,
|
|
343
344
|
maxAttempts: 5,
|
|
344
345
|
};
|
|
345
346
|
|
|
346
|
-
test(
|
|
347
|
+
test("should not attempt reconnection on unexpected close", async () => {
|
|
347
348
|
const { ws } = createStream(undefined, retryConfig);
|
|
348
349
|
|
|
349
350
|
await openConnection(ws);
|
|
350
351
|
|
|
351
352
|
ws.readyState = MockWebSocket.CLOSED;
|
|
352
|
-
ws.emit(
|
|
353
|
+
ws.emit("close");
|
|
353
354
|
await vi.advanceTimersByTimeAsync(150);
|
|
354
355
|
|
|
355
356
|
expect(MockWebSocket.instances.length).toBe(1);
|
|
356
357
|
});
|
|
357
358
|
|
|
358
|
-
test(
|
|
359
|
+
test("should stop reconnecting after max attempts", async () => {
|
|
359
360
|
createStream(undefined, { maxAttempts: 3, initialDelay: 10, maxDelay: 100 });
|
|
360
361
|
|
|
361
362
|
for (let i = 0; i < 5; i++) {
|
|
362
363
|
const ws = MockWebSocket.instances[MockWebSocket.instances.length - 1];
|
|
363
|
-
ws.simulateError(new Error(
|
|
364
|
+
ws.simulateError(new Error("Connection failed"));
|
|
364
365
|
await vi.advanceTimersByTimeAsync(200);
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
expect(MockWebSocket.instances.length).toBeLessThanOrEqual(4);
|
|
368
369
|
});
|
|
369
370
|
|
|
370
|
-
test(
|
|
371
|
+
test("should not reconnect after complete", async () => {
|
|
371
372
|
const { stream, ws } = createStream();
|
|
372
373
|
|
|
373
374
|
await openConnection(ws);
|
|
@@ -378,8 +379,8 @@ describe('WebSocketBiDiStream', () => {
|
|
|
378
379
|
});
|
|
379
380
|
});
|
|
380
381
|
|
|
381
|
-
describe(
|
|
382
|
-
test(
|
|
382
|
+
describe("error handling", () => {
|
|
383
|
+
test("should reject response iterator on parse error", async () => {
|
|
383
384
|
const { stream, ws } = createStream();
|
|
384
385
|
|
|
385
386
|
await openConnection(ws);
|
|
@@ -397,7 +398,7 @@ describe('WebSocketBiDiStream', () => {
|
|
|
397
398
|
await expect(nextPromise).rejects.toThrow();
|
|
398
399
|
});
|
|
399
400
|
|
|
400
|
-
test(
|
|
401
|
+
test("should throw on unsupported message format", async () => {
|
|
401
402
|
const { stream, ws } = createStream();
|
|
402
403
|
|
|
403
404
|
await openConnection(ws);
|
|
@@ -407,17 +408,16 @@ describe('WebSocketBiDiStream', () => {
|
|
|
407
408
|
for await (const _ of stream.responses) {
|
|
408
409
|
// Should not reach here
|
|
409
410
|
}
|
|
410
|
-
return
|
|
411
|
+
return "completed";
|
|
411
412
|
} catch (e) {
|
|
412
413
|
return e;
|
|
413
414
|
}
|
|
414
415
|
})();
|
|
415
416
|
|
|
416
|
-
ws.emit(
|
|
417
|
+
ws.emit("message", { data: "not a buffer" });
|
|
417
418
|
|
|
418
419
|
const result = await iteratorPromise;
|
|
419
420
|
expect(result).toBeInstanceOf(Error);
|
|
420
421
|
});
|
|
421
422
|
});
|
|
422
|
-
|
|
423
423
|
});
|