@milaboratories/pl-client 3.11.5 → 3.12.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/dist/core/PromiseTracker.cjs.map +1 -1
- package/dist/core/PromiseTracker.d.ts.map +1 -1
- package/dist/core/PromiseTracker.js.map +1 -1
- package/dist/core/StatefulPromise.cjs +2 -0
- package/dist/core/StatefulPromise.cjs.map +1 -1
- package/dist/core/StatefulPromise.js +2 -0
- package/dist/core/StatefulPromise.js.map +1 -1
- package/dist/core/abstract_stream.d.ts +1 -1
- package/dist/core/advisory_locks.cjs.map +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.map +1 -1
- package/dist/core/auth.js.map +1 -1
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/capabilities.cjs.map +1 -1
- package/dist/core/capabilities.d.ts +2 -3
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/client.cjs +23 -6
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +14 -4
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +22 -5
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.cjs.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/default_client.cjs +5 -5
- package/dist/core/default_client.cjs.map +1 -1
- package/dist/core/default_client.d.ts.map +1 -1
- package/dist/core/default_client.js.map +1 -1
- package/dist/core/driver.cjs.map +1 -1
- package/dist/core/driver.d.ts.map +1 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/error_resource.cjs.map +1 -1
- package/dist/core/error_resource.js.map +1 -1
- package/dist/core/errors.cjs +11 -11
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +11 -10
- package/dist/core/errors.js.map +1 -1
- package/dist/core/final.cjs +6 -2
- package/dist/core/final.cjs.map +1 -1
- package/dist/core/final.d.ts.map +1 -1
- package/dist/core/final.js +4 -1
- package/dist/core/final.js.map +1 -1
- package/dist/core/ll_client.cjs +60 -16
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +23 -7
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +62 -17
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/ll_transaction.cjs +3 -1
- package/dist/core/ll_transaction.cjs.map +1 -1
- package/dist/core/ll_transaction.d.ts +1 -1
- package/dist/core/ll_transaction.d.ts.map +1 -1
- package/dist/core/ll_transaction.js +2 -1
- package/dist/core/ll_transaction.js.map +1 -1
- package/dist/core/stat.cjs.map +1 -1
- package/dist/core/stat.js.map +1 -1
- package/dist/core/transaction.cjs +91 -4
- package/dist/core/transaction.cjs.map +1 -1
- package/dist/core/transaction.d.ts +52 -2
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +90 -5
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/tree_filter.cjs +73 -18
- package/dist/core/tree_filter.cjs.map +1 -1
- package/dist/core/tree_filter.d.ts +1 -1
- package/dist/core/tree_filter.d.ts.map +1 -1
- package/dist/core/tree_filter.js +72 -19
- package/dist/core/tree_filter.js.map +1 -1
- package/dist/core/type_conversion.cjs +19 -20
- package/dist/core/type_conversion.cjs.map +1 -1
- package/dist/core/type_conversion.js +19 -19
- package/dist/core/type_conversion.js.map +1 -1
- package/dist/core/types.cjs +2 -3
- package/dist/core/types.cjs.map +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/unauth_client.cjs +7 -6
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +9 -0
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +7 -5
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/core/user_resources.cjs +57 -1
- package/dist/core/user_resources.cjs.map +1 -1
- package/dist/core/user_resources.d.ts +55 -1
- package/dist/core/user_resources.d.ts.map +1 -1
- package/dist/core/user_resources.js +57 -0
- package/dist/core/user_resources.js.map +1 -1
- package/dist/core/websocket_stream.cjs +17 -20
- package/dist/core/websocket_stream.cjs.map +1 -1
- package/dist/core/websocket_stream.js +16 -19
- package/dist/core/websocket_stream.js.map +1 -1
- package/dist/core/wire.d.ts.map +1 -1
- package/dist/helpers/pl.cjs +25 -0
- package/dist/helpers/pl.cjs.map +1 -1
- package/dist/helpers/pl.d.ts +1 -1
- package/dist/helpers/pl.d.ts.map +1 -1
- package/dist/helpers/pl.js +1 -1
- package/dist/helpers/pl.js.map +1 -1
- package/dist/helpers/poll.cjs +5 -1
- package/dist/helpers/poll.cjs.map +1 -1
- package/dist/helpers/poll.d.ts.map +1 -1
- package/dist/helpers/poll.js +4 -0
- package/dist/helpers/poll.js.map +1 -1
- package/dist/helpers/retry_strategy.cjs +1 -0
- package/dist/helpers/retry_strategy.cjs.map +1 -1
- package/dist/helpers/retry_strategy.js +1 -1
- package/dist/helpers/retry_strategy.js.map +1 -1
- package/dist/helpers/tx_helpers.cjs +0 -1
- package/dist/helpers/tx_helpers.cjs.map +1 -1
- package/dist/helpers/tx_helpers.d.ts.map +1 -1
- package/dist/helpers/tx_helpers.js.map +1 -1
- package/dist/index.cjs +3 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs +0 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs.map +1 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +537 -83
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +11 -4
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +12 -6
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +11 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +112 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +336 -83
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs +23 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +10 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js +18 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs +1 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.cjs +1 -2
- package/dist/proto-grpc/google/protobuf/any.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.js +1 -1
- package/dist/proto-grpc/google/protobuf/any.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.cjs +0 -1
- package/dist/proto-grpc/google/protobuf/duration.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.cjs +0 -1
- package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
- package/dist/proto-grpc/google/rpc/code.d.ts.map +1 -1
- package/dist/proto-rest/index.cjs +1 -1
- package/dist/proto-rest/index.cjs.map +1 -1
- package/dist/proto-rest/index.d.ts +1 -1
- package/dist/proto-rest/index.d.ts.map +1 -1
- package/dist/proto-rest/index.js.map +1 -1
- package/dist/proto-rest/plapi.d.ts +30 -9
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/dist/test/tcp-proxy.cjs +2 -2
- package/dist/test/tcp-proxy.cjs.map +1 -1
- package/dist/test/tcp-proxy.d.ts +1 -1
- package/dist/test/tcp-proxy.js.map +1 -1
- package/dist/test/test_config.cjs +8 -2
- package/dist/test/test_config.cjs.map +1 -1
- package/dist/test/test_config.d.ts +1 -1
- package/dist/test/test_config.d.ts.map +1 -1
- package/dist/test/test_config.js +1 -1
- package/dist/test/test_config.js.map +1 -1
- package/dist/util/pl.cjs.map +1 -1
- package/dist/util/pl.js.map +1 -1
- package/dist/util/util.cjs.map +1 -1
- package/dist/util/util.js.map +1 -1
- package/package.json +4 -4
- package/src/core/capabilities.ts +10 -3
- package/src/core/client.ts +30 -3
- package/src/core/errors.ts +2 -0
- package/src/core/final.ts +4 -0
- package/src/core/ll_client.test.ts +15 -2
- package/src/core/ll_client.ts +66 -11
- package/src/core/transaction.ts +103 -3
- package/src/core/unauth_client.ts +17 -2
- package/src/core/user_resources.ts +85 -0
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +16 -3
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +335 -12
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +21 -7
- package/src/proto-rest/plapi.ts +29 -8
- package/dist/proto-grpc/google/rpc/code.cjs +0 -224
- package/dist/proto-grpc/google/rpc/code.cjs.map +0 -1
- package/dist/proto-grpc/google/rpc/code.js +0 -224
- package/dist/proto-grpc/google/rpc/code.js.map +0 -1
|
@@ -17,6 +17,9 @@ const LsProviderFieldPrefix = "storage/";
|
|
|
17
17
|
* 3. Named resource lookup/creation via transaction (legacy)
|
|
18
18
|
*/
|
|
19
19
|
var UserResources = class {
|
|
20
|
+
ll;
|
|
21
|
+
runTx;
|
|
22
|
+
authUser;
|
|
20
23
|
backendCapability;
|
|
21
24
|
constructor(ll, runTx, authUser) {
|
|
22
25
|
this.ll = ll;
|
|
@@ -69,6 +72,60 @@ var UserResources = class {
|
|
|
69
72
|
if (this.backendCapability !== "legacy") return await this.getDataLibrariesViaList(opts);
|
|
70
73
|
return await this.getDataLibrariesViaLegacy();
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Discovers shared resources of a given type granted to the user, as signed resource ids.
|
|
77
|
+
*
|
|
78
|
+
* The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the
|
|
79
|
+
* `ListUserResources` gRPC stream and returns every `sharedResource` entry whose
|
|
80
|
+
* `resourceType.name` matches `resourceTypeName`. Matching is by name only — the
|
|
81
|
+
* version is ignored (permissive, survives schema bumps).
|
|
82
|
+
*
|
|
83
|
+
* gRPC-only: `ListUserResources` throws on a REST-connected client
|
|
84
|
+
* ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.
|
|
85
|
+
*/
|
|
86
|
+
async listSharedResourcesByType(resourceTypeName, opts = {}) {
|
|
87
|
+
const responses = await this.ll.listUserResources({ login: opts.login });
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const msg of responses) {
|
|
90
|
+
if (msg.entry.oneofKind !== "sharedResource") continue;
|
|
91
|
+
const sr = msg.entry.sharedResource;
|
|
92
|
+
if (!sr.resourceType) continue;
|
|
93
|
+
if (sr.resourceType.name !== resourceTypeName) continue;
|
|
94
|
+
result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Enumerates the recipients of a single resource — the donor-side "who did I
|
|
100
|
+
* share with" view.
|
|
101
|
+
*
|
|
102
|
+
* Takes a signed, writable resource handle: the backend gates grant listing on a
|
|
103
|
+
* signed resource id with writable permission, so only the resource's owner can
|
|
104
|
+
* call it. An everyone-grant surfaces with `user` matching the backend's
|
|
105
|
+
* everyone-sentinel — test with {@link isEveryoneUserLogin} (from "./transaction");
|
|
106
|
+
* the caller maps that to "*".
|
|
107
|
+
*
|
|
108
|
+
* Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on
|
|
109
|
+
* every wire protocol — including REST, where the standalone server-streaming
|
|
110
|
+
* `ListGrants` RPC has no binding.
|
|
111
|
+
*/
|
|
112
|
+
async listGrants(resourceId) {
|
|
113
|
+
return (await this.runTx("ListGrants", false, "", (tx) => tx.listGrants(resourceId))).map((grant) => ({
|
|
114
|
+
user: grant.user,
|
|
115
|
+
writable: grant.permissions?.writable ?? false,
|
|
116
|
+
grantedBy: grant.grantedBy,
|
|
117
|
+
grantedAt: Number(grant.grantedAt)
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lists the logins of users known to the server, for the recipient picker. A user becomes
|
|
122
|
+
* known on first login; provisioned users who have never logged in do not appear. gRPC-only
|
|
123
|
+
* (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a
|
|
124
|
+
* REST-connected client, so callers gate on the `userListing:v1` capability.
|
|
125
|
+
*/
|
|
126
|
+
async listUsers() {
|
|
127
|
+
return (await this.ll.listUsers()).map((user) => ({ login: user.login }));
|
|
128
|
+
}
|
|
72
129
|
async getUserRootViaRpc(opts = {}) {
|
|
73
130
|
const resp = await this.ll.getUserRoot({
|
|
74
131
|
login: opts.login,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.js","names":[],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user_resources.js","names":[],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,24 +2,20 @@ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
|
2
2
|
const require_errors = require("./errors.cjs");
|
|
3
3
|
const require_retry_strategy = require("../helpers/retry_strategy.cjs");
|
|
4
4
|
let denque = require("denque");
|
|
5
|
-
denque = require_runtime.__toESM(denque);
|
|
5
|
+
denque = require_runtime.__toESM(denque, 1);
|
|
6
6
|
let undici = require("undici");
|
|
7
7
|
//#region src/core/websocket_stream.ts
|
|
8
|
-
var ConnectionState = /* @__PURE__ */ function(ConnectionState) {
|
|
9
|
-
ConnectionState[ConnectionState["NEW"] = 0] = "NEW";
|
|
10
|
-
ConnectionState[ConnectionState["CONNECTING"] = 1] = "CONNECTING";
|
|
11
|
-
ConnectionState[ConnectionState["CONNECTED"] = 2] = "CONNECTED";
|
|
12
|
-
ConnectionState[ConnectionState["CLOSING"] = 3] = "CLOSING";
|
|
13
|
-
ConnectionState[ConnectionState["CLOSED"] = 4] = "CLOSED";
|
|
14
|
-
return ConnectionState;
|
|
15
|
-
}(ConnectionState || {});
|
|
16
8
|
/**
|
|
17
9
|
* WebSocket-based bidirectional stream implementation for LLTransaction.
|
|
18
10
|
* Implements BiDiStream interface which is compatible with DuplexStreamingCall.
|
|
19
11
|
*/
|
|
20
12
|
var WebSocketBiDiStream = class {
|
|
13
|
+
url;
|
|
14
|
+
serializeClientMessage;
|
|
15
|
+
parseServerMessage;
|
|
16
|
+
options;
|
|
21
17
|
ws = null;
|
|
22
|
-
connectionState =
|
|
18
|
+
connectionState = 0;
|
|
23
19
|
reconnection;
|
|
24
20
|
sendQueue = new denque.default();
|
|
25
21
|
sendCompleted = false;
|
|
@@ -44,12 +40,12 @@ var WebSocketBiDiStream = class {
|
|
|
44
40
|
responses = { [Symbol.asyncIterator]: () => this.createResponseIterator() };
|
|
45
41
|
close() {
|
|
46
42
|
this.reconnection.cancel();
|
|
47
|
-
if (this.connectionState <
|
|
43
|
+
if (this.connectionState < 2) {
|
|
48
44
|
this.ws?.close();
|
|
49
45
|
this.onClose();
|
|
50
46
|
return;
|
|
51
47
|
}
|
|
52
|
-
if (!this.progressConnectionState(
|
|
48
|
+
if (!this.progressConnectionState(3)) return;
|
|
53
49
|
this.ws.close();
|
|
54
50
|
}
|
|
55
51
|
constructor(url, serializeClientMessage, parseServerMessage, options = {}) {
|
|
@@ -58,14 +54,15 @@ var WebSocketBiDiStream = class {
|
|
|
58
54
|
this.parseServerMessage = parseServerMessage;
|
|
59
55
|
this.options = options;
|
|
60
56
|
this.onComplete = this.options.onComplete ?? ((stream) => stream.close());
|
|
61
|
-
|
|
57
|
+
const retryConfig = this.options.retryConfig ?? {};
|
|
58
|
+
this.reconnection = new require_retry_strategy.RetryStrategy(retryConfig, {
|
|
62
59
|
onRetry: () => {
|
|
63
60
|
this.connect();
|
|
64
61
|
},
|
|
65
62
|
onMaxAttemptsReached: (error) => this.handleError(error)
|
|
66
63
|
});
|
|
67
64
|
if (this.options.abortSignal?.aborted) {
|
|
68
|
-
this.progressConnectionState(
|
|
65
|
+
this.progressConnectionState(4);
|
|
69
66
|
return;
|
|
70
67
|
}
|
|
71
68
|
this.abortHandler = () => this.close();
|
|
@@ -74,7 +71,7 @@ var WebSocketBiDiStream = class {
|
|
|
74
71
|
}
|
|
75
72
|
connect() {
|
|
76
73
|
if (this.options.abortSignal?.aborted) return;
|
|
77
|
-
if (!this.progressConnectionState(
|
|
74
|
+
if (!this.progressConnectionState(1)) return;
|
|
78
75
|
try {
|
|
79
76
|
this.ws = this.createWebSocket();
|
|
80
77
|
this.ws.addEventListener("open", () => this.onOpen());
|
|
@@ -95,7 +92,7 @@ var WebSocketBiDiStream = class {
|
|
|
95
92
|
return ws;
|
|
96
93
|
}
|
|
97
94
|
onOpen() {
|
|
98
|
-
this.progressConnectionState(
|
|
95
|
+
this.progressConnectionState(2);
|
|
99
96
|
this.processSendQueue();
|
|
100
97
|
}
|
|
101
98
|
onMessage(data) {
|
|
@@ -111,7 +108,7 @@ var WebSocketBiDiStream = class {
|
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
onError(error) {
|
|
114
|
-
if (this.connectionState <
|
|
111
|
+
if (this.connectionState < 2) {
|
|
115
112
|
this.lastError = this.toError(error);
|
|
116
113
|
this.reconnection.schedule();
|
|
117
114
|
return;
|
|
@@ -119,7 +116,7 @@ var WebSocketBiDiStream = class {
|
|
|
119
116
|
this.handleError(this.toError(error));
|
|
120
117
|
}
|
|
121
118
|
onClose() {
|
|
122
|
-
this.progressConnectionState(
|
|
119
|
+
this.progressConnectionState(4);
|
|
123
120
|
if (this.abortHandler) this.options.abortSignal?.removeEventListener("abort", this.abortHandler);
|
|
124
121
|
if (this.options.abortSignal?.aborted && !this.lastError) {
|
|
125
122
|
const reason = this.options.abortSignal.reason;
|
|
@@ -152,7 +149,7 @@ var WebSocketBiDiStream = class {
|
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
151
|
canSendMessages() {
|
|
155
|
-
return this.connectionState ===
|
|
152
|
+
return this.connectionState === 2;
|
|
156
153
|
}
|
|
157
154
|
sendQueuedMessage(queued) {
|
|
158
155
|
try {
|
|
@@ -254,7 +251,7 @@ var WebSocketBiDiStream = class {
|
|
|
254
251
|
return /* @__PURE__ */ new Error("Stream closed");
|
|
255
252
|
}
|
|
256
253
|
isStreamEnded() {
|
|
257
|
-
return this.connectionState ===
|
|
254
|
+
return this.connectionState === 4 || this.options.abortSignal?.aborted || false;
|
|
258
255
|
}
|
|
259
256
|
toError(error) {
|
|
260
257
|
if (error instanceof Error) return error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket_stream.cjs","names":["Denque","RetryStrategy","WebSocket","ErrorEvent","DisconnectedError"],"sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket, type WebSocketInit, type Dispatcher, ErrorEvent } from \"undici\";\nimport type { BiDiStream } from \"./abstract_stream\";\nimport Denque from \"denque\";\nimport type { RetryConfig } from \"../helpers/retry_strategy\";\nimport { RetryStrategy } from \"../helpers/retry_strategy\";\nimport { DisconnectedError } from \"./errors\";\n\ninterface QueuedMessage<InType extends object> {\n message: InType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver<OutType extends object> {\n resolve: (value: IteratorResult<OutType>) => void;\n reject: (error: Error) => void;\n}\n\nenum ConnectionState {\n NEW = 0,\n CONNECTING = 1,\n CONNECTED = 2,\n CLOSING = 3,\n CLOSED = 4,\n}\n\nexport type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {\n abortSignal?: AbortSignal;\n\n dispatcher?: Dispatcher;\n jwtToken?: string;\n retryConfig?: Partial<RetryConfig>;\n\n onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n};\n\n/**\n * WebSocket-based bidirectional stream implementation for LLTransaction.\n * Implements BiDiStream interface which is compatible with DuplexStreamingCall.\n */\nexport class WebSocketBiDiStream<\n ClientMsg extends object,\n ServerMsg extends object,\n> implements BiDiStream<ClientMsg, ServerMsg> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = ConnectionState.NEW;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage<ClientMsg>>();\n private sendCompleted = false;\n private readonly onComplete: (\n stream: WebSocketBiDiStream<ClientMsg, ServerMsg>,\n ) => void | Promise<void>;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMsg>();\n private responseResolvers: ResponseResolver<ServerMsg>[] = [];\n\n // Error tracking\n private lastError?: Error;\n\n // Abort listener reference for cleanup\n private readonly abortHandler?: () => void;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMsg): Promise<void> => {\n return await this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream\n try {\n await this.onComplete(this); // custom onComplete may send additional messages\n } catch {\n // When 'complete' gets called concurrently with connection break or over a broken\n // transaction stream (server decided it should drop transaction), server would close\n // connection anyway on its end. We can safely ignore error here and just continue working.\n }\n this.sendCompleted = true;\n },\n };\n\n public readonly responses: AsyncIterable<ServerMsg> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n public close(): void {\n this.reconnection.cancel();\n\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Never reached CONNECTED state. ws.close() will never trigger 'close' event.\n this.ws?.close();\n this.onClose();\n return;\n }\n\n if (!this.progressConnectionState(ConnectionState.CLOSING)) return;\n this.ws!.close();\n }\n\n constructor(\n private readonly url: string,\n private readonly serializeClientMessage: (message: ClientMsg) => Uint8Array,\n private readonly parseServerMessage: (data: Uint8Array) => ServerMsg,\n private readonly options: WSStreamOptions<ClientMsg, ServerMsg> = {},\n ) {\n this.onComplete = this.options.onComplete ?? ((stream) => stream.close());\n\n const retryConfig = this.options.retryConfig ?? {};\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => {\n void this.connect();\n },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (this.options.abortSignal?.aborted) {\n this.progressConnectionState(ConnectionState.CLOSED);\n return;\n }\n\n this.abortHandler = () => this.close();\n this.options.abortSignal?.addEventListener(\"abort\", this.abortHandler, { once: true });\n this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.options.abortSignal?.aborted) return;\n\n // Prevent reconnecting after first successful connection.\n if (!this.progressConnectionState(ConnectionState.CONNECTING)) return;\n\n try {\n this.ws = this.createWebSocket();\n\n this.ws.addEventListener(\"open\", () => this.onOpen());\n this.ws.addEventListener(\"message\", (event) => this.onMessage(event.data));\n this.ws.addEventListener(\"error\", (error) => this.onError(error));\n this.ws.addEventListener(\"close\", () => this.onClose());\n } catch (error) {\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options: WebSocketInit = {};\n\n if (this.options.jwtToken)\n options.headers = { authorization: `Bearer ${this.options.jwtToken}` };\n if (this.options.dispatcher) options.dispatcher = this.options.dispatcher;\n\n const ws = new WebSocket(this.url, options);\n ws.binaryType = \"arraybuffer\";\n return ws;\n }\n\n private onOpen(): void {\n this.progressConnectionState(ConnectionState.CONNECTED);\n this.processSendQueue();\n }\n\n private onMessage(data: unknown): void {\n if (!(data instanceof ArrayBuffer)) {\n this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));\n return;\n }\n\n try {\n const message = this.parseServerMessage(new Uint8Array(data));\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private onError(error: unknown): void {\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Try to connect several times until we succeed or run out of attempts.\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n return;\n }\n\n this.handleError(this.toError(error));\n }\n\n private onClose(): void {\n this.progressConnectionState(ConnectionState.CLOSED);\n\n // Clean up abort listener to prevent memory leaks\n if (this.abortHandler) {\n this.options.abortSignal?.removeEventListener(\"abort\", this.abortHandler);\n }\n\n // If abort signal was triggered, use that as the error source\n if (this.options.abortSignal?.aborted && !this.lastError) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n this.lastError = reason;\n } else if (reason !== undefined) {\n this.lastError = new Error(String(reason), { cause: reason });\n } else {\n this.lastError = this.createStreamClosedError();\n }\n }\n\n if (!this.lastError) {\n this.rejectAllSendOperations(this.createStreamClosedError());\n this.resolveAllPendingResponses(); // unblock active async iterator\n } else {\n this.rejectAllPendingOperations(this.lastError);\n }\n }\n\n // === Send Queue Management ===\n\n private enqueueSend(message: ClientMsg): Promise<void> {\n if (this.sendCompleted) {\n throw new Error(\"Cannot send: stream already completed\");\n }\n\n if (this.options.abortSignal?.aborted) {\n throw new Error(\"Cannot send: stream aborted\");\n }\n\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n this.processSendQueue();\n });\n }\n\n private processSendQueue(): void {\n if (!this.canSendMessages()) return;\n\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n this.sendQueuedMessage(queued);\n }\n }\n\n private canSendMessages(): boolean {\n return this.connectionState === ConnectionState.CONNECTED;\n }\n\n private sendQueuedMessage(queued: QueuedMessage<ClientMsg>): void {\n try {\n const ws = this.ws;\n if (!ws) {\n throw new Error(\"WebSocket is not connected\");\n }\n\n // Check if WebSocket is in a valid state for sending\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);\n }\n\n const binary = this.serializeClientMessage(queued.message);\n ws.send(binary);\n queued.resolve();\n } catch (error) {\n queued.reject(this.toError(error));\n }\n }\n\n private async drainSendQueue(): Promise<void> {\n const POLL_INTERVAL_MS = 5;\n\n while (this.sendQueue.length > 0) {\n await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);\n }\n }\n\n private waitForCondition(condition: () => boolean, intervalMs: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.options.abortSignal?.aborted) {\n return reject(this.toError(this.options.abortSignal.reason) ?? new Error(\"Stream aborted\"));\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(this.toError(this.options.abortSignal?.reason) ?? new Error(\"Stream aborted\"));\n };\n\n this.options.abortSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.options.abortSignal?.removeEventListener(\"abort\", onAbort);\n resolve();\n } else {\n timeoutId = setTimeout(check, intervalMs);\n }\n };\n\n check();\n });\n }\n\n // === Response Delivery ===\n\n private deliverResponse(message: ServerMsg): void {\n if (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: message, done: false });\n } else {\n this.responseQueue.push(message);\n }\n }\n\n private async *createResponseIterator(): AsyncIterator<ServerMsg> {\n while (true) {\n const result = await this.nextResponse();\n\n if (result.done) break;\n\n yield result.value;\n }\n }\n\n private nextResponse(): Promise<IteratorResult<ServerMsg>> {\n return new Promise<IteratorResult<ServerMsg>>((resolve, reject) => {\n // Fast path: message already available\n if (this.responseQueue.length > 0) {\n const message = this.responseQueue.shift()!;\n resolve({ value: message, done: false });\n return;\n }\n\n // Stream ended\n if (this.isStreamEnded()) {\n if (this.lastError) {\n reject(this.lastError);\n } else {\n resolve({ value: undefined as any, done: true });\n }\n return;\n }\n\n // Wait for next message\n this.responseResolvers.push({ resolve, reject });\n });\n }\n\n private resolveAllPendingResponses(): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: undefined as any, done: true });\n }\n }\n\n // === Error Handling ===\n\n private handleError(error: Error): void {\n this.lastError = error;\n this.close();\n }\n\n private rejectAllPendingOperations(error: Error): void {\n this.rejectAllSendOperations(error);\n this.rejectAllResponseResolvers(error);\n }\n\n private rejectAllSendOperations(error: Error): void {\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n queued.reject(error);\n }\n }\n\n private rejectAllResponseResolvers(error: Error): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.reject(error);\n }\n }\n\n private createStreamClosedError(): Error {\n if (this.options.abortSignal?.aborted) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error(\"Stream aborted\", { cause: reason });\n }\n\n return new Error(\"Stream closed\");\n }\n\n // === Helpers ===\n\n private isStreamEnded(): boolean {\n return (\n this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false\n );\n }\n\n private toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (error instanceof ErrorEvent) {\n const err = error.error;\n // undici WebSocket throws TypeError with empty message on socket close\n // (e.g., when connection is lost or server disconnects)\n if (err instanceof TypeError && !err.message) {\n return new DisconnectedError(\"WebSocket connection closed unexpectedly\");\n }\n return err instanceof Error ? err : new Error(\"WebSocket error\", { cause: error });\n }\n return new Error(String(error));\n }\n\n /**\n * Connection state progresses linearly from NEW to CLOSED and never goes back.\n * This internal contract dramatically simplifies the internal stream state management.\n *\n * If you ever feel the need to make this contract less strict, think twice.\n */\n private progressConnectionState(newState: ConnectionState): boolean {\n if (newState < this.connectionState) {\n return false;\n }\n this.connectionState = newState;\n return true;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"websocket_stream.cjs","names":["Denque","RetryStrategy","WebSocket","ErrorEvent","DisconnectedError"],"sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket, type WebSocketInit, type Dispatcher, ErrorEvent } from \"undici\";\nimport type { BiDiStream } from \"./abstract_stream\";\nimport Denque from \"denque\";\nimport type { RetryConfig } from \"../helpers/retry_strategy\";\nimport { RetryStrategy } from \"../helpers/retry_strategy\";\nimport { DisconnectedError } from \"./errors\";\n\ninterface QueuedMessage<InType extends object> {\n message: InType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver<OutType extends object> {\n resolve: (value: IteratorResult<OutType>) => void;\n reject: (error: Error) => void;\n}\n\nenum ConnectionState {\n NEW = 0,\n CONNECTING = 1,\n CONNECTED = 2,\n CLOSING = 3,\n CLOSED = 4,\n}\n\nexport type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {\n abortSignal?: AbortSignal;\n\n dispatcher?: Dispatcher;\n jwtToken?: string;\n retryConfig?: Partial<RetryConfig>;\n\n onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n};\n\n/**\n * WebSocket-based bidirectional stream implementation for LLTransaction.\n * Implements BiDiStream interface which is compatible with DuplexStreamingCall.\n */\nexport class WebSocketBiDiStream<\n ClientMsg extends object,\n ServerMsg extends object,\n> implements BiDiStream<ClientMsg, ServerMsg> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = ConnectionState.NEW;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage<ClientMsg>>();\n private sendCompleted = false;\n private readonly onComplete: (\n stream: WebSocketBiDiStream<ClientMsg, ServerMsg>,\n ) => void | Promise<void>;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMsg>();\n private responseResolvers: ResponseResolver<ServerMsg>[] = [];\n\n // Error tracking\n private lastError?: Error;\n\n // Abort listener reference for cleanup\n private readonly abortHandler?: () => void;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMsg): Promise<void> => {\n return await this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream\n try {\n await this.onComplete(this); // custom onComplete may send additional messages\n } catch {\n // When 'complete' gets called concurrently with connection break or over a broken\n // transaction stream (server decided it should drop transaction), server would close\n // connection anyway on its end. We can safely ignore error here and just continue working.\n }\n this.sendCompleted = true;\n },\n };\n\n public readonly responses: AsyncIterable<ServerMsg> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n public close(): void {\n this.reconnection.cancel();\n\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Never reached CONNECTED state. ws.close() will never trigger 'close' event.\n this.ws?.close();\n this.onClose();\n return;\n }\n\n if (!this.progressConnectionState(ConnectionState.CLOSING)) return;\n this.ws!.close();\n }\n\n constructor(\n private readonly url: string,\n private readonly serializeClientMessage: (message: ClientMsg) => Uint8Array,\n private readonly parseServerMessage: (data: Uint8Array) => ServerMsg,\n private readonly options: WSStreamOptions<ClientMsg, ServerMsg> = {},\n ) {\n this.onComplete = this.options.onComplete ?? ((stream) => stream.close());\n\n const retryConfig = this.options.retryConfig ?? {};\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => {\n void this.connect();\n },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (this.options.abortSignal?.aborted) {\n this.progressConnectionState(ConnectionState.CLOSED);\n return;\n }\n\n this.abortHandler = () => this.close();\n this.options.abortSignal?.addEventListener(\"abort\", this.abortHandler, { once: true });\n this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.options.abortSignal?.aborted) return;\n\n // Prevent reconnecting after first successful connection.\n if (!this.progressConnectionState(ConnectionState.CONNECTING)) return;\n\n try {\n this.ws = this.createWebSocket();\n\n this.ws.addEventListener(\"open\", () => this.onOpen());\n this.ws.addEventListener(\"message\", (event) => this.onMessage(event.data));\n this.ws.addEventListener(\"error\", (error) => this.onError(error));\n this.ws.addEventListener(\"close\", () => this.onClose());\n } catch (error) {\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options: WebSocketInit = {};\n\n if (this.options.jwtToken)\n options.headers = { authorization: `Bearer ${this.options.jwtToken}` };\n if (this.options.dispatcher) options.dispatcher = this.options.dispatcher;\n\n const ws = new WebSocket(this.url, options);\n ws.binaryType = \"arraybuffer\";\n return ws;\n }\n\n private onOpen(): void {\n this.progressConnectionState(ConnectionState.CONNECTED);\n this.processSendQueue();\n }\n\n private onMessage(data: unknown): void {\n if (!(data instanceof ArrayBuffer)) {\n this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));\n return;\n }\n\n try {\n const message = this.parseServerMessage(new Uint8Array(data));\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private onError(error: unknown): void {\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Try to connect several times until we succeed or run out of attempts.\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n return;\n }\n\n this.handleError(this.toError(error));\n }\n\n private onClose(): void {\n this.progressConnectionState(ConnectionState.CLOSED);\n\n // Clean up abort listener to prevent memory leaks\n if (this.abortHandler) {\n this.options.abortSignal?.removeEventListener(\"abort\", this.abortHandler);\n }\n\n // If abort signal was triggered, use that as the error source\n if (this.options.abortSignal?.aborted && !this.lastError) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n this.lastError = reason;\n } else if (reason !== undefined) {\n this.lastError = new Error(String(reason), { cause: reason });\n } else {\n this.lastError = this.createStreamClosedError();\n }\n }\n\n if (!this.lastError) {\n this.rejectAllSendOperations(this.createStreamClosedError());\n this.resolveAllPendingResponses(); // unblock active async iterator\n } else {\n this.rejectAllPendingOperations(this.lastError);\n }\n }\n\n // === Send Queue Management ===\n\n private enqueueSend(message: ClientMsg): Promise<void> {\n if (this.sendCompleted) {\n throw new Error(\"Cannot send: stream already completed\");\n }\n\n if (this.options.abortSignal?.aborted) {\n throw new Error(\"Cannot send: stream aborted\");\n }\n\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n this.processSendQueue();\n });\n }\n\n private processSendQueue(): void {\n if (!this.canSendMessages()) return;\n\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n this.sendQueuedMessage(queued);\n }\n }\n\n private canSendMessages(): boolean {\n return this.connectionState === ConnectionState.CONNECTED;\n }\n\n private sendQueuedMessage(queued: QueuedMessage<ClientMsg>): void {\n try {\n const ws = this.ws;\n if (!ws) {\n throw new Error(\"WebSocket is not connected\");\n }\n\n // Check if WebSocket is in a valid state for sending\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);\n }\n\n const binary = this.serializeClientMessage(queued.message);\n ws.send(binary);\n queued.resolve();\n } catch (error) {\n queued.reject(this.toError(error));\n }\n }\n\n private async drainSendQueue(): Promise<void> {\n const POLL_INTERVAL_MS = 5;\n\n while (this.sendQueue.length > 0) {\n await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);\n }\n }\n\n private waitForCondition(condition: () => boolean, intervalMs: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.options.abortSignal?.aborted) {\n return reject(this.toError(this.options.abortSignal.reason) ?? new Error(\"Stream aborted\"));\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(this.toError(this.options.abortSignal?.reason) ?? new Error(\"Stream aborted\"));\n };\n\n this.options.abortSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.options.abortSignal?.removeEventListener(\"abort\", onAbort);\n resolve();\n } else {\n timeoutId = setTimeout(check, intervalMs);\n }\n };\n\n check();\n });\n }\n\n // === Response Delivery ===\n\n private deliverResponse(message: ServerMsg): void {\n if (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: message, done: false });\n } else {\n this.responseQueue.push(message);\n }\n }\n\n private async *createResponseIterator(): AsyncIterator<ServerMsg> {\n while (true) {\n const result = await this.nextResponse();\n\n if (result.done) break;\n\n yield result.value;\n }\n }\n\n private nextResponse(): Promise<IteratorResult<ServerMsg>> {\n return new Promise<IteratorResult<ServerMsg>>((resolve, reject) => {\n // Fast path: message already available\n if (this.responseQueue.length > 0) {\n const message = this.responseQueue.shift()!;\n resolve({ value: message, done: false });\n return;\n }\n\n // Stream ended\n if (this.isStreamEnded()) {\n if (this.lastError) {\n reject(this.lastError);\n } else {\n resolve({ value: undefined as any, done: true });\n }\n return;\n }\n\n // Wait for next message\n this.responseResolvers.push({ resolve, reject });\n });\n }\n\n private resolveAllPendingResponses(): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: undefined as any, done: true });\n }\n }\n\n // === Error Handling ===\n\n private handleError(error: Error): void {\n this.lastError = error;\n this.close();\n }\n\n private rejectAllPendingOperations(error: Error): void {\n this.rejectAllSendOperations(error);\n this.rejectAllResponseResolvers(error);\n }\n\n private rejectAllSendOperations(error: Error): void {\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n queued.reject(error);\n }\n }\n\n private rejectAllResponseResolvers(error: Error): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.reject(error);\n }\n }\n\n private createStreamClosedError(): Error {\n if (this.options.abortSignal?.aborted) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error(\"Stream aborted\", { cause: reason });\n }\n\n return new Error(\"Stream closed\");\n }\n\n // === Helpers ===\n\n private isStreamEnded(): boolean {\n return (\n this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false\n );\n }\n\n private toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (error instanceof ErrorEvent) {\n const err = error.error;\n // undici WebSocket throws TypeError with empty message on socket close\n // (e.g., when connection is lost or server disconnects)\n if (err instanceof TypeError && !err.message) {\n return new DisconnectedError(\"WebSocket connection closed unexpectedly\");\n }\n return err instanceof Error ? err : new Error(\"WebSocket error\", { cause: error });\n }\n return new Error(String(error));\n }\n\n /**\n * Connection state progresses linearly from NEW to CLOSED and never goes back.\n * This internal contract dramatically simplifies the internal stream state management.\n *\n * If you ever feel the need to make this contract less strict, think twice.\n */\n private progressConnectionState(newState: ConnectionState): boolean {\n if (newState < this.connectionState) {\n return false;\n }\n this.connectionState = newState;\n return true;\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,21 +3,17 @@ import { RetryStrategy } from "../helpers/retry_strategy.js";
|
|
|
3
3
|
import Denque from "denque";
|
|
4
4
|
import { ErrorEvent, WebSocket } from "undici";
|
|
5
5
|
//#region src/core/websocket_stream.ts
|
|
6
|
-
var ConnectionState = /* @__PURE__ */ function(ConnectionState) {
|
|
7
|
-
ConnectionState[ConnectionState["NEW"] = 0] = "NEW";
|
|
8
|
-
ConnectionState[ConnectionState["CONNECTING"] = 1] = "CONNECTING";
|
|
9
|
-
ConnectionState[ConnectionState["CONNECTED"] = 2] = "CONNECTED";
|
|
10
|
-
ConnectionState[ConnectionState["CLOSING"] = 3] = "CLOSING";
|
|
11
|
-
ConnectionState[ConnectionState["CLOSED"] = 4] = "CLOSED";
|
|
12
|
-
return ConnectionState;
|
|
13
|
-
}(ConnectionState || {});
|
|
14
6
|
/**
|
|
15
7
|
* WebSocket-based bidirectional stream implementation for LLTransaction.
|
|
16
8
|
* Implements BiDiStream interface which is compatible with DuplexStreamingCall.
|
|
17
9
|
*/
|
|
18
10
|
var WebSocketBiDiStream = class {
|
|
11
|
+
url;
|
|
12
|
+
serializeClientMessage;
|
|
13
|
+
parseServerMessage;
|
|
14
|
+
options;
|
|
19
15
|
ws = null;
|
|
20
|
-
connectionState =
|
|
16
|
+
connectionState = 0;
|
|
21
17
|
reconnection;
|
|
22
18
|
sendQueue = new Denque();
|
|
23
19
|
sendCompleted = false;
|
|
@@ -42,12 +38,12 @@ var WebSocketBiDiStream = class {
|
|
|
42
38
|
responses = { [Symbol.asyncIterator]: () => this.createResponseIterator() };
|
|
43
39
|
close() {
|
|
44
40
|
this.reconnection.cancel();
|
|
45
|
-
if (this.connectionState <
|
|
41
|
+
if (this.connectionState < 2) {
|
|
46
42
|
this.ws?.close();
|
|
47
43
|
this.onClose();
|
|
48
44
|
return;
|
|
49
45
|
}
|
|
50
|
-
if (!this.progressConnectionState(
|
|
46
|
+
if (!this.progressConnectionState(3)) return;
|
|
51
47
|
this.ws.close();
|
|
52
48
|
}
|
|
53
49
|
constructor(url, serializeClientMessage, parseServerMessage, options = {}) {
|
|
@@ -56,14 +52,15 @@ var WebSocketBiDiStream = class {
|
|
|
56
52
|
this.parseServerMessage = parseServerMessage;
|
|
57
53
|
this.options = options;
|
|
58
54
|
this.onComplete = this.options.onComplete ?? ((stream) => stream.close());
|
|
59
|
-
|
|
55
|
+
const retryConfig = this.options.retryConfig ?? {};
|
|
56
|
+
this.reconnection = new RetryStrategy(retryConfig, {
|
|
60
57
|
onRetry: () => {
|
|
61
58
|
this.connect();
|
|
62
59
|
},
|
|
63
60
|
onMaxAttemptsReached: (error) => this.handleError(error)
|
|
64
61
|
});
|
|
65
62
|
if (this.options.abortSignal?.aborted) {
|
|
66
|
-
this.progressConnectionState(
|
|
63
|
+
this.progressConnectionState(4);
|
|
67
64
|
return;
|
|
68
65
|
}
|
|
69
66
|
this.abortHandler = () => this.close();
|
|
@@ -72,7 +69,7 @@ var WebSocketBiDiStream = class {
|
|
|
72
69
|
}
|
|
73
70
|
connect() {
|
|
74
71
|
if (this.options.abortSignal?.aborted) return;
|
|
75
|
-
if (!this.progressConnectionState(
|
|
72
|
+
if (!this.progressConnectionState(1)) return;
|
|
76
73
|
try {
|
|
77
74
|
this.ws = this.createWebSocket();
|
|
78
75
|
this.ws.addEventListener("open", () => this.onOpen());
|
|
@@ -93,7 +90,7 @@ var WebSocketBiDiStream = class {
|
|
|
93
90
|
return ws;
|
|
94
91
|
}
|
|
95
92
|
onOpen() {
|
|
96
|
-
this.progressConnectionState(
|
|
93
|
+
this.progressConnectionState(2);
|
|
97
94
|
this.processSendQueue();
|
|
98
95
|
}
|
|
99
96
|
onMessage(data) {
|
|
@@ -109,7 +106,7 @@ var WebSocketBiDiStream = class {
|
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
108
|
onError(error) {
|
|
112
|
-
if (this.connectionState <
|
|
109
|
+
if (this.connectionState < 2) {
|
|
113
110
|
this.lastError = this.toError(error);
|
|
114
111
|
this.reconnection.schedule();
|
|
115
112
|
return;
|
|
@@ -117,7 +114,7 @@ var WebSocketBiDiStream = class {
|
|
|
117
114
|
this.handleError(this.toError(error));
|
|
118
115
|
}
|
|
119
116
|
onClose() {
|
|
120
|
-
this.progressConnectionState(
|
|
117
|
+
this.progressConnectionState(4);
|
|
121
118
|
if (this.abortHandler) this.options.abortSignal?.removeEventListener("abort", this.abortHandler);
|
|
122
119
|
if (this.options.abortSignal?.aborted && !this.lastError) {
|
|
123
120
|
const reason = this.options.abortSignal.reason;
|
|
@@ -150,7 +147,7 @@ var WebSocketBiDiStream = class {
|
|
|
150
147
|
}
|
|
151
148
|
}
|
|
152
149
|
canSendMessages() {
|
|
153
|
-
return this.connectionState ===
|
|
150
|
+
return this.connectionState === 2;
|
|
154
151
|
}
|
|
155
152
|
sendQueuedMessage(queued) {
|
|
156
153
|
try {
|
|
@@ -252,7 +249,7 @@ var WebSocketBiDiStream = class {
|
|
|
252
249
|
return /* @__PURE__ */ new Error("Stream closed");
|
|
253
250
|
}
|
|
254
251
|
isStreamEnded() {
|
|
255
|
-
return this.connectionState ===
|
|
252
|
+
return this.connectionState === 4 || this.options.abortSignal?.aborted || false;
|
|
256
253
|
}
|
|
257
254
|
toError(error) {
|
|
258
255
|
if (error instanceof Error) return error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket_stream.js","names":[],"sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket, type WebSocketInit, type Dispatcher, ErrorEvent } from \"undici\";\nimport type { BiDiStream } from \"./abstract_stream\";\nimport Denque from \"denque\";\nimport type { RetryConfig } from \"../helpers/retry_strategy\";\nimport { RetryStrategy } from \"../helpers/retry_strategy\";\nimport { DisconnectedError } from \"./errors\";\n\ninterface QueuedMessage<InType extends object> {\n message: InType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver<OutType extends object> {\n resolve: (value: IteratorResult<OutType>) => void;\n reject: (error: Error) => void;\n}\n\nenum ConnectionState {\n NEW = 0,\n CONNECTING = 1,\n CONNECTED = 2,\n CLOSING = 3,\n CLOSED = 4,\n}\n\nexport type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {\n abortSignal?: AbortSignal;\n\n dispatcher?: Dispatcher;\n jwtToken?: string;\n retryConfig?: Partial<RetryConfig>;\n\n onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n};\n\n/**\n * WebSocket-based bidirectional stream implementation for LLTransaction.\n * Implements BiDiStream interface which is compatible with DuplexStreamingCall.\n */\nexport class WebSocketBiDiStream<\n ClientMsg extends object,\n ServerMsg extends object,\n> implements BiDiStream<ClientMsg, ServerMsg> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = ConnectionState.NEW;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage<ClientMsg>>();\n private sendCompleted = false;\n private readonly onComplete: (\n stream: WebSocketBiDiStream<ClientMsg, ServerMsg>,\n ) => void | Promise<void>;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMsg>();\n private responseResolvers: ResponseResolver<ServerMsg>[] = [];\n\n // Error tracking\n private lastError?: Error;\n\n // Abort listener reference for cleanup\n private readonly abortHandler?: () => void;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMsg): Promise<void> => {\n return await this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream\n try {\n await this.onComplete(this); // custom onComplete may send additional messages\n } catch {\n // When 'complete' gets called concurrently with connection break or over a broken\n // transaction stream (server decided it should drop transaction), server would close\n // connection anyway on its end. We can safely ignore error here and just continue working.\n }\n this.sendCompleted = true;\n },\n };\n\n public readonly responses: AsyncIterable<ServerMsg> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n public close(): void {\n this.reconnection.cancel();\n\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Never reached CONNECTED state. ws.close() will never trigger 'close' event.\n this.ws?.close();\n this.onClose();\n return;\n }\n\n if (!this.progressConnectionState(ConnectionState.CLOSING)) return;\n this.ws!.close();\n }\n\n constructor(\n private readonly url: string,\n private readonly serializeClientMessage: (message: ClientMsg) => Uint8Array,\n private readonly parseServerMessage: (data: Uint8Array) => ServerMsg,\n private readonly options: WSStreamOptions<ClientMsg, ServerMsg> = {},\n ) {\n this.onComplete = this.options.onComplete ?? ((stream) => stream.close());\n\n const retryConfig = this.options.retryConfig ?? {};\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => {\n void this.connect();\n },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (this.options.abortSignal?.aborted) {\n this.progressConnectionState(ConnectionState.CLOSED);\n return;\n }\n\n this.abortHandler = () => this.close();\n this.options.abortSignal?.addEventListener(\"abort\", this.abortHandler, { once: true });\n this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.options.abortSignal?.aborted) return;\n\n // Prevent reconnecting after first successful connection.\n if (!this.progressConnectionState(ConnectionState.CONNECTING)) return;\n\n try {\n this.ws = this.createWebSocket();\n\n this.ws.addEventListener(\"open\", () => this.onOpen());\n this.ws.addEventListener(\"message\", (event) => this.onMessage(event.data));\n this.ws.addEventListener(\"error\", (error) => this.onError(error));\n this.ws.addEventListener(\"close\", () => this.onClose());\n } catch (error) {\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options: WebSocketInit = {};\n\n if (this.options.jwtToken)\n options.headers = { authorization: `Bearer ${this.options.jwtToken}` };\n if (this.options.dispatcher) options.dispatcher = this.options.dispatcher;\n\n const ws = new WebSocket(this.url, options);\n ws.binaryType = \"arraybuffer\";\n return ws;\n }\n\n private onOpen(): void {\n this.progressConnectionState(ConnectionState.CONNECTED);\n this.processSendQueue();\n }\n\n private onMessage(data: unknown): void {\n if (!(data instanceof ArrayBuffer)) {\n this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));\n return;\n }\n\n try {\n const message = this.parseServerMessage(new Uint8Array(data));\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private onError(error: unknown): void {\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Try to connect several times until we succeed or run out of attempts.\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n return;\n }\n\n this.handleError(this.toError(error));\n }\n\n private onClose(): void {\n this.progressConnectionState(ConnectionState.CLOSED);\n\n // Clean up abort listener to prevent memory leaks\n if (this.abortHandler) {\n this.options.abortSignal?.removeEventListener(\"abort\", this.abortHandler);\n }\n\n // If abort signal was triggered, use that as the error source\n if (this.options.abortSignal?.aborted && !this.lastError) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n this.lastError = reason;\n } else if (reason !== undefined) {\n this.lastError = new Error(String(reason), { cause: reason });\n } else {\n this.lastError = this.createStreamClosedError();\n }\n }\n\n if (!this.lastError) {\n this.rejectAllSendOperations(this.createStreamClosedError());\n this.resolveAllPendingResponses(); // unblock active async iterator\n } else {\n this.rejectAllPendingOperations(this.lastError);\n }\n }\n\n // === Send Queue Management ===\n\n private enqueueSend(message: ClientMsg): Promise<void> {\n if (this.sendCompleted) {\n throw new Error(\"Cannot send: stream already completed\");\n }\n\n if (this.options.abortSignal?.aborted) {\n throw new Error(\"Cannot send: stream aborted\");\n }\n\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n this.processSendQueue();\n });\n }\n\n private processSendQueue(): void {\n if (!this.canSendMessages()) return;\n\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n this.sendQueuedMessage(queued);\n }\n }\n\n private canSendMessages(): boolean {\n return this.connectionState === ConnectionState.CONNECTED;\n }\n\n private sendQueuedMessage(queued: QueuedMessage<ClientMsg>): void {\n try {\n const ws = this.ws;\n if (!ws) {\n throw new Error(\"WebSocket is not connected\");\n }\n\n // Check if WebSocket is in a valid state for sending\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);\n }\n\n const binary = this.serializeClientMessage(queued.message);\n ws.send(binary);\n queued.resolve();\n } catch (error) {\n queued.reject(this.toError(error));\n }\n }\n\n private async drainSendQueue(): Promise<void> {\n const POLL_INTERVAL_MS = 5;\n\n while (this.sendQueue.length > 0) {\n await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);\n }\n }\n\n private waitForCondition(condition: () => boolean, intervalMs: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.options.abortSignal?.aborted) {\n return reject(this.toError(this.options.abortSignal.reason) ?? new Error(\"Stream aborted\"));\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(this.toError(this.options.abortSignal?.reason) ?? new Error(\"Stream aborted\"));\n };\n\n this.options.abortSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.options.abortSignal?.removeEventListener(\"abort\", onAbort);\n resolve();\n } else {\n timeoutId = setTimeout(check, intervalMs);\n }\n };\n\n check();\n });\n }\n\n // === Response Delivery ===\n\n private deliverResponse(message: ServerMsg): void {\n if (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: message, done: false });\n } else {\n this.responseQueue.push(message);\n }\n }\n\n private async *createResponseIterator(): AsyncIterator<ServerMsg> {\n while (true) {\n const result = await this.nextResponse();\n\n if (result.done) break;\n\n yield result.value;\n }\n }\n\n private nextResponse(): Promise<IteratorResult<ServerMsg>> {\n return new Promise<IteratorResult<ServerMsg>>((resolve, reject) => {\n // Fast path: message already available\n if (this.responseQueue.length > 0) {\n const message = this.responseQueue.shift()!;\n resolve({ value: message, done: false });\n return;\n }\n\n // Stream ended\n if (this.isStreamEnded()) {\n if (this.lastError) {\n reject(this.lastError);\n } else {\n resolve({ value: undefined as any, done: true });\n }\n return;\n }\n\n // Wait for next message\n this.responseResolvers.push({ resolve, reject });\n });\n }\n\n private resolveAllPendingResponses(): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: undefined as any, done: true });\n }\n }\n\n // === Error Handling ===\n\n private handleError(error: Error): void {\n this.lastError = error;\n this.close();\n }\n\n private rejectAllPendingOperations(error: Error): void {\n this.rejectAllSendOperations(error);\n this.rejectAllResponseResolvers(error);\n }\n\n private rejectAllSendOperations(error: Error): void {\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n queued.reject(error);\n }\n }\n\n private rejectAllResponseResolvers(error: Error): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.reject(error);\n }\n }\n\n private createStreamClosedError(): Error {\n if (this.options.abortSignal?.aborted) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error(\"Stream aborted\", { cause: reason });\n }\n\n return new Error(\"Stream closed\");\n }\n\n // === Helpers ===\n\n private isStreamEnded(): boolean {\n return (\n this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false\n );\n }\n\n private toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (error instanceof ErrorEvent) {\n const err = error.error;\n // undici WebSocket throws TypeError with empty message on socket close\n // (e.g., when connection is lost or server disconnects)\n if (err instanceof TypeError && !err.message) {\n return new DisconnectedError(\"WebSocket connection closed unexpectedly\");\n }\n return err instanceof Error ? err : new Error(\"WebSocket error\", { cause: error });\n }\n return new Error(String(error));\n }\n\n /**\n * Connection state progresses linearly from NEW to CLOSED and never goes back.\n * This internal contract dramatically simplifies the internal stream state management.\n *\n * If you ever feel the need to make this contract less strict, think twice.\n */\n private progressConnectionState(newState: ConnectionState): boolean {\n if (newState < this.connectionState) {\n return false;\n }\n this.connectionState = newState;\n return true;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"websocket_stream.js","names":[],"sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket, type WebSocketInit, type Dispatcher, ErrorEvent } from \"undici\";\nimport type { BiDiStream } from \"./abstract_stream\";\nimport Denque from \"denque\";\nimport type { RetryConfig } from \"../helpers/retry_strategy\";\nimport { RetryStrategy } from \"../helpers/retry_strategy\";\nimport { DisconnectedError } from \"./errors\";\n\ninterface QueuedMessage<InType extends object> {\n message: InType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver<OutType extends object> {\n resolve: (value: IteratorResult<OutType>) => void;\n reject: (error: Error) => void;\n}\n\nenum ConnectionState {\n NEW = 0,\n CONNECTING = 1,\n CONNECTED = 2,\n CLOSING = 3,\n CLOSED = 4,\n}\n\nexport type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {\n abortSignal?: AbortSignal;\n\n dispatcher?: Dispatcher;\n jwtToken?: string;\n retryConfig?: Partial<RetryConfig>;\n\n onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n};\n\n/**\n * WebSocket-based bidirectional stream implementation for LLTransaction.\n * Implements BiDiStream interface which is compatible with DuplexStreamingCall.\n */\nexport class WebSocketBiDiStream<\n ClientMsg extends object,\n ServerMsg extends object,\n> implements BiDiStream<ClientMsg, ServerMsg> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = ConnectionState.NEW;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage<ClientMsg>>();\n private sendCompleted = false;\n private readonly onComplete: (\n stream: WebSocketBiDiStream<ClientMsg, ServerMsg>,\n ) => void | Promise<void>;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMsg>();\n private responseResolvers: ResponseResolver<ServerMsg>[] = [];\n\n // Error tracking\n private lastError?: Error;\n\n // Abort listener reference for cleanup\n private readonly abortHandler?: () => void;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMsg): Promise<void> => {\n return await this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream\n try {\n await this.onComplete(this); // custom onComplete may send additional messages\n } catch {\n // When 'complete' gets called concurrently with connection break or over a broken\n // transaction stream (server decided it should drop transaction), server would close\n // connection anyway on its end. We can safely ignore error here and just continue working.\n }\n this.sendCompleted = true;\n },\n };\n\n public readonly responses: AsyncIterable<ServerMsg> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n public close(): void {\n this.reconnection.cancel();\n\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Never reached CONNECTED state. ws.close() will never trigger 'close' event.\n this.ws?.close();\n this.onClose();\n return;\n }\n\n if (!this.progressConnectionState(ConnectionState.CLOSING)) return;\n this.ws!.close();\n }\n\n constructor(\n private readonly url: string,\n private readonly serializeClientMessage: (message: ClientMsg) => Uint8Array,\n private readonly parseServerMessage: (data: Uint8Array) => ServerMsg,\n private readonly options: WSStreamOptions<ClientMsg, ServerMsg> = {},\n ) {\n this.onComplete = this.options.onComplete ?? ((stream) => stream.close());\n\n const retryConfig = this.options.retryConfig ?? {};\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => {\n void this.connect();\n },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (this.options.abortSignal?.aborted) {\n this.progressConnectionState(ConnectionState.CLOSED);\n return;\n }\n\n this.abortHandler = () => this.close();\n this.options.abortSignal?.addEventListener(\"abort\", this.abortHandler, { once: true });\n this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.options.abortSignal?.aborted) return;\n\n // Prevent reconnecting after first successful connection.\n if (!this.progressConnectionState(ConnectionState.CONNECTING)) return;\n\n try {\n this.ws = this.createWebSocket();\n\n this.ws.addEventListener(\"open\", () => this.onOpen());\n this.ws.addEventListener(\"message\", (event) => this.onMessage(event.data));\n this.ws.addEventListener(\"error\", (error) => this.onError(error));\n this.ws.addEventListener(\"close\", () => this.onClose());\n } catch (error) {\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options: WebSocketInit = {};\n\n if (this.options.jwtToken)\n options.headers = { authorization: `Bearer ${this.options.jwtToken}` };\n if (this.options.dispatcher) options.dispatcher = this.options.dispatcher;\n\n const ws = new WebSocket(this.url, options);\n ws.binaryType = \"arraybuffer\";\n return ws;\n }\n\n private onOpen(): void {\n this.progressConnectionState(ConnectionState.CONNECTED);\n this.processSendQueue();\n }\n\n private onMessage(data: unknown): void {\n if (!(data instanceof ArrayBuffer)) {\n this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));\n return;\n }\n\n try {\n const message = this.parseServerMessage(new Uint8Array(data));\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private onError(error: unknown): void {\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Try to connect several times until we succeed or run out of attempts.\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n return;\n }\n\n this.handleError(this.toError(error));\n }\n\n private onClose(): void {\n this.progressConnectionState(ConnectionState.CLOSED);\n\n // Clean up abort listener to prevent memory leaks\n if (this.abortHandler) {\n this.options.abortSignal?.removeEventListener(\"abort\", this.abortHandler);\n }\n\n // If abort signal was triggered, use that as the error source\n if (this.options.abortSignal?.aborted && !this.lastError) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n this.lastError = reason;\n } else if (reason !== undefined) {\n this.lastError = new Error(String(reason), { cause: reason });\n } else {\n this.lastError = this.createStreamClosedError();\n }\n }\n\n if (!this.lastError) {\n this.rejectAllSendOperations(this.createStreamClosedError());\n this.resolveAllPendingResponses(); // unblock active async iterator\n } else {\n this.rejectAllPendingOperations(this.lastError);\n }\n }\n\n // === Send Queue Management ===\n\n private enqueueSend(message: ClientMsg): Promise<void> {\n if (this.sendCompleted) {\n throw new Error(\"Cannot send: stream already completed\");\n }\n\n if (this.options.abortSignal?.aborted) {\n throw new Error(\"Cannot send: stream aborted\");\n }\n\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n this.processSendQueue();\n });\n }\n\n private processSendQueue(): void {\n if (!this.canSendMessages()) return;\n\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n this.sendQueuedMessage(queued);\n }\n }\n\n private canSendMessages(): boolean {\n return this.connectionState === ConnectionState.CONNECTED;\n }\n\n private sendQueuedMessage(queued: QueuedMessage<ClientMsg>): void {\n try {\n const ws = this.ws;\n if (!ws) {\n throw new Error(\"WebSocket is not connected\");\n }\n\n // Check if WebSocket is in a valid state for sending\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);\n }\n\n const binary = this.serializeClientMessage(queued.message);\n ws.send(binary);\n queued.resolve();\n } catch (error) {\n queued.reject(this.toError(error));\n }\n }\n\n private async drainSendQueue(): Promise<void> {\n const POLL_INTERVAL_MS = 5;\n\n while (this.sendQueue.length > 0) {\n await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);\n }\n }\n\n private waitForCondition(condition: () => boolean, intervalMs: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.options.abortSignal?.aborted) {\n return reject(this.toError(this.options.abortSignal.reason) ?? new Error(\"Stream aborted\"));\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(this.toError(this.options.abortSignal?.reason) ?? new Error(\"Stream aborted\"));\n };\n\n this.options.abortSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.options.abortSignal?.removeEventListener(\"abort\", onAbort);\n resolve();\n } else {\n timeoutId = setTimeout(check, intervalMs);\n }\n };\n\n check();\n });\n }\n\n // === Response Delivery ===\n\n private deliverResponse(message: ServerMsg): void {\n if (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: message, done: false });\n } else {\n this.responseQueue.push(message);\n }\n }\n\n private async *createResponseIterator(): AsyncIterator<ServerMsg> {\n while (true) {\n const result = await this.nextResponse();\n\n if (result.done) break;\n\n yield result.value;\n }\n }\n\n private nextResponse(): Promise<IteratorResult<ServerMsg>> {\n return new Promise<IteratorResult<ServerMsg>>((resolve, reject) => {\n // Fast path: message already available\n if (this.responseQueue.length > 0) {\n const message = this.responseQueue.shift()!;\n resolve({ value: message, done: false });\n return;\n }\n\n // Stream ended\n if (this.isStreamEnded()) {\n if (this.lastError) {\n reject(this.lastError);\n } else {\n resolve({ value: undefined as any, done: true });\n }\n return;\n }\n\n // Wait for next message\n this.responseResolvers.push({ resolve, reject });\n });\n }\n\n private resolveAllPendingResponses(): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: undefined as any, done: true });\n }\n }\n\n // === Error Handling ===\n\n private handleError(error: Error): void {\n this.lastError = error;\n this.close();\n }\n\n private rejectAllPendingOperations(error: Error): void {\n this.rejectAllSendOperations(error);\n this.rejectAllResponseResolvers(error);\n }\n\n private rejectAllSendOperations(error: Error): void {\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n queued.reject(error);\n }\n }\n\n private rejectAllResponseResolvers(error: Error): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.reject(error);\n }\n }\n\n private createStreamClosedError(): Error {\n if (this.options.abortSignal?.aborted) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error(\"Stream aborted\", { cause: reason });\n }\n\n return new Error(\"Stream closed\");\n }\n\n // === Helpers ===\n\n private isStreamEnded(): boolean {\n return (\n this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false\n );\n }\n\n private toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (error instanceof ErrorEvent) {\n const err = error.error;\n // undici WebSocket throws TypeError with empty message on socket close\n // (e.g., when connection is lost or server disconnects)\n if (err instanceof TypeError && !err.message) {\n return new DisconnectedError(\"WebSocket connection closed unexpectedly\");\n }\n return err instanceof Error ? err : new Error(\"WebSocket error\", { cause: error });\n }\n return new Error(String(error));\n }\n\n /**\n * Connection state progresses linearly from NEW to CLOSED and never goes back.\n * This internal contract dramatically simplifies the internal stream state management.\n *\n * If you ever feel the need to make this contract less strict, think twice.\n */\n private progressConnectionState(newState: ConnectionState): boolean {\n if (newState < this.connectionState) {\n return false;\n }\n this.connectionState = newState;\n return true;\n }\n}\n"],"mappings":"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"}
|
package/dist/core/wire.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.d.ts","names":[],"sources":["../../src/core/wire.ts"],"mappings":";;;;;;;;AASA;;KAAY,cAAA;EACV,IAAA;EACA,MAAA,EAAQ,cAAA;EACR,UAAA,EAAY,UAAA;EACZ,WAAA,EAAa,UAAA;AAAA;AAAA,KAGH,cAAA;EACV,IAAA;EACA,SAAA,EAAW,
|
|
1
|
+
{"version":3,"file":"wire.d.ts","names":[],"sources":["../../src/core/wire.ts"],"mappings":";;;;;;;;AASA;;KAAY,cAAA;EACV,IAAA;EACA,MAAA,EAAQ,cAAA;EACR,UAAA,EAAY,UAAA;EACZ,WAAA,EAAa,UAAA;AAAA;AAAA,KAGH,cAAA;EACV,IAAA;EACA,SAAA,EAAW,aAAa;AAAA;AAAA,KAGd,cAAA,GAAiB,cAAA,GAAiB,cAAc;;;;AARnC;AAGzB;UAciB,kBAAA;EACf,GAAA,IAAO,MAAM;AAAA;;;;UAME,yBAAA;EACf,wBAAA,SACE,iBAAA,GAAoB,SAAA,EAAW,cAAA,KAAmB,MAAA,GACjD,kBAAA,CAAmB,MAAA;AAAA"}
|
package/dist/helpers/pl.cjs
CHANGED
|
@@ -108,13 +108,38 @@ function unwrapHolder(tx, ref) {
|
|
|
108
108
|
}
|
|
109
109
|
//#endregion
|
|
110
110
|
exports.ClientRoot = ClientRoot;
|
|
111
|
+
exports.EphHolder = EphHolder;
|
|
112
|
+
exports.EphStdMap = EphStdMap;
|
|
113
|
+
exports.Holder = Holder;
|
|
114
|
+
exports.JsonArray = JsonArray;
|
|
115
|
+
exports.JsonBool = JsonBool;
|
|
111
116
|
exports.JsonGzObject = JsonGzObject;
|
|
117
|
+
exports.JsonNull = JsonNull;
|
|
118
|
+
exports.JsonNumber = JsonNumber;
|
|
112
119
|
exports.JsonObject = JsonObject;
|
|
120
|
+
exports.JsonString = JsonString;
|
|
121
|
+
exports.RNull = RNull;
|
|
122
|
+
exports.StdMap = StdMap;
|
|
123
|
+
exports.StructTestResource = StructTestResource;
|
|
124
|
+
exports.ValueTestResource = ValueTestResource;
|
|
125
|
+
exports.createPlArray = createPlArray;
|
|
126
|
+
exports.createPlBool = createPlBool;
|
|
127
|
+
exports.createPlMap = createPlMap;
|
|
128
|
+
exports.createPlNull = createPlNull;
|
|
129
|
+
exports.createPlNumber = createPlNumber;
|
|
130
|
+
exports.createPlObject = createPlObject;
|
|
131
|
+
exports.createPlString = createPlString;
|
|
132
|
+
exports.futureRecord = futureRecord;
|
|
133
|
+
exports.plEntries = plEntries;
|
|
134
|
+
exports.plEntry = plEntry;
|
|
113
135
|
Object.defineProperty(exports, "pl_exports", {
|
|
114
136
|
enumerable: true,
|
|
115
137
|
get: function() {
|
|
116
138
|
return pl_exports;
|
|
117
139
|
}
|
|
118
140
|
});
|
|
141
|
+
exports.unwrapHolder = unwrapHolder;
|
|
142
|
+
exports.wrapInEphHolder = wrapInEphHolder;
|
|
143
|
+
exports.wrapInHolder = wrapInHolder;
|
|
119
144
|
|
|
120
145
|
//# sourceMappingURL=pl.cjs.map
|