@irpclib/irpc 1.0.0-beta.21 → 1.0.0-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/state.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { IRPC_STATUS } from "./enum.js";
2
- import { anchor, mutable, subscribe } from "@anchorlib/core";
2
+ import { ERROR_CODE, ERROR_MESSAGE } from "./error.js";
3
+ import { getAbortSignal } from "./context.js";
4
+ import { $do, anchor, mutable, onCleanup, subscribe } from "@anchorlib/core";
3
5
 
4
6
  //#region src/state.ts
5
7
  /**
@@ -12,62 +14,95 @@ import { anchor, mutable, subscribe } from "@anchorlib/core";
12
14
  * @template T - The type of data held by the state.
13
15
  */
14
16
  var RemoteState = class extends Promise {
15
- state;
16
- accept;
17
- reject;
17
+ #state;
18
+ #accept;
19
+ #reject;
20
+ #closed = false;
21
+ get state() {
22
+ return this.#state;
23
+ }
18
24
  /**
19
25
  * The current data payload of the state.
20
26
  */
21
27
  get data() {
22
- return this.state.data;
28
+ return this.#state.data;
23
29
  }
24
30
  set data(data) {
25
- this.state.data = data;
31
+ if (this.#closed) return;
32
+ this.#state.data = data;
26
33
  }
27
34
  /**
28
35
  * The current error encountered by the state, if any.
29
36
  */
30
37
  get error() {
31
- return this.state.error;
38
+ return this.#state.error;
32
39
  }
33
40
  set error(error) {
34
- this.state.error = error;
41
+ if (this.#closed) return;
42
+ this.#state.error = error;
35
43
  }
36
44
  /**
37
45
  * The execution status of the state (PENDING, SUCCESS, ERROR).
38
46
  * Transitioning to a terminal status (SUCCESS or ERROR) will automatically resolve or reject the underlying Promise.
39
47
  */
40
48
  get status() {
41
- return this.state.status;
49
+ return this.#state.status;
42
50
  }
43
51
  set status(status) {
52
+ if (this.#closed) return;
44
53
  this.state.status = status;
45
- if (this.status === IRPC_STATUS.ERROR) {
46
- this.reject(new Error(this.error.message));
47
- this.destroy();
48
- } else if (this.status === IRPC_STATUS.SUCCESS) {
49
- this.accept(this.data);
50
- this.destroy();
51
- }
54
+ if (status === IRPC_STATUS.ERROR) this.reject();
55
+ else if (status === IRPC_STATUS.SUCCESS) this.accept();
52
56
  }
53
57
  /**
54
58
  * Initializes a new RemoteState with an optional initial payload.
55
59
  *
56
60
  * @param init - An optional starting value for the data payload.
61
+ * @param status - The initial status of the state (PENDING, SUCCESS, ERROR).
57
62
  */
58
- constructor(init) {
63
+ constructor(init, status = IRPC_STATUS.PENDING) {
59
64
  let acceptFn;
60
65
  let rejectFn;
61
66
  super((resolve, reject) => {
62
67
  acceptFn = resolve;
63
68
  rejectFn = reject;
64
69
  });
65
- this.accept = acceptFn;
66
- this.reject = rejectFn;
67
- this.state = mutable({
70
+ this.#accept = acceptFn;
71
+ this.#reject = rejectFn;
72
+ this.#state = mutable({
68
73
  data: init,
69
74
  error: void 0,
70
- status: IRPC_STATUS.PENDING
75
+ status
76
+ });
77
+ onCleanup(() => this.close());
78
+ }
79
+ accept(...args) {
80
+ $do(() => {
81
+ if (this.#closed) return;
82
+ const value = args.length ? args[0] : this.data;
83
+ this.#closed = true;
84
+ this.#state.status = IRPC_STATUS.SUCCESS;
85
+ this.#state.data = value;
86
+ this.#accept(value);
87
+ this.destroy();
88
+ });
89
+ }
90
+ reject(...args) {
91
+ $do(() => {
92
+ if (this.#closed) return;
93
+ if (args.length) this.#state.error = args[0];
94
+ this.#closed = true;
95
+ this.#state.status = IRPC_STATUS.ERROR;
96
+ this.#reject(this.error ?? new Error(ERROR_MESSAGE[ERROR_CODE.UNKNOWN]));
97
+ this.destroy();
98
+ });
99
+ }
100
+ abort() {
101
+ $do(() => {
102
+ this.#closed = true;
103
+ this.#state.status = IRPC_STATUS.ABORTED;
104
+ this.#accept(this.data);
105
+ this.destroy();
71
106
  });
72
107
  }
73
108
  /**
@@ -80,6 +115,15 @@ var RemoteState = class extends Promise {
80
115
  return subscribe(this.state, handler);
81
116
  }
82
117
  /**
118
+ * Closes the reactive state and terminates the underlying Promise.
119
+ */
120
+ close() {
121
+ if (this.#closed) return;
122
+ this.#closed = true;
123
+ this.#accept(this.data);
124
+ this.destroy();
125
+ }
126
+ /**
83
127
  * Destroys the reactive state bindings.
84
128
  */
85
129
  destroy() {
@@ -105,17 +149,27 @@ var RemoteState = class extends Promise {
105
149
  */
106
150
  function stream(construct, init) {
107
151
  const state = new RemoteState(init);
152
+ const abortSignal = getAbortSignal();
108
153
  const accept = ((...values) => {
109
154
  if (values.length > 0) state.data = values[0];
110
155
  state.status = IRPC_STATUS.SUCCESS;
156
+ abortSignal?.removeEventListener("abort", abort);
111
157
  });
112
158
  const reject = (error) => {
113
159
  state.error = error;
114
160
  state.status = IRPC_STATUS.ERROR;
161
+ abortSignal?.removeEventListener("abort", abort);
115
162
  };
163
+ const abort = () => state.abort();
164
+ abortSignal?.addEventListener("abort", abort, { once: true });
116
165
  try {
117
- const result = construct(state.data, accept, reject);
118
- if (result instanceof Promise) result.catch(reject);
166
+ const cleanup = construct(state, accept, reject);
167
+ if (cleanup instanceof Promise) cleanup.then((futureCleanup) => {
168
+ if (typeof futureCleanup === "function") if (abortSignal?.aborted || state.status !== IRPC_STATUS.PENDING) $do(futureCleanup);
169
+ else abortSignal?.addEventListener("abort", () => $do(futureCleanup), { once: true });
170
+ }).catch(reject);
171
+ else if (typeof cleanup === "function") if (abortSignal?.aborted || state.status !== IRPC_STATUS.PENDING) $do(cleanup);
172
+ else abortSignal?.addEventListener("abort", () => $do(cleanup), { once: true });
119
173
  } catch (error) {
120
174
  reject(error);
121
175
  }
@@ -0,0 +1,44 @@
1
+ import { IRPC_STORE_EVENT } from "./enum.js";
2
+ import { IRPCData } from "./types.js";
3
+ import { IRPCPackage } from "./module.js";
4
+ import { IRPCRouter } from "./router.js";
5
+ import { IRPCStream } from "./stream.js";
6
+
7
+ //#region src/store.d.ts
8
+ type IRPCStoreEvent = {
9
+ type: typeof IRPC_STORE_EVENT.REGISTER;
10
+ data: IRPCPackage;
11
+ } | {
12
+ type: typeof IRPC_STORE_EVENT.ROUTE;
13
+ data: IRPCRouter;
14
+ } | {
15
+ type: typeof IRPC_STORE_EVENT.QUEUE;
16
+ data: IRPCStream<IRPCData>;
17
+ } | {
18
+ type: typeof IRPC_STORE_EVENT.DEQUEUE;
19
+ data: IRPCStream<IRPCData>;
20
+ } | {
21
+ type: typeof IRPC_STORE_EVENT.ERROR;
22
+ error: Error;
23
+ data?: unknown[];
24
+ };
25
+ type IRPCStoreSubscriber = (event: IRPCStoreEvent) => void;
26
+ declare class IRPCStore {
27
+ #private;
28
+ calls: Set<IRPCStream<IRPCData>>;
29
+ routers: Set<IRPCRouter>;
30
+ packages: Set<IRPCPackage>;
31
+ callCount: number;
32
+ errorCount: number;
33
+ register(pkg: IRPCPackage): void;
34
+ route(router: IRPCRouter): void;
35
+ queue(call: IRPCStream<IRPCData>): void;
36
+ dequeue(call: IRPCStream<IRPCData>): void;
37
+ error(error: Error, data?: unknown[]): void;
38
+ print(): void;
39
+ subscribe(handler: IRPCStoreSubscriber): () => boolean;
40
+ private broadcast;
41
+ }
42
+ declare const IRPC_STORE: IRPCStore;
43
+ //#endregion
44
+ export { IRPCStore, IRPCStoreEvent, IRPCStoreSubscriber, IRPC_STORE };
package/dist/store.js ADDED
@@ -0,0 +1,76 @@
1
+ import { IRPC_STORE_EVENT } from "./enum.js";
2
+ import { IRPCPackage } from "./module.js";
3
+ import { IRPCRouter } from "./router.js";
4
+ import { IRPCStream } from "./stream.js";
5
+
6
+ //#region src/store.ts
7
+ var IRPCStore = class {
8
+ #subscribers = /* @__PURE__ */ new Set();
9
+ calls = /* @__PURE__ */ new Set();
10
+ routers = /* @__PURE__ */ new Set();
11
+ packages = /* @__PURE__ */ new Set();
12
+ callCount = 0;
13
+ errorCount = 0;
14
+ register(pkg) {
15
+ if (!(pkg instanceof IRPCPackage)) throw new Error("Invalid package: package must be an instance of IRPCPackage.");
16
+ this.packages.add(pkg);
17
+ this.broadcast({
18
+ type: IRPC_STORE_EVENT.REGISTER,
19
+ data: pkg
20
+ });
21
+ }
22
+ route(router) {
23
+ if (!(router instanceof IRPCRouter)) throw new Error("Invalid router: router must be an instance of IRPCRouter.");
24
+ this.routers.add(router);
25
+ this.broadcast({
26
+ type: IRPC_STORE_EVENT.ROUTE,
27
+ data: router
28
+ });
29
+ }
30
+ queue(call) {
31
+ if (!(call instanceof IRPCStream)) throw new Error("Invalid call: call must be an instance of IRPCStream.");
32
+ this.calls.add(call);
33
+ this.callCount += 1;
34
+ this.broadcast({
35
+ type: IRPC_STORE_EVENT.QUEUE,
36
+ data: call
37
+ });
38
+ }
39
+ dequeue(call) {
40
+ if (!(call instanceof IRPCStream)) throw new Error("Invalid call: call must be an instance of IRPCStream.");
41
+ this.calls.delete(call);
42
+ this.broadcast({
43
+ type: IRPC_STORE_EVENT.DEQUEUE,
44
+ data: call
45
+ });
46
+ }
47
+ error(error, data) {
48
+ this.errorCount += 1;
49
+ this.broadcast({
50
+ type: IRPC_STORE_EVENT.ERROR,
51
+ error,
52
+ data
53
+ });
54
+ }
55
+ print() {
56
+ console.table([{
57
+ Packages: this.packages.size,
58
+ Routers: this.routers.size,
59
+ Running: this.calls.size,
60
+ Calls: this.callCount,
61
+ Errors: this.errorCount
62
+ }]);
63
+ }
64
+ subscribe(handler) {
65
+ if (typeof handler !== "function") throw new Error("Invalid handler: handler must be a function.");
66
+ this.#subscribers.add(handler);
67
+ return () => this.#subscribers.delete(handler);
68
+ }
69
+ broadcast(event) {
70
+ for (const subscriber of this.#subscribers) subscriber(event);
71
+ }
72
+ };
73
+ const IRPC_STORE = new IRPCStore();
74
+
75
+ //#endregion
76
+ export { IRPCStore, IRPC_STORE };
package/dist/stream.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { IRPCData, IRPCError, IRPCPacketStream, IRPCResponse, IRPCStatus } from "./types.js";
1
+ import { IRPCData, IRPCDataSchema, IRPCError, IRPCInputs, IRPCPacketStream, IRPCResponse, IRPCSpec, IRPCStatus } from "./types.js";
2
+ import { IRPCRouter } from "./router.js";
2
3
 
3
4
  //#region src/stream.d.ts
4
5
 
@@ -14,20 +15,29 @@ declare class IRPCStream<T extends IRPCData> {
14
15
  private id;
15
16
  private name;
16
17
  private initializer;
18
+ spec?: IRPCSpec<IRPCInputs, IRPCDataSchema> | undefined;
19
+ router?: IRPCRouter | undefined;
17
20
  private pipeHandlers;
18
21
  private closeHandlers;
19
22
  private errorHandlers;
20
23
  value?: T;
21
24
  error?: IRPCError;
22
25
  status: IRPCStatus;
26
+ closed: boolean;
27
+ createdAt: number;
28
+ startedAt?: number;
29
+ updatedAt?: number;
30
+ controller?: AbortController;
23
31
  /**
24
32
  * Initializes a stream wrapping an asynchronous RPC execution.
25
33
  *
26
34
  * @param id - The unique identifier of the RPC request.
27
35
  * @param name - The name of the specification processing the execution.
28
36
  * @param initializer - An execution callback that yields an IRPCResponse.
37
+ * @param spec - The specification for the RPC execution.
38
+ * @param router - The router instance managing the stream.
29
39
  */
30
- constructor(id: string, name: string, initializer: () => Promise<IRPCResponse>);
40
+ constructor(id: string, name: string, initializer: () => Promise<IRPCResponse>, spec?: IRPCSpec<IRPCInputs, IRPCDataSchema> | undefined, router?: IRPCRouter | undefined);
31
41
  /**
32
42
  * Evaluates the underlying initializer and propagates standard transport packets
33
43
  * to all bound pipe handlers based on the output lifecycle.
@@ -52,6 +62,7 @@ declare class IRPCStream<T extends IRPCData> {
52
62
  * @param handler - A callback function invoked at stream completion.
53
63
  */
54
64
  close(handler: () => void): void;
65
+ private finish;
55
66
  }
56
67
  //#endregion
57
68
  export { IRPCStream };
package/dist/stream.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
2
2
  import { ERROR_CODE } from "./error.js";
3
+ import { getAbortController, getAbortSignal } from "./context.js";
3
4
  import { RemoteState } from "./state.js";
5
+ import { IRPC_STORE } from "./store.js";
4
6
 
5
7
  //#region src/stream.ts
6
8
  /**
@@ -18,28 +20,50 @@ var IRPCStream = class {
18
20
  value;
19
21
  error;
20
22
  status = IRPC_STATUS.IDLE;
23
+ closed = false;
24
+ createdAt = Date.now();
25
+ startedAt;
26
+ updatedAt;
27
+ controller;
21
28
  /**
22
29
  * Initializes a stream wrapping an asynchronous RPC execution.
23
30
  *
24
31
  * @param id - The unique identifier of the RPC request.
25
32
  * @param name - The name of the specification processing the execution.
26
33
  * @param initializer - An execution callback that yields an IRPCResponse.
34
+ * @param spec - The specification for the RPC execution.
35
+ * @param router - The router instance managing the stream.
27
36
  */
28
- constructor(id, name, initializer) {
37
+ constructor(id, name, initializer, spec, router) {
29
38
  this.id = id;
30
39
  this.name = name;
31
40
  this.initializer = initializer;
41
+ this.spec = spec;
42
+ this.router = router;
43
+ IRPC_STORE.queue(this);
32
44
  }
33
45
  /**
34
46
  * Evaluates the underlying initializer and propagates standard transport packets
35
47
  * to all bound pipe handlers based on the output lifecycle.
36
48
  */
37
49
  async start() {
38
- if (this.status !== IRPC_STATUS.IDLE) return;
50
+ if (this.status !== IRPC_STATUS.IDLE || this.closed) return;
51
+ this.startedAt = Date.now();
52
+ this.controller = getAbortController();
53
+ const abortSignal = getAbortSignal();
54
+ if (abortSignal?.aborted) {
55
+ this.finish();
56
+ return;
57
+ }
39
58
  this.status = IRPC_STATUS.PENDING;
40
59
  const { id, name } = this;
41
60
  try {
42
- const { result } = await this.initializer();
61
+ const response = await this.initializer();
62
+ if (abortSignal?.aborted) {
63
+ this.finish();
64
+ return;
65
+ }
66
+ const { result } = response;
43
67
  if (result instanceof RemoteState) {
44
68
  this.value = result.data;
45
69
  if (result.status === IRPC_STATUS.SUCCESS || result.status === IRPC_STATUS.ERROR) {
@@ -60,8 +84,8 @@ var IRPCStream = class {
60
84
  createdAt: Date.now()
61
85
  };
62
86
  this.pipeHandlers.forEach((handler) => handler(packet));
63
- this.errorHandlers.forEach((handler) => handler(this.error));
64
- this.closeHandlers.forEach((handler) => handler());
87
+ if (this.error) this.errorHandlers.forEach((handler) => handler(this.error));
88
+ this.finish();
65
89
  return;
66
90
  }
67
91
  this.pipeHandlers.forEach((handler) => {
@@ -81,13 +105,13 @@ var IRPCStream = class {
81
105
  handler({
82
106
  id,
83
107
  name,
84
- type: IRPC_PACKET_TYPE.EVENT,
85
- status: state.status,
86
108
  data: {
87
109
  type,
88
110
  keys,
89
111
  value
90
112
  },
113
+ type: IRPC_PACKET_TYPE.EVENT,
114
+ status: state.status,
91
115
  createdAt: Date.now()
92
116
  });
93
117
  });
@@ -111,25 +135,43 @@ var IRPCStream = class {
111
135
  createdAt: Date.now()
112
136
  });
113
137
  });
114
- this.closeHandlers.forEach((handler) => handler());
138
+ abortSignal?.removeEventListener("abort", abortStream);
139
+ this.finish();
115
140
  unsubscribe();
116
141
  }
117
142
  });
143
+ const abortStream = () => {
144
+ unsubscribe();
145
+ this.finish();
146
+ };
147
+ abortSignal?.addEventListener("abort", abortStream, { once: true });
118
148
  } else {
119
149
  this.value = result;
120
- this.status = IRPC_STATUS.SUCCESS;
150
+ if (response.error) {
151
+ this.error = {
152
+ code: ERROR_CODE.STREAM_ERROR,
153
+ message: response.error.message
154
+ };
155
+ this.status = IRPC_STATUS.ERROR;
156
+ this.errorHandlers.forEach((handler) => handler(this.error));
157
+ } else this.status = IRPC_STATUS.SUCCESS;
121
158
  const packet = {
122
159
  id,
123
160
  name,
124
161
  type: IRPC_PACKET_TYPE.ANSWER,
125
- status: IRPC_STATUS.SUCCESS,
126
162
  data: this.value,
163
+ error: this.error,
164
+ status: this.status,
127
165
  createdAt: Date.now()
128
166
  };
129
167
  this.pipeHandlers.forEach((handler) => handler(packet));
130
- this.closeHandlers.forEach((handler) => handler());
168
+ this.finish();
131
169
  }
132
170
  } catch (error) {
171
+ IRPC_STORE.error(error, [{
172
+ id: this.id,
173
+ name: this.name
174
+ }]);
133
175
  this.error = {
134
176
  code: ERROR_CODE.STREAM_ERROR,
135
177
  message: error.message
@@ -146,7 +188,7 @@ var IRPCStream = class {
146
188
  });
147
189
  });
148
190
  this.errorHandlers.forEach((handler) => handler(this.error));
149
- this.closeHandlers.forEach((handler) => handler());
191
+ this.finish();
150
192
  return;
151
193
  }
152
194
  }
@@ -169,8 +211,12 @@ var IRPCStream = class {
169
211
  });
170
212
  return;
171
213
  }
214
+ if (this.closed) return;
172
215
  this.pipeHandlers.add(handler);
173
- this.start().catch(() => {});
216
+ this.start().catch((err) => IRPC_STORE.error(err, [{
217
+ id: this.id,
218
+ name: this.name
219
+ }]));
174
220
  }
175
221
  /**
176
222
  * Binds a handler to trap any internal runtime failures independently.
@@ -182,8 +228,12 @@ var IRPCStream = class {
182
228
  handler(this.error);
183
229
  return;
184
230
  }
231
+ if (this.closed) return;
185
232
  this.errorHandlers.add(handler);
186
- this.start().catch(() => {});
233
+ this.start().catch((err) => IRPC_STORE.error(err, [{
234
+ id: this.id,
235
+ name: this.name
236
+ }]));
187
237
  }
188
238
  /**
189
239
  * Binds a handler triggered upon terminal completion of the stream process (success or error).
@@ -195,8 +245,21 @@ var IRPCStream = class {
195
245
  handler();
196
246
  return;
197
247
  }
248
+ if (this.closed) return;
198
249
  this.closeHandlers.add(handler);
199
- this.start().catch(() => {});
250
+ this.start().catch((err) => IRPC_STORE.error(err, [{
251
+ id: this.id,
252
+ name: this.name
253
+ }]));
254
+ }
255
+ finish() {
256
+ this.closed = true;
257
+ this.closeHandlers.forEach((handler) => handler());
258
+ this.pipeHandlers.clear();
259
+ this.errorHandlers.clear();
260
+ this.closeHandlers.clear();
261
+ this.updatedAt = Date.now();
262
+ IRPC_STORE.dequeue(this);
200
263
  }
201
264
  };
202
265
 
@@ -21,18 +21,25 @@ declare class IRPCTransport {
21
21
  constructor(config?: TransportConfig | undefined);
22
22
  /**
23
23
  * Initiates an RPC call with the given specification and arguments.
24
+ * @param reader - The reader instance to attach to the RPC call.
24
25
  * @param spec - The RPC specification defining the method to call.
25
26
  * @param args - An array of arguments to pass to the RPC method.
26
27
  * @param config - Optional call configuration, including timeout, retry settings, and more.
27
28
  * @returns A promise that resolves with the RPC response data or rejects with an error.
28
29
  */
29
- call(spec: IRPCSpec<IRPCInputs, IRPCOutput>, args: IRPCData[], config?: IRPCCallConfig): IRPCReader<IRPCData>;
30
+ call(spec: IRPCSpec<IRPCInputs, IRPCOutput>, args: IRPCData[], config?: IRPCCallConfig, reader?: IRPCReader<IRPCData>): IRPCReader<IRPCData>;
30
31
  /**
31
32
  * Schedules an RPC call for execution, implementing debouncing logic.
32
33
  * Queued calls will be dispatched after the configured debounce delay.
33
34
  * @param call - The RPC call to schedule.
34
35
  */
35
36
  schedule(call: IRPCCall): void;
37
+ /**
38
+ * Closes an RPC call. This base implementation does nothing.
39
+ * Subclasses should override this method to provide closing logic.
40
+ * @param call - The RPC call to cancel.
41
+ */
42
+ close(call: IRPCCall): void;
36
43
  /**
37
44
  * Dispatches a batch of RPC calls. This base implementation rejects all calls
38
45
  * with a "not implemented" error. Subclasses should override this method to
package/dist/transport.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
2
2
  import { ERROR_CODE, ERROR_MESSAGE } from "./error.js";
3
+ import { IRPCReader } from "./reader.js";
4
+ import { IRPC_STORE } from "./store.js";
3
5
  import { IRPCCall } from "./call.js";
6
+ import { onCleanup, uuid } from "@anchorlib/core";
4
7
 
5
8
  //#region src/transport.ts
6
9
  /**
@@ -21,12 +24,13 @@ var IRPCTransport = class {
21
24
  }
22
25
  /**
23
26
  * Initiates an RPC call with the given specification and arguments.
27
+ * @param reader - The reader instance to attach to the RPC call.
24
28
  * @param spec - The RPC specification defining the method to call.
25
29
  * @param args - An array of arguments to pass to the RPC method.
26
30
  * @param config - Optional call configuration, including timeout, retry settings, and more.
27
31
  * @returns A promise that resolves with the RPC response data or rejects with an error.
28
32
  */
29
- call(spec, args, config) {
33
+ call(spec, args, config, reader = new IRPCReader(uuid())) {
30
34
  const payload = {
31
35
  name: spec.name,
32
36
  args
@@ -40,8 +44,15 @@ var IRPCTransport = class {
40
44
  maxRetries,
41
45
  retryMode,
42
46
  retryDelay
43
- });
44
- this.schedule(call);
47
+ }, reader);
48
+ if (spec.stream) {
49
+ this.dispatch([call]).finally(() => {}).catch((err) => IRPC_STORE.error(err, [{
50
+ id: call.id,
51
+ name: call.payload.name
52
+ }]));
53
+ return call.reader;
54
+ } else this.schedule(call);
55
+ onCleanup(() => this.close(call));
45
56
  return call.reader;
46
57
  }
47
58
  /**
@@ -52,12 +63,16 @@ var IRPCTransport = class {
52
63
  schedule(call) {
53
64
  const { debounce } = this.config ?? {};
54
65
  if (debounce === false) {
55
- this.dispatch([call]).finally(() => {}).catch(() => {});
66
+ this.dispatch([call]).finally(() => {}).catch((err) => IRPC_STORE.error(err, [{
67
+ id: call.id,
68
+ name: call.payload.name
69
+ }]));
56
70
  return;
57
71
  }
58
72
  const timeout = typeof debounce === "number" && !Number.isNaN(debounce) ? debounce : 0;
59
73
  const dispatch = () => {
60
- this.dispatch(Array.from(this.queue)).finally(() => {}).catch(() => {});
74
+ const pending = Array.from(this.queue);
75
+ this.dispatch(pending);
61
76
  this.queue.clear();
62
77
  };
63
78
  if (!this.queue.size) if (timeout === 0) queueMicrotask(dispatch);
@@ -65,6 +80,14 @@ var IRPCTransport = class {
65
80
  this.queue.add(call);
66
81
  }
67
82
  /**
83
+ * Closes an RPC call. This base implementation does nothing.
84
+ * Subclasses should override this method to provide closing logic.
85
+ * @param call - The RPC call to cancel.
86
+ */
87
+ close(call) {
88
+ console.log("[irpc] Closing call", call);
89
+ }
90
+ /**
68
91
  * Dispatches a batch of RPC calls. This base implementation rejects all calls
69
92
  * with a "not implemented" error. Subclasses should override this method to
70
93
  * provide actual transport mechanism.