@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.
Files changed (206) hide show
  1. package/dist/core/PromiseTracker.cjs.map +1 -1
  2. package/dist/core/PromiseTracker.d.ts.map +1 -1
  3. package/dist/core/PromiseTracker.js.map +1 -1
  4. package/dist/core/StatefulPromise.cjs +2 -0
  5. package/dist/core/StatefulPromise.cjs.map +1 -1
  6. package/dist/core/StatefulPromise.js +2 -0
  7. package/dist/core/StatefulPromise.js.map +1 -1
  8. package/dist/core/abstract_stream.d.ts +1 -1
  9. package/dist/core/advisory_locks.cjs.map +1 -1
  10. package/dist/core/advisory_locks.js.map +1 -1
  11. package/dist/core/auth.cjs.map +1 -1
  12. package/dist/core/auth.d.ts.map +1 -1
  13. package/dist/core/auth.js.map +1 -1
  14. package/dist/core/cache.d.ts.map +1 -1
  15. package/dist/core/capabilities.cjs.map +1 -1
  16. package/dist/core/capabilities.d.ts +2 -3
  17. package/dist/core/capabilities.d.ts.map +1 -1
  18. package/dist/core/capabilities.js.map +1 -1
  19. package/dist/core/client.cjs +23 -6
  20. package/dist/core/client.cjs.map +1 -1
  21. package/dist/core/client.d.ts +14 -4
  22. package/dist/core/client.d.ts.map +1 -1
  23. package/dist/core/client.js +22 -5
  24. package/dist/core/client.js.map +1 -1
  25. package/dist/core/config.cjs.map +1 -1
  26. package/dist/core/config.d.ts.map +1 -1
  27. package/dist/core/config.js.map +1 -1
  28. package/dist/core/default_client.cjs +5 -5
  29. package/dist/core/default_client.cjs.map +1 -1
  30. package/dist/core/default_client.d.ts.map +1 -1
  31. package/dist/core/default_client.js.map +1 -1
  32. package/dist/core/driver.cjs.map +1 -1
  33. package/dist/core/driver.d.ts.map +1 -1
  34. package/dist/core/driver.js.map +1 -1
  35. package/dist/core/error_resource.cjs.map +1 -1
  36. package/dist/core/error_resource.js.map +1 -1
  37. package/dist/core/errors.cjs +11 -11
  38. package/dist/core/errors.cjs.map +1 -1
  39. package/dist/core/errors.d.ts.map +1 -1
  40. package/dist/core/errors.js +11 -10
  41. package/dist/core/errors.js.map +1 -1
  42. package/dist/core/final.cjs +6 -2
  43. package/dist/core/final.cjs.map +1 -1
  44. package/dist/core/final.d.ts.map +1 -1
  45. package/dist/core/final.js +4 -1
  46. package/dist/core/final.js.map +1 -1
  47. package/dist/core/ll_client.cjs +60 -16
  48. package/dist/core/ll_client.cjs.map +1 -1
  49. package/dist/core/ll_client.d.ts +23 -7
  50. package/dist/core/ll_client.d.ts.map +1 -1
  51. package/dist/core/ll_client.js +62 -17
  52. package/dist/core/ll_client.js.map +1 -1
  53. package/dist/core/ll_transaction.cjs +3 -1
  54. package/dist/core/ll_transaction.cjs.map +1 -1
  55. package/dist/core/ll_transaction.d.ts +1 -1
  56. package/dist/core/ll_transaction.d.ts.map +1 -1
  57. package/dist/core/ll_transaction.js +2 -1
  58. package/dist/core/ll_transaction.js.map +1 -1
  59. package/dist/core/stat.cjs.map +1 -1
  60. package/dist/core/stat.js.map +1 -1
  61. package/dist/core/transaction.cjs +91 -4
  62. package/dist/core/transaction.cjs.map +1 -1
  63. package/dist/core/transaction.d.ts +52 -2
  64. package/dist/core/transaction.d.ts.map +1 -1
  65. package/dist/core/transaction.js +90 -5
  66. package/dist/core/transaction.js.map +1 -1
  67. package/dist/core/tree_filter.cjs +73 -18
  68. package/dist/core/tree_filter.cjs.map +1 -1
  69. package/dist/core/tree_filter.d.ts +1 -1
  70. package/dist/core/tree_filter.d.ts.map +1 -1
  71. package/dist/core/tree_filter.js +72 -19
  72. package/dist/core/tree_filter.js.map +1 -1
  73. package/dist/core/type_conversion.cjs +19 -20
  74. package/dist/core/type_conversion.cjs.map +1 -1
  75. package/dist/core/type_conversion.js +19 -19
  76. package/dist/core/type_conversion.js.map +1 -1
  77. package/dist/core/types.cjs +2 -3
  78. package/dist/core/types.cjs.map +1 -1
  79. package/dist/core/types.d.ts.map +1 -1
  80. package/dist/core/types.js +2 -2
  81. package/dist/core/types.js.map +1 -1
  82. package/dist/core/unauth_client.cjs +7 -6
  83. package/dist/core/unauth_client.cjs.map +1 -1
  84. package/dist/core/unauth_client.d.ts +9 -0
  85. package/dist/core/unauth_client.d.ts.map +1 -1
  86. package/dist/core/unauth_client.js +7 -5
  87. package/dist/core/unauth_client.js.map +1 -1
  88. package/dist/core/user_resources.cjs +57 -1
  89. package/dist/core/user_resources.cjs.map +1 -1
  90. package/dist/core/user_resources.d.ts +55 -1
  91. package/dist/core/user_resources.d.ts.map +1 -1
  92. package/dist/core/user_resources.js +57 -0
  93. package/dist/core/user_resources.js.map +1 -1
  94. package/dist/core/websocket_stream.cjs +17 -20
  95. package/dist/core/websocket_stream.cjs.map +1 -1
  96. package/dist/core/websocket_stream.js +16 -19
  97. package/dist/core/websocket_stream.js.map +1 -1
  98. package/dist/core/wire.d.ts.map +1 -1
  99. package/dist/helpers/pl.cjs +25 -0
  100. package/dist/helpers/pl.cjs.map +1 -1
  101. package/dist/helpers/pl.d.ts +1 -1
  102. package/dist/helpers/pl.d.ts.map +1 -1
  103. package/dist/helpers/pl.js +1 -1
  104. package/dist/helpers/pl.js.map +1 -1
  105. package/dist/helpers/poll.cjs +5 -1
  106. package/dist/helpers/poll.cjs.map +1 -1
  107. package/dist/helpers/poll.d.ts.map +1 -1
  108. package/dist/helpers/poll.js +4 -0
  109. package/dist/helpers/poll.js.map +1 -1
  110. package/dist/helpers/retry_strategy.cjs +1 -0
  111. package/dist/helpers/retry_strategy.cjs.map +1 -1
  112. package/dist/helpers/retry_strategy.js +1 -1
  113. package/dist/helpers/retry_strategy.js.map +1 -1
  114. package/dist/helpers/tx_helpers.cjs +0 -1
  115. package/dist/helpers/tx_helpers.cjs.map +1 -1
  116. package/dist/helpers/tx_helpers.d.ts.map +1 -1
  117. package/dist/helpers/tx_helpers.js.map +1 -1
  118. package/dist/index.cjs +3 -1
  119. package/dist/index.d.ts +3 -3
  120. package/dist/index.js +2 -2
  121. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs +0 -1
  122. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs.map +1 -1
  123. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts.map +1 -1
  124. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.js.map +1 -1
  125. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +537 -83
  126. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
  127. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +11 -4
  128. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
  129. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +12 -6
  130. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
  131. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +11 -3
  132. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
  133. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +112 -7
  134. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
  135. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +336 -83
  136. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
  137. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs +23 -7
  138. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs.map +1 -1
  139. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +10 -3
  140. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
  141. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js +18 -7
  142. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js.map +1 -1
  143. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs +1 -2
  144. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs.map +1 -1
  145. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
  146. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js +1 -1
  147. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js.map +1 -1
  148. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs +2 -2
  149. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs.map +1 -1
  150. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts +1 -1
  151. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts.map +1 -1
  152. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js +2 -2
  153. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js.map +1 -1
  154. package/dist/proto-grpc/google/protobuf/any.cjs +1 -2
  155. package/dist/proto-grpc/google/protobuf/any.cjs.map +1 -1
  156. package/dist/proto-grpc/google/protobuf/any.d.ts.map +1 -1
  157. package/dist/proto-grpc/google/protobuf/any.js +1 -1
  158. package/dist/proto-grpc/google/protobuf/any.js.map +1 -1
  159. package/dist/proto-grpc/google/protobuf/duration.cjs +0 -1
  160. package/dist/proto-grpc/google/protobuf/duration.cjs.map +1 -1
  161. package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -1
  162. package/dist/proto-grpc/google/protobuf/duration.js.map +1 -1
  163. package/dist/proto-grpc/google/protobuf/timestamp.cjs +0 -1
  164. package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
  165. package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
  166. package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
  167. package/dist/proto-grpc/google/rpc/code.d.ts.map +1 -1
  168. package/dist/proto-rest/index.cjs +1 -1
  169. package/dist/proto-rest/index.cjs.map +1 -1
  170. package/dist/proto-rest/index.d.ts +1 -1
  171. package/dist/proto-rest/index.d.ts.map +1 -1
  172. package/dist/proto-rest/index.js.map +1 -1
  173. package/dist/proto-rest/plapi.d.ts +30 -9
  174. package/dist/proto-rest/plapi.d.ts.map +1 -1
  175. package/dist/test/tcp-proxy.cjs +2 -2
  176. package/dist/test/tcp-proxy.cjs.map +1 -1
  177. package/dist/test/tcp-proxy.d.ts +1 -1
  178. package/dist/test/tcp-proxy.js.map +1 -1
  179. package/dist/test/test_config.cjs +8 -2
  180. package/dist/test/test_config.cjs.map +1 -1
  181. package/dist/test/test_config.d.ts +1 -1
  182. package/dist/test/test_config.d.ts.map +1 -1
  183. package/dist/test/test_config.js +1 -1
  184. package/dist/test/test_config.js.map +1 -1
  185. package/dist/util/pl.cjs.map +1 -1
  186. package/dist/util/pl.js.map +1 -1
  187. package/dist/util/util.cjs.map +1 -1
  188. package/dist/util/util.js.map +1 -1
  189. package/package.json +4 -4
  190. package/src/core/capabilities.ts +10 -3
  191. package/src/core/client.ts +30 -3
  192. package/src/core/errors.ts +2 -0
  193. package/src/core/final.ts +4 -0
  194. package/src/core/ll_client.test.ts +15 -2
  195. package/src/core/ll_client.ts +66 -11
  196. package/src/core/transaction.ts +103 -3
  197. package/src/core/unauth_client.ts +17 -2
  198. package/src/core/user_resources.ts +85 -0
  199. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +16 -3
  200. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +335 -12
  201. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +21 -7
  202. package/src/proto-rest/plapi.ts +29 -8
  203. package/dist/proto-grpc/google/rpc/code.cjs +0 -224
  204. package/dist/proto-grpc/google/rpc/code.cjs.map +0 -1
  205. package/dist/proto-grpc/google/rpc/code.js +0 -224
  206. 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 = ConnectionState.NEW;
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 < ConnectionState.CONNECTED) {
43
+ if (this.connectionState < 2) {
48
44
  this.ws?.close();
49
45
  this.onClose();
50
46
  return;
51
47
  }
52
- if (!this.progressConnectionState(ConnectionState.CLOSING)) return;
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
- this.reconnection = new require_retry_strategy.RetryStrategy(this.options.retryConfig ?? {}, {
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(ConnectionState.CLOSED);
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(ConnectionState.CONNECTING)) return;
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(ConnectionState.CONNECTED);
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 < ConnectionState.CONNECTED) {
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(ConnectionState.CLOSED);
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 === ConnectionState.CONNECTED;
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 === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false;
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 = ConnectionState.NEW;
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 < ConnectionState.CONNECTED) {
41
+ if (this.connectionState < 2) {
46
42
  this.ws?.close();
47
43
  this.onClose();
48
44
  return;
49
45
  }
50
- if (!this.progressConnectionState(ConnectionState.CLOSING)) return;
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
- this.reconnection = new RetryStrategy(this.options.retryConfig ?? {}, {
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(ConnectionState.CLOSED);
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(ConnectionState.CONNECTING)) return;
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(ConnectionState.CONNECTED);
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 < ConnectionState.CONNECTED) {
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(ConnectionState.CLOSED);
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 === ConnectionState.CONNECTED;
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 === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false;
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":";;;;;;;;;AAwCA,IAAa,sBAAb,MAG8C;CAgEzB;CACA;CACA;CACA;CAjEnB,KAA+B;CAC/B,kBAAQ;CACR;CAGA,YAA6B,IAAI,OAAiC;CAClE,gBAAwB;CACxB;CAKA,gBAAiC,IAAI,OAAkB;CACvD,oBAA2D,CAAC;CAG5D;CAGA;CAIA,WAA2B;EACzB,MAAM,OAAO,YAAsC;GACjD,OAAO,MAAM,KAAK,YAAY,OAAO;EACvC;EAEA,UAAU,YAA2B;GACnC,IAAI,KAAK,eAAe;GAExB,MAAM,KAAK,eAAe;GAC1B,IAAI;IACF,MAAM,KAAK,WAAW,IAAI;GAC5B,QAAQ,CAIR;GACA,KAAK,gBAAgB;EACvB;CACF;CAEA,YAAsD,GACnD,OAAO,sBAAsB,KAAK,uBAAuB,EAC5D;CAEA,QAAqB;EACnB,KAAK,aAAa,OAAO;EAEzB,IAAI,KAAK,kBAAA,GAA6C;GAEpD,KAAK,IAAI,MAAM;GACf,KAAK,QAAQ;GACb;EACF;EAEA,IAAI,CAAC,KAAK,wBAAA,CAA+C,GAAG;EAC5D,KAAK,GAAI,MAAM;CACjB;CAEA,YACE,KACA,wBACA,oBACA,UAAkE,CAAC,GACnE;EAJiB,KAAA,MAAA;EACA,KAAA,yBAAA;EACA,KAAA,qBAAA;EACA,KAAA,UAAA;EAEjB,KAAK,aAAa,KAAK,QAAQ,gBAAgB,WAAW,OAAO,MAAM;EAEvE,MAAM,cAAc,KAAK,QAAQ,eAAe,CAAC;EACjD,KAAK,eAAe,IAAI,cAAc,aAAa;GACjD,eAAe;IACb,KAAU,QAAQ;GACpB;GACA,uBAAuB,UAAU,KAAK,YAAY,KAAK;EACzD,CAAC;EAED,IAAI,KAAK,QAAQ,aAAa,SAAS;GACrC,KAAK,wBAAA,CAA8C;GACnD;EACF;EAEA,KAAK,qBAAqB,KAAK,MAAM;EACrC,KAAK,QAAQ,aAAa,iBAAiB,SAAS,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;EACrF,KAAK,QAAQ;CACf;CAIA,UAAwB;EACtB,IAAI,KAAK,QAAQ,aAAa,SAAS;EAGvC,IAAI,CAAC,KAAK,wBAAA,CAAkD,GAAG;EAE/D,IAAI;GACF,KAAK,KAAK,KAAK,gBAAgB;GAE/B,KAAK,GAAG,iBAAiB,cAAc,KAAK,OAAO,CAAC;GACpD,KAAK,GAAG,iBAAiB,YAAY,UAAU,KAAK,UAAU,MAAM,IAAI,CAAC;GACzE,KAAK,GAAG,iBAAiB,UAAU,UAAU,KAAK,QAAQ,KAAK,CAAC;GAChE,KAAK,GAAG,iBAAiB,eAAe,KAAK,QAAQ,CAAC;EACxD,SAAS,OAAO;GACd,KAAK,YAAY,KAAK,QAAQ,KAAK;GACnC,KAAK,aAAa,SAAS;EAC7B;CACF;CAEA,kBAAqC;EACnC,MAAM,UAAyB,CAAC;EAEhC,IAAI,KAAK,QAAQ,UACf,QAAQ,UAAU,EAAE,eAAe,UAAU,KAAK,QAAQ,WAAW;EACvE,IAAI,KAAK,QAAQ,YAAY,QAAQ,aAAa,KAAK,QAAQ;EAE/D,MAAM,KAAK,IAAI,UAAU,KAAK,KAAK,OAAO;EAC1C,GAAG,aAAa;EAChB,OAAO;CACT;CAEA,SAAuB;EACrB,KAAK,wBAAA,CAAiD;EACtD,KAAK,iBAAiB;CACxB;CAEA,UAAkB,MAAqB;EACrC,IAAI,EAAE,gBAAgB,cAAc;GAClC,KAAK,4BAAY,IAAI,MAAM,iCAAiC,OAAO,MAAM,CAAC;GAC1E;EACF;EAEA,IAAI;GACF,MAAM,UAAU,KAAK,mBAAmB,IAAI,WAAW,IAAI,CAAC;GAC5D,KAAK,gBAAgB,OAAO;EAC9B,SAAS,OAAO;GACd,KAAK,YAAY,KAAK,QAAQ,KAAK,CAAC;EACtC;CACF;CAEA,QAAgB,OAAsB;EACpC,IAAI,KAAK,kBAAA,GAA6C;GAEpD,KAAK,YAAY,KAAK,QAAQ,KAAK;GACnC,KAAK,aAAa,SAAS;GAC3B;EACF;EAEA,KAAK,YAAY,KAAK,QAAQ,KAAK,CAAC;CACtC;CAEA,UAAwB;EACtB,KAAK,wBAAA,CAA8C;EAGnD,IAAI,KAAK,cACP,KAAK,QAAQ,aAAa,oBAAoB,SAAS,KAAK,YAAY;EAI1E,IAAI,KAAK,QAAQ,aAAa,WAAW,CAAC,KAAK,WAAW;GACxD,MAAM,SAAS,KAAK,QAAQ,YAAY;GACxC,IAAI,kBAAkB,OACpB,KAAK,YAAY;QACZ,IAAI,WAAW,KAAA,GACpB,KAAK,YAAY,IAAI,MAAM,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;QAE5D,KAAK,YAAY,KAAK,wBAAwB;EAElD;EAEA,IAAI,CAAC,KAAK,WAAW;GACnB,KAAK,wBAAwB,KAAK,wBAAwB,CAAC;GAC3D,KAAK,2BAA2B;EAClC,OACE,KAAK,2BAA2B,KAAK,SAAS;CAElD;CAIA,YAAoB,SAAmC;EACrD,IAAI,KAAK,eACP,MAAM,IAAI,MAAM,uCAAuC;EAGzD,IAAI,KAAK,QAAQ,aAAa,SAC5B,MAAM,IAAI,MAAM,6BAA6B;EAG/C,OAAO,IAAI,SAAe,SAAS,WAAW;GAC5C,KAAK,UAAU,KAAK;IAAE;IAAS;IAAS;GAAO,CAAC;GAChD,KAAK,iBAAiB;EACxB,CAAC;CACH;CAEA,mBAAiC;EAC/B,IAAI,CAAC,KAAK,gBAAgB,GAAG;EAE7B,OAAO,KAAK,UAAU,SAAS,GAAG;GAChC,MAAM,SAAS,KAAK,UAAU,MAAM;GACpC,KAAK,kBAAkB,MAAM;EAC/B;CACF;CAEA,kBAAmC;EACjC,OAAO,KAAK,oBAAA;CACd;CAEA,kBAA0B,QAAwC;EAChE,IAAI;GACF,MAAM,KAAK,KAAK;GAChB,IAAI,CAAC,IACH,MAAM,IAAI,MAAM,4BAA4B;GAI9C,IAAI,GAAG,eAAe,UAAU,MAC9B,MAAM,IAAI,MAAM,sCAAsC,GAAG,WAAW,EAAE;GAGxE,MAAM,SAAS,KAAK,uBAAuB,OAAO,OAAO;GACzD,GAAG,KAAK,MAAM;GACd,OAAO,QAAQ;EACjB,SAAS,OAAO;GACd,OAAO,OAAO,KAAK,QAAQ,KAAK,CAAC;EACnC;CACF;CAEA,MAAc,iBAAgC;EAC5C,MAAM,mBAAmB;EAEzB,OAAO,KAAK,UAAU,SAAS,GAC7B,MAAM,KAAK,uBAAuB,KAAK,UAAU,WAAW,GAAG,gBAAgB;CAEnF;CAEA,iBAAyB,WAA0B,YAAmC;EACpF,OAAO,IAAI,SAAe,SAAS,WAAW;GAC5C,IAAI,KAAK,QAAQ,aAAa,SAC5B,OAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,YAAY,MAAM,qBAAK,IAAI,MAAM,gBAAgB,CAAC;GAG5F,IAAI;GACJ,MAAM,gBAAgB;IACpB,aAAa,SAAS;IACtB,OAAO,KAAK,QAAQ,KAAK,QAAQ,aAAa,MAAM,qBAAK,IAAI,MAAM,gBAAgB,CAAC;GACtF;GAEA,KAAK,QAAQ,aAAa,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;GAE3E,MAAM,cAAc;IAClB,IAAI,UAAU,KAAK,KAAK,cAAc,GAAG;KACvC,KAAK,QAAQ,aAAa,oBAAoB,SAAS,OAAO;KAC9D,QAAQ;IACV,OACE,YAAY,WAAW,OAAO,UAAU;GAE5C;GAEA,MAAM;EACR,CAAC;CACH;CAIA,gBAAwB,SAA0B;EAChD,IAAI,KAAK,kBAAkB,SAAS,GAElC,KADsB,kBAAkB,MACjC,CAAC,CAAC,QAAQ;GAAE,OAAO;GAAS,MAAM;EAAM,CAAC;OAEhD,KAAK,cAAc,KAAK,OAAO;CAEnC;CAEA,OAAe,yBAAmD;EAChE,OAAO,MAAM;GACX,MAAM,SAAS,MAAM,KAAK,aAAa;GAEvC,IAAI,OAAO,MAAM;GAEjB,MAAM,OAAO;EACf;CACF;CAEA,eAA2D;EACzD,OAAO,IAAI,SAAoC,SAAS,WAAW;GAEjE,IAAI,KAAK,cAAc,SAAS,GAAG;IAEjC,QAAQ;KAAE,OADM,KAAK,cAAc,MACZ;KAAG,MAAM;IAAM,CAAC;IACvC;GACF;GAGA,IAAI,KAAK,cAAc,GAAG;IACxB,IAAI,KAAK,WACP,OAAO,KAAK,SAAS;SAErB,QAAQ;KAAE,OAAO,KAAA;KAAkB,MAAM;IAAK,CAAC;IAEjD;GACF;GAGA,KAAK,kBAAkB,KAAK;IAAE;IAAS;GAAO,CAAC;EACjD,CAAC;CACH;CAEA,6BAA2C;EACzC,OAAO,KAAK,kBAAkB,SAAS,GAErC,KADsB,kBAAkB,MACjC,CAAC,CAAC,QAAQ;GAAE,OAAO,KAAA;GAAkB,MAAM;EAAK,CAAC;CAE5D;CAIA,YAAoB,OAAoB;EACtC,KAAK,YAAY;EACjB,KAAK,MAAM;CACb;CAEA,2BAAmC,OAAoB;EACrD,KAAK,wBAAwB,KAAK;EAClC,KAAK,2BAA2B,KAAK;CACvC;CAEA,wBAAgC,OAAoB;EAClD,OAAO,KAAK,UAAU,SAAS,GAE7B,KADoB,UAAU,MACzB,CAAC,CAAC,OAAO,KAAK;CAEvB;CAEA,2BAAmC,OAAoB;EACrD,OAAO,KAAK,kBAAkB,SAAS,GAErC,KADsB,kBAAkB,MACjC,CAAC,CAAC,OAAO,KAAK;CAEzB;CAEA,0BAAyC;EACvC,IAAI,KAAK,QAAQ,aAAa,SAAS;GACrC,MAAM,SAAS,KAAK,QAAQ,YAAY;GACxC,IAAI,kBAAkB,OACpB,OAAO;GAET,OAAO,IAAI,MAAM,kBAAkB,EAAE,OAAO,OAAO,CAAC;EACtD;EAEA,uBAAO,IAAI,MAAM,eAAe;CAClC;CAIA,gBAAiC;EAC/B,OACE,KAAK,oBAAA,KAA8C,KAAK,QAAQ,aAAa,WAAW;CAE5F;CAEA,QAAgB,OAAuB;EACrC,IAAI,iBAAiB,OAAO,OAAO;EACnC,IAAI,iBAAiB,YAAY;GAC/B,MAAM,MAAM,MAAM;GAGlB,IAAI,eAAe,aAAa,CAAC,IAAI,SACnC,OAAO,IAAI,kBAAkB,0CAA0C;GAEzE,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,mBAAmB,EAAE,OAAO,MAAM,CAAC;EACnF;EACA,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;CAChC;;;;;;;CAQA,wBAAgC,UAAoC;EAClE,IAAI,WAAW,KAAK,iBAClB,OAAO;EAET,KAAK,kBAAkB;EACvB,OAAO;CACT;AACF"}
@@ -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,aAAA;AAAA;AAAA,KAGD,cAAA,GAAiB,cAAA,GAAiB,cAAA;;;;;AAL9C;UAciB,kBAAA;EACf,GAAA,IAAO,MAAA;AAAA;;;;UAMQ,yBAAA;EACf,wBAAA,SACE,iBAAA,GAAoB,SAAA,EAAW,cAAA,KAAmB,MAAA,GACjD,kBAAA,CAAmB,MAAA;AAAA"}
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"}
@@ -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