@ckb-ccc/core 2.0.0-next.0 → 2.0.0-next.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 (89) hide show
  1. package/CHANGELOG.md +133 -1
  2. package/README.md +3 -3
  3. package/dist/advanced.d.mts +2 -2
  4. package/dist/advanced.mjs +1 -1
  5. package/{dist.commonjs/advancedBarrel-DNdhwiU0.d.ts → dist/advancedBarrel-DfFMFhdl.d.mts} +2790 -2237
  6. package/dist/advancedBarrel-DfFMFhdl.d.mts.map +1 -0
  7. package/dist/advancedBarrel.d.mts +2 -2
  8. package/dist/advancedBarrel.mjs +1 -1
  9. package/dist/advancedBarrel.mjs.map +1 -1
  10. package/dist/barrel-ONYEMVg5.mjs +7 -0
  11. package/dist/barrel-ONYEMVg5.mjs.map +1 -0
  12. package/dist/barrel.d.mts +2 -2
  13. package/dist/barrel.mjs +1 -1
  14. package/dist/index.d.mts +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/transformers-CduZ1Js5.mjs +2 -0
  17. package/dist/transformers-CduZ1Js5.mjs.map +1 -0
  18. package/dist.commonjs/advanced.d.ts +2 -2
  19. package/dist.commonjs/advanced.js +1 -1
  20. package/{dist/advancedBarrel-BHwnMJqR.d.mts → dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts} +2791 -2236
  21. package/dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts.map +1 -0
  22. package/dist.commonjs/advancedBarrel.d.ts +2 -2
  23. package/dist.commonjs/advancedBarrel.js +1 -1
  24. package/dist.commonjs/advancedBarrel.js.map +1 -1
  25. package/dist.commonjs/barrel-HfgYMxP_.js +23 -0
  26. package/dist.commonjs/barrel-HfgYMxP_.js.map +1 -0
  27. package/dist.commonjs/barrel.d.ts +2 -2
  28. package/dist.commonjs/barrel.js +1 -1
  29. package/dist.commonjs/index.d.ts +2 -2
  30. package/dist.commonjs/index.js +1 -1
  31. package/dist.commonjs/{rolldown-runtime-DWNI8pZO.js → rolldown-runtime-Chgba0Kb.js} +1 -1
  32. package/dist.commonjs/transformers-BUlLwiyt.js +2 -0
  33. package/dist.commonjs/transformers-BUlLwiyt.js.map +1 -0
  34. package/package.json +8 -7
  35. package/src/advancedBarrel.ts +1 -0
  36. package/src/bytes/advanced.ts +1 -6
  37. package/src/bytes/index.ts +37 -10
  38. package/src/ckb/transaction.ts +7 -5
  39. package/src/client/client.ts +83 -21
  40. package/src/client/clientPublicMainnet.advanced.ts +9 -0
  41. package/src/client/clientPublicMainnet.ts +75 -36
  42. package/src/client/clientPublicTestnet.advanced.ts +9 -0
  43. package/src/client/clientPublicTestnet.ts +75 -36
  44. package/src/client/jsonRpc/client.ts +77 -30
  45. package/src/client/jsonRpc/transformers.ts +4 -4
  46. package/src/jsonRpc/index.ts +1 -0
  47. package/src/jsonRpc/requestor.ts +106 -29
  48. package/src/jsonRpc/transports/advanced.ts +0 -4
  49. package/src/jsonRpc/transports/factory.ts +12 -5
  50. package/src/jsonRpc/transports/fallback.ts +14 -6
  51. package/src/jsonRpc/transports/http.ts +19 -12
  52. package/src/jsonRpc/transports/index.ts +4 -0
  53. package/src/jsonRpc/transports/transport.ts +42 -6
  54. package/src/jsonRpc/transports/webSocket.ts +145 -40
  55. package/src/keystore/index.ts +3 -3
  56. package/src/molecule/codec.ts +20 -17
  57. package/src/signer/advanced.ts +1 -0
  58. package/src/signer/btc/psbt.ts +4 -2
  59. package/src/signer/btc/verify.ts +2 -2
  60. package/src/signer/ckb/secp256k1Signing.ts +3 -3
  61. package/src/signer/doge/signerDoge.ts +1 -1
  62. package/src/signer/doge/signerDogePrivateKey.ts +1 -1
  63. package/src/signer/index.ts +1 -0
  64. package/src/signer/jsonRpc/handler.ts +478 -0
  65. package/src/signer/jsonRpc/index.ts +2 -0
  66. package/src/signer/jsonRpc/network.ts +15 -0
  67. package/src/signer/jsonRpc/signer.ts +378 -0
  68. package/src/signer/jsonRpc/transformers.ts +71 -0
  69. package/src/signer/signer/index.ts +5 -13
  70. package/src/utils/abortSignal.ts +56 -0
  71. package/src/utils/constructor.ts +11 -0
  72. package/src/utils/index.ts +172 -5
  73. package/src/utils/owner/aggregated.ts +56 -0
  74. package/src/utils/owner/index.ts +4 -0
  75. package/src/utils/owner/owner.ts +141 -0
  76. package/src/utils/owner/refCount.ts +78 -0
  77. package/src/utils/owner/unique.ts +30 -0
  78. package/src/utils/proxy.ts +165 -0
  79. package/tsdown.config.mts +1 -0
  80. package/dist/address.advanced-LxntWbev.mjs +0 -2
  81. package/dist/address.advanced-LxntWbev.mjs.map +0 -1
  82. package/dist/advancedBarrel-BHwnMJqR.d.mts.map +0 -1
  83. package/dist/barrel-C-BaV9Pe.mjs +0 -7
  84. package/dist/barrel-C-BaV9Pe.mjs.map +0 -1
  85. package/dist.commonjs/address.advanced-BqcYOpZn.js +0 -2
  86. package/dist.commonjs/address.advanced-BqcYOpZn.js.map +0 -1
  87. package/dist.commonjs/advancedBarrel-DNdhwiU0.d.ts.map +0 -1
  88. package/dist.commonjs/barrel-BBOmj4RJ.js +0 -21
  89. package/dist.commonjs/barrel-BBOmj4RJ.js.map +0 -1
@@ -1,55 +1,94 @@
1
1
  import WebSocket from "isomorphic-ws";
2
+ import { RequestorJsonRpc } from "../jsonRpc/requestor.js";
3
+ import type { ClientConfig } from "./client.js";
2
4
  import { TESTNET_SCRIPTS } from "./clientPublicTestnet.advanced.js";
3
- import { ScriptInfo, ScriptInfoLike } from "./clientTypes.js";
4
- import { ClientJsonRpc, ClientJsonRpcConfig } from "./jsonRpc/index.js";
5
- import { KnownScript } from "./knownScript.js";
5
+ import { ClientJsonRpc, type ClientJsonRpcConfig } from "./jsonRpc/client.js";
6
6
 
7
7
  /**
8
8
  * @public
9
9
  */
10
10
  export class ClientPublicTestnet extends ClientJsonRpc {
11
+ private static defaultUrls(): readonly [string, ...string[]] {
12
+ return typeof WebSocket !== "undefined"
13
+ ? [
14
+ "wss://testnet.ckb.dev/ws",
15
+ "https://testnet.ckb.dev/",
16
+ "https://testnet.ckbapp.dev/",
17
+ ]
18
+ : ["https://testnet.ckb.dev/", "https://testnet.ckbapp.dev/"];
19
+ }
20
+
21
+ private static resolveConfig(
22
+ config?: ClientJsonRpcConfig & {
23
+ /** @deprecated URL belongs to Transport construction. */
24
+ url?: string;
25
+ },
26
+ ): ClientJsonRpcConfig & { url: string; fallbacks: string[] } {
27
+ const defaultUrls = this.defaultUrls();
28
+ return {
29
+ ...config,
30
+ url: config?.url ?? defaultUrls[0],
31
+ fallbacks: config?.fallbacks ?? [...defaultUrls],
32
+ scripts: config?.scripts ?? TESTNET_SCRIPTS,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @deprecated Use {@link ClientPublicTestnet.new} with a borrowed Transport or
38
+ * {@link ClientPublicTestnet.open} when creating Transports.
39
+ */
11
40
  constructor(
12
- private readonly config?: ClientJsonRpcConfig & {
41
+ config?: ClientJsonRpcConfig & {
42
+ /** @deprecated URL belongs to Transport construction. */
13
43
  url?: string;
14
- scripts?: Record<KnownScript, ScriptInfoLike | undefined>;
15
44
  },
16
45
  ) {
17
- const hasWebSocket = typeof WebSocket !== "undefined";
18
- super(
19
- config?.url ??
20
- (hasWebSocket
21
- ? "wss://testnet.ckb.dev/ws"
22
- : "https://testnet.ckb.dev/"),
23
- {
24
- ...config,
25
- fallbacks:
26
- config?.fallbacks ??
27
- (hasWebSocket
28
- ? [
29
- "wss://testnet.ckb.dev/ws",
30
- "https://testnet.ckb.dev/",
31
- "https://testnet.ckbapp.dev/",
32
- ]
33
- : ["https://testnet.ckb.dev/", "https://testnet.ckbapp.dev/"]),
34
- },
35
- );
46
+ const resolved = ClientPublicTestnet.resolveConfig(config);
47
+ super(resolved.url, resolved);
36
48
  }
37
49
 
38
- get scripts(): Record<KnownScript, ScriptInfoLike | undefined> {
39
- return this.config?.scripts ?? TESTNET_SCRIPTS;
50
+ /** Creates a Client that borrows an existing Transport. */
51
+ static new(
52
+ config: Omit<Parameters<typeof RequestorJsonRpc.new>[0], "onError"> &
53
+ ClientConfig,
54
+ ): ClientPublicTestnet {
55
+ const { cache, scripts, ...requestorConfig } = config;
56
+ const requestor = this.newRequestor(requestorConfig);
57
+ return new ClientPublicTestnet({ cache, scripts, requestor });
40
58
  }
41
59
 
42
- get addressPrefix(): string {
43
- return "ckt";
60
+ /** Opens a Client with explicit ownership of its default dependencies. */
61
+ static open(
62
+ config?: Omit<
63
+ Parameters<typeof RequestorJsonRpc.open>[0],
64
+ "onError" | "urls"
65
+ > &
66
+ ClientConfig & {
67
+ urls?: Parameters<typeof RequestorJsonRpc.open>[0]["urls"];
68
+ },
69
+ ) {
70
+ const {
71
+ cache,
72
+ scripts,
73
+ urls = this.defaultUrls(),
74
+ ...requestorConfig
75
+ } = config ?? {};
76
+ return this.openRequestor({
77
+ ...requestorConfig,
78
+ urls,
79
+ }).map(
80
+ (requestor) =>
81
+ new ClientPublicTestnet({
82
+ cache,
83
+ scripts,
84
+ requestor,
85
+ url: urls[0],
86
+ fallbacks: [...urls.slice(1)],
87
+ }),
88
+ );
44
89
  }
45
90
 
46
- async getKnownScript(script: KnownScript): Promise<ScriptInfo> {
47
- const found = this.scripts[script];
48
- if (!found) {
49
- throw new Error(
50
- `No script information was found for ${script} on ${this.addressPrefix}`,
51
- );
52
- }
53
- return ScriptInfo.from(found);
91
+ get addressPrefix(): string {
92
+ return "ckt";
54
93
  }
55
94
  }
@@ -10,8 +10,8 @@ import {
10
10
  RequestorJsonRpcConfig,
11
11
  } from "../../jsonRpc/requestor.js";
12
12
  import { Num, NumLike, numFrom, numToHex } from "../../num/index.js";
13
- import { apply } from "../../utils/index.js";
14
- import { ClientCache } from "../cache/index.js";
13
+ import { Owner, apply } from "../../utils/index.js";
14
+ import type { ClientConfig } from "../client.js";
15
15
  import { Client } from "../client.js";
16
16
  import {
17
17
  ClientFindCellsResponse,
@@ -67,10 +67,35 @@ const ERROR_PARSERS: [
67
67
  ],
68
68
  ];
69
69
 
70
- export type ClientJsonRpcConfig = RequestorJsonRpcConfig & {
71
- cache?: ClientCache;
72
- requestor?: RequestorJsonRpc;
73
- };
70
+ function handleJsonRpcError(errAny: unknown): never {
71
+ if (
72
+ typeof errAny !== "object" ||
73
+ errAny === null ||
74
+ !("data" in errAny) ||
75
+ typeof errAny.data !== "string"
76
+ ) {
77
+ throw errAny;
78
+ }
79
+ const err = errAny as ErrorClientBaseLike;
80
+
81
+ for (const [regexp, builder] of ERROR_PARSERS) {
82
+ const match = err.data.match(regexp);
83
+ if (match) {
84
+ throw builder(err, match);
85
+ }
86
+ }
87
+
88
+ throw new ErrorClientBase(err);
89
+ }
90
+
91
+ export type ClientJsonRpcConfig = RequestorJsonRpcConfig &
92
+ ClientConfig & {
93
+ /**
94
+ * @deprecated Requestor injection is supported only by legacy constructors.
95
+ * Use a borrowed Transport with `Client.new` or let `Client.open` create one.
96
+ */
97
+ requestor?: RequestorJsonRpc;
98
+ };
74
99
 
75
100
  /**
76
101
  * An abstract class implementing JSON-RPC client functionality for a specific URL and timeout.
@@ -84,42 +109,64 @@ export abstract class ClientJsonRpc extends Client {
84
109
  *
85
110
  * @param url_ - The URL of the JSON-RPC server.
86
111
  * @param timeout - The timeout for requests in milliseconds
112
+ * @deprecated Use the concrete Client's `new` or `open` method.
87
113
  */
88
114
 
89
- constructor(url_: string, config?: ClientJsonRpcConfig) {
115
+ constructor(
116
+ private readonly url_: string,
117
+ config?: ClientJsonRpcConfig,
118
+ ) {
90
119
  super(config);
91
120
 
92
- this.requestor =
93
- config?.requestor ??
94
- new RequestorJsonRpc(url_, config, (errAny) => {
95
- if (
96
- typeof errAny !== "object" ||
97
- errAny === null ||
98
- !("data" in errAny) ||
99
- typeof errAny.data !== "string"
100
- ) {
101
- throw errAny;
102
- }
103
- const err = errAny as ErrorClientBaseLike;
104
-
105
- for (const [regexp, builder] of ERROR_PARSERS) {
106
- const match = err.data.match(regexp);
107
- if (match) {
108
- throw builder(err, match);
109
- }
110
- }
111
-
112
- throw new ErrorClientBase(err);
121
+ const requestor = config?.requestor;
122
+ if (requestor) {
123
+ this.requestor = requestor;
124
+ } else if (config?.transport) {
125
+ this.requestor = RequestorJsonRpc.new({
126
+ transport: config.transport,
127
+ maxConcurrent: config.maxConcurrent,
128
+ onError: handleJsonRpcError,
113
129
  });
130
+ } else {
131
+ // Legacy constructor intentionally discards ownership.
132
+ this.requestor = RequestorJsonRpc.open({
133
+ urls: [url_, ...(config?.fallbacks ?? [])],
134
+ timeout: config?.timeout,
135
+ maxConcurrent: config?.maxConcurrent,
136
+ onError: handleJsonRpcError,
137
+ }).value;
138
+ }
139
+ }
140
+
141
+ /** Creates a Requestor that borrows an existing Transport. */
142
+ protected static newRequestor(
143
+ config: Omit<Parameters<typeof RequestorJsonRpc.new>[0], "onError">,
144
+ ): RequestorJsonRpc {
145
+ return RequestorJsonRpc.new({
146
+ ...config,
147
+ onError: handleJsonRpcError,
148
+ });
149
+ }
150
+
151
+ /** Opens a Requestor with ownership of its default Transport. */
152
+ protected static openRequestor(
153
+ config: Omit<Parameters<typeof RequestorJsonRpc.open>[0], "onError">,
154
+ ): Owner<RequestorJsonRpc> {
155
+ return RequestorJsonRpc.open({
156
+ ...config,
157
+ onError: handleJsonRpcError,
158
+ });
114
159
  }
115
160
 
116
161
  /**
117
- * Returns the URL of the JSON-RPC server.
162
+ * Returns the legacy primary URL of the JSON-RPC server.
118
163
  *
119
164
  * @returns The URL of the JSON-RPC server.
165
+ * @deprecated A Client may use multiple endpoints or a Transport without a
166
+ * URL, so this value does not reliably identify its connection.
120
167
  */
121
168
  get url(): string {
122
- return this.requestor.url;
169
+ return this.url_;
123
170
  }
124
171
 
125
172
  /**
@@ -212,10 +212,10 @@ export class JsonRpcTransformers {
212
212
  return {
213
213
  compactTarget: numFrom(header.compact_target),
214
214
  dao: {
215
- c: numLeFromBytes(dao.slice(0, 8)),
216
- ar: numLeFromBytes(dao.slice(8, 16)),
217
- s: numLeFromBytes(dao.slice(16, 24)),
218
- u: numLeFromBytes(dao.slice(24, 32)),
215
+ c: numLeFromBytes(dao.subarray(0, 8)),
216
+ ar: numLeFromBytes(dao.subarray(8, 16)),
217
+ s: numLeFromBytes(dao.subarray(16, 24)),
218
+ u: numLeFromBytes(dao.subarray(24, 32)),
219
219
  },
220
220
  epoch: Epoch.fromNum(header.epoch),
221
221
  extraHash: header.extra_hash,
@@ -1 +1,2 @@
1
1
  export * from "./requestor.js";
2
+ export * from "./transports/index.js";
@@ -1,9 +1,27 @@
1
+ import { abortSignalToPromise } from "../utils/abortSignal.js";
2
+ import { OwnerAggregated } from "../utils/owner/aggregated.js";
3
+ import { Owner } from "../utils/owner/owner.js";
4
+ import { jsonRpcTransportFromUri } from "./transports/factory.js";
5
+ import { JsonRpcTransportFallback } from "./transports/fallback.js";
1
6
  import {
7
+ JsonRpcError,
2
8
  JsonRpcPayload,
3
- Transport,
4
- TransportFallback,
5
- transportFromUri,
6
- } from "./transports/advanced.js";
9
+ JsonRpcResponse,
10
+ JsonRpcTransport,
11
+ JsonRpcTransportRequestOptions,
12
+ } from "./transports/index.js";
13
+
14
+ function openTransports(
15
+ urls: readonly [string, ...string[]],
16
+ config?: { timeout?: number },
17
+ ): Owner<JsonRpcTransportFallback> {
18
+ const transportOwners = Array.from(new Set(urls), (url) =>
19
+ jsonRpcTransportFromUri(url, config),
20
+ );
21
+ return OwnerAggregated.from(transportOwners).map(
22
+ (transports) => new JsonRpcTransportFallback([...transports]),
23
+ );
24
+ }
7
25
 
8
26
  /**
9
27
  * Applies a transformation function to a value if the transformer is provided.
@@ -25,11 +43,15 @@ function transform(value: unknown, transformer?: (i: unknown) => unknown) {
25
43
  return value;
26
44
  }
27
45
 
46
+ /**
47
+ * @deprecated Used only by the legacy positional constructor. Use the static
48
+ * {@link RequestorJsonRpc.new} or {@link RequestorJsonRpc.open} methods.
49
+ */
28
50
  export type RequestorJsonRpcConfig = {
29
51
  fallbacks?: string[];
30
52
  timeout?: number;
31
53
  maxConcurrent?: number;
32
- transport?: Transport;
54
+ transport?: JsonRpcTransport;
33
55
  };
34
56
 
35
57
  export class RequestorJsonRpc {
@@ -37,15 +59,17 @@ export class RequestorJsonRpc {
37
59
  private concurrent = 0;
38
60
  private readonly pending: (() => void)[] = [];
39
61
 
40
- public readonly transport: Transport;
62
+ public readonly transport: JsonRpcTransport;
41
63
 
42
64
  private id = 0;
43
65
 
44
66
  /**
45
- * Creates an instance of ClientJsonRpc.
67
+ * Creates a Requestor using legacy positional arguments.
46
68
  *
47
69
  * @param url_ - The URL of the JSON-RPC server.
48
70
  * @param timeout - The timeout for requests in milliseconds
71
+ * @deprecated Use {@link RequestorJsonRpc.new} with a borrowed Transport or
72
+ * {@link RequestorJsonRpc.open} when creating Transports.
49
73
  */
50
74
  constructor(
51
75
  private readonly url_: string,
@@ -53,20 +77,56 @@ export class RequestorJsonRpc {
53
77
  private readonly onError?: (err: unknown) => Promise<void> | void,
54
78
  ) {
55
79
  this.maxConcurrent = config?.maxConcurrent;
56
- this.transport =
57
- config?.transport ??
58
- new TransportFallback(
59
- Array.from(
60
- new Set([url_, ...(config?.fallbacks ?? [])]).values(),
61
- (url) => transportFromUri(url, config),
62
- ),
63
- );
80
+ if (config?.transport) {
81
+ this.transport = config.transport;
82
+ } else {
83
+ this.transport = openTransports(
84
+ [url_, ...(config?.fallbacks ?? [])],
85
+ config,
86
+ ).value;
87
+ }
88
+ }
89
+
90
+ /** Creates a Requestor that borrows an existing Transport. */
91
+ static new({
92
+ transport,
93
+ maxConcurrent,
94
+ onError,
95
+ }: {
96
+ transport: JsonRpcTransport;
97
+ maxConcurrent?: number;
98
+ onError?: (err: unknown) => Promise<void> | void;
99
+ }): RequestorJsonRpc {
100
+ return new RequestorJsonRpc("", { transport, maxConcurrent }, onError);
101
+ }
102
+
103
+ /** Opens a Requestor with ownership of its default transports. */
104
+ static open({
105
+ urls: [url, ...fallbacks],
106
+ onError,
107
+ ...config
108
+ }: {
109
+ urls: readonly [string, ...string[]];
110
+ timeout?: number;
111
+ maxConcurrent?: number;
112
+ onError?: (err: unknown) => Promise<void> | void;
113
+ }): Owner<RequestorJsonRpc> {
114
+ const transportOwner = openTransports([url, ...fallbacks], config);
115
+ return transportOwner.map((transport) =>
116
+ RequestorJsonRpc.new({
117
+ transport,
118
+ ...config,
119
+ onError,
120
+ }),
121
+ );
64
122
  }
65
123
 
66
124
  /**
67
125
  * Returns the URL of the JSON-RPC server.
68
126
  *
69
127
  * @returns The URL of the JSON-RPC server.
128
+ * @deprecated URL belongs to Transport construction and is unavailable for
129
+ * Requestors created with {@link RequestorJsonRpc.new}.
70
130
  */
71
131
 
72
132
  get url(): string {
@@ -89,6 +149,7 @@ export class RequestorJsonRpc {
89
149
  inTransformers?: (((_: any) => unknown) | undefined)[],
90
150
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
151
  outTransformer?: (_: any) => unknown,
152
+ options?: JsonRpcTransportRequestOptions,
92
153
  ): Promise<unknown> {
93
154
  const payload = this.buildPayload(
94
155
  rpcMethod,
@@ -107,7 +168,7 @@ export class RequestorJsonRpc {
107
168
 
108
169
  try {
109
170
  return await transform(
110
- await this.requestPayload(payload),
171
+ await this.requestPayload(payload, options),
111
172
  outTransformer,
112
173
  );
113
174
  } catch (err: unknown) {
@@ -118,36 +179,52 @@ export class RequestorJsonRpc {
118
179
  }
119
180
  }
120
181
 
121
- async requestPayload(payload: JsonRpcPayload): Promise<unknown> {
182
+ async requestPayload(
183
+ payload: JsonRpcPayload,
184
+ options?: JsonRpcTransportRequestOptions,
185
+ ): Promise<unknown> {
122
186
  if (
123
187
  this.maxConcurrent !== undefined &&
124
188
  this.concurrent >= this.maxConcurrent
125
189
  ) {
126
- const pending = new Promise<void>((resolve) =>
127
- this.pending.push(resolve),
128
- );
129
- await pending;
190
+ options?.signal?.throwIfAborted();
191
+ let resolvePending: (() => void) | undefined;
192
+ const pending = new Promise<void>((resolve) => {
193
+ resolvePending = resolve;
194
+ this.pending.push(resolve);
195
+ });
196
+ try {
197
+ await (options?.signal
198
+ ? Promise.race([pending, abortSignalToPromise(options.signal)])
199
+ : pending);
200
+ } catch (cause) {
201
+ const index = resolvePending
202
+ ? this.pending.indexOf(resolvePending)
203
+ : -1;
204
+ if (index !== -1) {
205
+ this.pending.splice(index, 1);
206
+ } else {
207
+ this.pending.shift()?.();
208
+ }
209
+ throw cause;
210
+ }
130
211
  }
131
212
 
132
- const res = (await (async () => {
213
+ const res: JsonRpcResponse = await (async () => {
133
214
  this.concurrent += 1;
134
215
  try {
135
- return await this.transport.request(payload);
216
+ return await this.transport.request(payload, options);
136
217
  } finally {
137
218
  this.concurrent -= 1;
138
219
  this.pending.shift()?.();
139
220
  }
140
- })()) as {
141
- id: number;
142
- error: unknown;
143
- result: unknown;
144
- };
221
+ })();
145
222
 
146
223
  if (res.id !== payload.id) {
147
224
  throw new Error(`Id mismatched, got ${res.id}, expected ${payload.id}`);
148
225
  }
149
226
  if (res.error != null) {
150
- throw res.error as unknown;
227
+ throw new JsonRpcError(res.error);
151
228
  }
152
229
  return res.result;
153
230
  }
@@ -1,5 +1 @@
1
1
  export * from "./factory.js";
2
- export * from "./fallback.js";
3
- export * from "./http.js";
4
- export * from "./transport.js";
5
- export * from "./webSocket.js";
@@ -1,10 +1,17 @@
1
- import { TransportHttp } from "./http.js";
2
- import { TransportWebSocket } from "./webSocket.js";
1
+ import { OwnerUnique } from "../../utils/owner/unique.js";
2
+ import { JsonRpcTransportHttp } from "./http.js";
3
+ import { JsonRpcTransportWebSocket } from "./webSocket.js";
3
4
 
4
- export function transportFromUri(uri: string, config?: { timeout?: number }) {
5
+ export function jsonRpcTransportFromUri(
6
+ uri: string,
7
+ config?: { timeout?: number },
8
+ ) {
5
9
  if (uri.startsWith("wss://") || uri.startsWith("ws://")) {
6
- return new TransportWebSocket(uri, config?.timeout);
10
+ return JsonRpcTransportWebSocket.open(uri, config?.timeout);
7
11
  }
8
12
 
9
- return new TransportHttp(uri, config?.timeout);
13
+ return new OwnerUnique(
14
+ new JsonRpcTransportHttp(uri, config?.timeout),
15
+ () => {},
16
+ );
10
17
  }
@@ -1,22 +1,30 @@
1
- import { JsonRpcPayload, Transport } from "./transport.js";
1
+ import {
2
+ JsonRpcPayload,
3
+ JsonRpcResponse,
4
+ JsonRpcTransport,
5
+ JsonRpcTransportRequestOptions,
6
+ } from "./transport.js";
2
7
 
3
- export class TransportFallback implements Transport {
8
+ export class JsonRpcTransportFallback implements JsonRpcTransport {
4
9
  // Current transport index
5
10
  private i = 0;
6
11
 
7
- constructor(private readonly transports: Transport[]) {}
12
+ constructor(private readonly transports: JsonRpcTransport[]) {}
8
13
 
9
- async request(data: JsonRpcPayload): Promise<unknown> {
14
+ async request(
15
+ data: JsonRpcPayload,
16
+ options?: JsonRpcTransportRequestOptions,
17
+ ): Promise<JsonRpcResponse> {
10
18
  const startI = this.i;
11
19
  let lastErr: unknown = new Error(
12
- "TransportFallback requires at least one transport",
20
+ "JsonRpcTransportFallback requires at least one transport",
13
21
  );
14
22
 
15
23
  for (let tried = 0; tried < this.transports.length; tried += 1) {
16
24
  const i = (startI + tried) % this.transports.length;
17
25
 
18
26
  try {
19
- const res = await this.transports[i].request(data);
27
+ const res = await this.transports[i].request(data, options);
20
28
 
21
29
  this.i = i;
22
30
  return res;
@@ -1,28 +1,35 @@
1
- import { JsonRpcPayload, Transport } from "./transport.js";
1
+ import { abortSignalAny } from "../../utils/abortSignal.js";
2
+ import {
3
+ JsonRpcPayload,
4
+ JsonRpcResponse,
5
+ JsonRpcTransport,
6
+ JsonRpcTransportRequestOptions,
7
+ } from "./transport.js";
2
8
 
3
- export class TransportHttp implements Transport {
9
+ export class JsonRpcTransportHttp implements JsonRpcTransport {
4
10
  constructor(
5
11
  private readonly url: string,
6
12
  private readonly timeout = 30000,
7
13
  ) {}
8
14
 
9
- async request(payload: JsonRpcPayload) {
10
- const aborter = new AbortController();
11
- const abortTimer = setTimeout(() => aborter.abort(), this.timeout);
15
+ async request(
16
+ payload: JsonRpcPayload,
17
+ options?: JsonRpcTransportRequestOptions,
18
+ ): Promise<JsonRpcResponse> {
19
+ const timeoutSignal = AbortSignal.timeout(options?.timeout ?? this.timeout);
20
+ const signal = options?.signal
21
+ ? abortSignalAny([options.signal, timeoutSignal])
22
+ : timeoutSignal;
12
23
 
13
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
14
- const raw = await (
24
+ return (await (
15
25
  await fetch(this.url, {
16
26
  method: "POST",
17
27
  headers: {
18
28
  "content-type": "application/json",
19
29
  },
20
30
  body: JSON.stringify(payload),
21
- signal: aborter.signal,
31
+ signal,
22
32
  })
23
- ).json();
24
- clearTimeout(abortTimer);
25
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
26
- return raw;
33
+ ).json()) as JsonRpcResponse;
27
34
  }
28
35
  }
@@ -0,0 +1,4 @@
1
+ export * from "./fallback.js";
2
+ export * from "./http.js";
3
+ export * from "./transport.js";
4
+ export * from "./webSocket.js";
@@ -1,18 +1,54 @@
1
+ export type JsonRpcId = string | number;
2
+
1
3
  export type JsonRpcPayload = {
2
- id: number;
4
+ id: JsonRpcId;
3
5
  jsonrpc: "2.0";
4
6
  method: string;
5
7
  params: unknown[] | Record<string, unknown>;
6
8
  };
7
9
 
8
- export interface Transport {
10
+ export type JsonRpcErrorLike<Data = unknown> = {
11
+ code: number;
12
+ message: string;
13
+ data?: Data;
14
+ };
15
+
16
+ export class JsonRpcError<Data = unknown> extends Error {
17
+ readonly code: number;
18
+ readonly data?: Data;
19
+
20
+ constructor(error: JsonRpcErrorLike<Data>) {
21
+ super(error.message);
22
+ this.name = "JsonRpcError";
23
+ this.code = error.code;
24
+ this.data = error.data;
25
+ }
26
+ }
27
+
28
+ export type JsonRpcResponse<Result = unknown, ErrorData = unknown> = {
29
+ id: JsonRpcId;
30
+ jsonrpc: "2.0";
31
+ } & (
32
+ | { result: Result; error?: never }
33
+ | { result?: never; error: JsonRpcErrorLike<ErrorData> }
34
+ );
35
+
36
+ export type JsonRpcTransportRequestOptions = {
37
+ /** Cancels this individual request without disposing the transport. */
38
+ signal?: AbortSignal;
39
+ /** Overrides the transport's default timeout for this request. */
40
+ timeout?: number;
41
+ };
42
+
43
+ export interface JsonRpcTransport {
9
44
  /**
10
45
  * Sends a JSON-RPC request to the server.
11
46
  *
12
47
  * @param payload - The JSON-RPC payload to send.
13
- * @returns The result of the JSON-RPC request.
14
- *
15
- * @throws Will throw an error if the response ID does not match the request ID, or if the response contains an error.
48
+ * @returns The JSON-RPC response.
16
49
  */
17
- request(data: JsonRpcPayload): Promise<unknown>;
50
+ request(
51
+ payload: JsonRpcPayload,
52
+ options?: JsonRpcTransportRequestOptions,
53
+ ): Promise<JsonRpcResponse>;
18
54
  }