@irpclib/irpc 1.0.0-beta.22 → 1.0.0-beta.24

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/module.d.ts CHANGED
@@ -1,10 +1,17 @@
1
- import { IRPCCacher } from "./cache.js";
2
1
  import { IRPCTransport } from "./transport.js";
3
- import { IRPCData, IRPCDeclareInit, IRPCHandler, IRPCInputs, IRPCOutput, IRPCPackageConfig, IRPCPackageInfo, IRPCRequest, IRPCSpec, IRPCSpecStore, IRPCStubStore } from "./types.js";
2
+ import { IRPCData, IRPCDeclareInit, IRPCFunction, IRPCHandler, IRPCInputs, IRPCOutput, IRPCPackageConfig, IRPCPackageInfo, IRPCRequest, IRPCSpec, IRPCStub } from "./types.js";
4
3
  import { RemoteState } from "./state.js";
4
+ import { IRPCReader } from "./reader.js";
5
5
 
6
6
  //#region src/module.d.ts
7
-
7
+ type IRPCHookArgs<F$1> = F$1 extends ((...args: infer A) => unknown) ? {
8
+ name: string;
9
+ args: A;
10
+ } : {
11
+ name: string;
12
+ args: unknown[];
13
+ };
14
+ type IRPCSpecHook<F$1> = (req: IRPCHookArgs<F$1>) => void | Promise<void>;
8
15
  /**
9
16
  * IRPCPackage represents a package containing multiple IRPC (Isomorphic-RPC) specifications
10
17
  * and their corresponding stubs. It manages the configuration, transport, and execution
@@ -14,15 +21,16 @@ declare class IRPCPackage {
14
21
  /**
15
22
  * A map storing all IRPC specifications by their names
16
23
  */
17
- specs: IRPCSpecStore;
24
+ private specs;
25
+ private hooks;
18
26
  /**
19
27
  * A weak map linking stub functions to their corresponding specifications
20
28
  */
21
- stubs: IRPCStubStore;
29
+ private stubs;
22
30
  /**
23
31
  * A map storing caches for each IRPC Entry
24
32
  */
25
- cache: WeakMap<Function, IRPCCacher>;
33
+ private cache;
26
34
  /**
27
35
  * Configuration object for the IRPC package
28
36
  */
@@ -51,7 +59,7 @@ declare class IRPCPackage {
51
59
  * @returns A stub function that can be used to call the IRPC
52
60
  * @throws Error if an IRPC with the same name already exists
53
61
  */
54
- declare<F, I extends IRPCInputs = IRPCInputs, O extends IRPCOutput = IRPCOutput>(options: IRPCDeclareInit<F, I, O>): F;
62
+ declare<F, I extends IRPCInputs = IRPCInputs, O extends IRPCOutput = IRPCOutput>(options: IRPCDeclareInit<F, I, O>): IRPCFunction<F>;
55
63
  /**
56
64
  * Resolves and executes an IRPC call based on a request object
57
65
  * @param req - The request containing the IRPC name and arguments
@@ -66,7 +74,22 @@ declare class IRPCPackage {
66
74
  * @returns This IRPCPackage instance for chaining
67
75
  * @throws Error if the stub or handler is invalid, or if no IRPC exists for the stub
68
76
  */
69
- construct<F extends IRPCHandler>(stub: F, handler: F): this;
77
+ construct<F, A extends unknown[], R extends IRPCData>(stub: IRPCStub<F, A, R>, handler: F): this;
78
+ /**
79
+ * Registers a hook function for a specific stub function
80
+ * @param stub - The stub function created by declare()
81
+ * @param handler - The hook function to register
82
+ * @returns This IRPCPackage instance for chaining
83
+ * @throws Error if the stub is invalid or if no IRPC exists for the stub
84
+ */
85
+ hook<F extends IRPCHandler>(stub: F, handler: IRPCSpecHook<F>): this;
86
+ /**
87
+ * Resolves and executes all registered hooks for a given request
88
+ * @param req - The request containing the IRPC name and arguments
89
+ * @returns A promise that resolves when all hooks have been executed
90
+ * @throws Error if no IRPC exists for the request or if the hooks are not registered
91
+ */
92
+ resolveHooks(req: IRPCRequest): Promise<void>;
70
93
  /**
71
94
  * Sets the transport mechanism for this package
72
95
  * @param transport - The transport instance to use for remote calls
@@ -100,5 +123,14 @@ declare class IRPCPackage {
100
123
  * @returns A new IRPCPackage instance
101
124
  */
102
125
  declare function createPackage(config?: Partial<IRPCPackageConfig>): IRPCPackage;
126
+ /**
127
+ * Intercepts local function call to get an instant response without remote execution.
128
+ *
129
+ * @param reader - The reader object to intercept.
130
+ * @param spec - The IRPC specification for the function call.
131
+ * @param args - The arguments to be passed to the function.
132
+ * @returns {IRPCReader<IRPCData>} - The IRPCReader object for consumer.
133
+ */
134
+ declare function intercept(spec: IRPCSpec<IRPCInputs, IRPCOutput>, args: unknown[], reader: IRPCReader<IRPCData>): IRPCReader<IRPCData>;
103
135
  //#endregion
104
- export { IRPCPackage, createPackage };
136
+ export { IRPCHookArgs, IRPCPackage, IRPCSpecHook, createPackage, intercept };
package/dist/module.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import { IRPCCacher } from "./cache.js";
2
+ import { IRPC_STATUS } from "./enum.js";
2
3
  import { ERROR_CODE, ERROR_MESSAGE } from "./error.js";
4
+ import { getAbortSignal } from "./context.js";
3
5
  import { RemoteState } from "./state.js";
6
+ import { IRPCReader } from "./reader.js";
4
7
  import { IRPCTransport } from "./transport.js";
8
+ import { IRPC_STORE } from "./store.js";
9
+ import { anchor, createObserver, isBrowser, microtask, onCleanup, replay, uuid } from "@anchorlib/core";
5
10
 
6
11
  //#region src/module.ts
7
12
  const DEFAULT_TIMEOUT = 2e4;
@@ -17,6 +22,7 @@ var IRPCPackage = class {
17
22
  * A map storing all IRPC specifications by their names
18
23
  */
19
24
  specs = /* @__PURE__ */ new Map();
25
+ hooks = /* @__PURE__ */ new Map();
20
26
  /**
21
27
  * A weak map linking stub functions to their corresponding specifications
22
28
  */
@@ -63,6 +69,7 @@ var IRPCPackage = class {
63
69
  */
64
70
  constructor(config) {
65
71
  this.configure(config ?? {});
72
+ IRPC_STORE.register(this);
66
73
  }
67
74
  /**
68
75
  * Declares a new IRPC specification and creates a corresponding stub function
@@ -71,17 +78,84 @@ var IRPCPackage = class {
71
78
  * @throws Error if an IRPC with the same name already exists
72
79
  */
73
80
  declare(options) {
74
- if (this.specs.has(options.name)) throw new Error(`IRPC ${options.name} already exists.`);
75
- const spec = { ...options };
81
+ const $options = options;
82
+ if (this.specs.has($options.name)) throw new Error(`IRPC ${$options.name} already exists.`);
83
+ const spec = {
84
+ init: () => void 0,
85
+ ...$options
86
+ };
76
87
  const calls = /* @__PURE__ */ new Map();
77
88
  const caches = new IRPCCacher();
78
89
  const stub = ((...args) => {
90
+ return execute(args, new IRPCReader(uuid(), spec.init()));
91
+ });
92
+ /** Browser only stub for single immediate execution **/
93
+ stub.once = (...args) => {
94
+ return prepare(() => args);
95
+ };
96
+ stub.with = (getArgs, debounce) => {
97
+ return prepare(typeof getArgs === "function" ? getArgs : () => getArgs, false, debounce);
98
+ };
99
+ stub.when = (getArgs, debounce) => {
100
+ return prepare(typeof getArgs === "function" ? getArgs : () => getArgs, true, debounce);
101
+ };
102
+ stub.later = (debounce) => {
103
+ const reader = new IRPCReader(uuid(), spec.init(), IRPC_STATUS.IDLE, true);
104
+ if (debounce) {
105
+ const [schedule, cancel] = microtask(debounce);
106
+ reader.dispatch = (...args) => schedule(() => {
107
+ reader.resume();
108
+ execute(args, reader);
109
+ });
110
+ onCleanup(cancel);
111
+ return reader;
112
+ }
113
+ reader.dispatch = (...args) => {
114
+ reader.resume();
115
+ execute(args, reader);
116
+ };
117
+ return reader;
118
+ };
119
+ /**
120
+ * A preparation utility to generate and schedule call on the browser environment.
121
+ *
122
+ * @param getArgs - A function that returns the arguments for the call.
123
+ * @param deferred - A flag indicating whether the call should be deferred.
124
+ * @param debounce - The debounce time in milliseconds.
125
+ * @returns {IRPCReader<IRPCData>} - The reader for the call.
126
+ */
127
+ function prepare(getArgs, deferred, debounce = 0) {
128
+ const reader = new IRPCReader(uuid(), spec.init(), deferred ? IRPC_STATUS.IDLE : IRPC_STATUS.PENDING);
129
+ if (isBrowser()) {
130
+ const observer = createObserver(() => {
131
+ observer.reset();
132
+ dispatch();
133
+ });
134
+ const [schedule, cancel] = microtask(debounce);
135
+ const dispatch = (coalesce = true) => {
136
+ const args = observer.run(getArgs);
137
+ if (!coalesce) return execute(args, reader);
138
+ schedule(() => {
139
+ execute(args, reader);
140
+ });
141
+ };
142
+ if (deferred) observer.run(getArgs);
143
+ else dispatch(false);
144
+ onCleanup(() => {
145
+ cancel();
146
+ observer.destroy();
147
+ });
148
+ }
149
+ return reader;
150
+ }
151
+ const execute = (args, reader) => {
79
152
  if (!this.transport && typeof spec.handler !== "function") return Promise.reject(new Error(ERROR_MESSAGE[ERROR_CODE.TRANSPORT_MISSING]));
153
+ reader.status = IRPC_STATUS.PENDING;
80
154
  const callKey = JSON.stringify(args);
81
155
  const cached = caches.get(callKey);
82
156
  if (cached) return cached.value;
83
157
  if (spec.coalesce !== false && calls.has(callKey)) return calls.get(callKey);
84
- const { timeout, maxRetries, retryDelay, retryMode } = {
158
+ const { timeout, maxRetries, retryDelay, retryMode, init } = {
85
159
  ...this.config,
86
160
  ...spec
87
161
  };
@@ -89,19 +163,25 @@ var IRPCPackage = class {
89
163
  timeout,
90
164
  maxRetries,
91
165
  retryDelay,
92
- retryMode
166
+ retryMode,
167
+ init
93
168
  };
94
- const call = typeof spec.handler === "function" ? spec.handler(...args) : this.transport.call(spec, args, config);
169
+ const hooks = this.hooks.get(spec);
170
+ if (hooks) hooks.forEach((hook) => hook({
171
+ name: spec.name,
172
+ args
173
+ }));
174
+ const call = typeof spec.handler === "function" ? intercept(spec, args, reader) : this.transport.call(spec, args, config, reader);
95
175
  calls.set(callKey, call);
96
176
  if (spec.maxAge) caches.set(callKey, call, spec.maxAge);
97
- if (typeof spec.init === "function" && call instanceof RemoteState && typeof call.data === "undefined") call.data = spec.init();
98
- if (call instanceof Promise) call.finally(() => calls.delete(callKey)).catch(() => {});
99
- else calls.delete(callKey);
100
- return call;
101
- });
102
- this.specs.set(options.name, spec);
177
+ onCleanup(() => call.close());
178
+ call.finally(() => calls.delete(callKey)).catch((err) => IRPC_STORE.error(err, [{ name: spec.name }]));
179
+ return reader;
180
+ };
181
+ this.specs.set($options.name, spec);
103
182
  this.stubs.set(stub, spec);
104
183
  this.cache.set(stub, caches);
184
+ this.hooks.set(spec, /* @__PURE__ */ new Set());
105
185
  return stub;
106
186
  }
107
187
  /**
@@ -132,6 +212,35 @@ var IRPCPackage = class {
132
212
  return this;
133
213
  }
134
214
  /**
215
+ * Registers a hook function for a specific stub function
216
+ * @param stub - The stub function created by declare()
217
+ * @param handler - The hook function to register
218
+ * @returns This IRPCPackage instance for chaining
219
+ * @throws Error if the stub is invalid or if no IRPC exists for the stub
220
+ */
221
+ hook(stub, handler) {
222
+ if (!this.stubs.has(stub)) {
223
+ const error = new Error(ERROR_MESSAGE[ERROR_CODE.NOT_FOUND]);
224
+ IRPC_STORE.error(error);
225
+ return this;
226
+ }
227
+ const spec = this.stubs.get(stub);
228
+ this.hooks.get(spec).add(handler);
229
+ return this;
230
+ }
231
+ /**
232
+ * Resolves and executes all registered hooks for a given request
233
+ * @param req - The request containing the IRPC name and arguments
234
+ * @returns A promise that resolves when all hooks have been executed
235
+ * @throws Error if no IRPC exists for the request or if the hooks are not registered
236
+ */
237
+ async resolveHooks(req) {
238
+ const spec = this.specs.get(req.name);
239
+ if (!spec || !this.hooks.has(spec)) throw new Error(ERROR_MESSAGE[ERROR_CODE.NOT_FOUND]);
240
+ const hooks = this.hooks.get(spec);
241
+ for (const hook of hooks) await hook(req);
242
+ }
243
+ /**
135
244
  * Sets the transport mechanism for this package
136
245
  * @param transport - The transport instance to use for remote calls
137
246
  * @returns This IRPCPackage instance for chaining
@@ -139,6 +248,8 @@ var IRPCPackage = class {
139
248
  */
140
249
  use(transport) {
141
250
  if (!(transport instanceof IRPCTransport)) throw new Error(ERROR_MESSAGE[ERROR_CODE.TRANSPORT_INVALID]);
251
+ if (this.transport) this.transport.modules.delete(this);
252
+ transport.modules.add(this);
142
253
  this.config.transport = transport;
143
254
  return this;
144
255
  }
@@ -183,6 +294,66 @@ var IRPCPackage = class {
183
294
  function createPackage(config) {
184
295
  return new IRPCPackage(config);
185
296
  }
297
+ /**
298
+ * Intercepts local function call to get an instant response without remote execution.
299
+ *
300
+ * @param reader - The reader object to intercept.
301
+ * @param spec - The IRPC specification for the function call.
302
+ * @param args - The arguments to be passed to the function.
303
+ * @returns {IRPCReader<IRPCData>} - The IRPCReader object for consumer.
304
+ */
305
+ function intercept(spec, args, reader) {
306
+ const signal = getAbortSignal();
307
+ if (signal?.aborted) {
308
+ reader.abort();
309
+ return reader;
310
+ }
311
+ const abort = () => reader.abort();
312
+ signal?.addEventListener("abort", abort, { once: true });
313
+ try {
314
+ const result = spec.handler(...args);
315
+ if (!(result instanceof Promise)) {
316
+ reader.accept(result);
317
+ signal?.removeEventListener("abort", abort);
318
+ return reader;
319
+ }
320
+ if (!(result instanceof RemoteState)) {
321
+ result.then((value) => {
322
+ reader.accept(value);
323
+ }).catch((err) => {
324
+ reader.reject(err);
325
+ }).finally(() => {
326
+ signal?.removeEventListener("abort", abort);
327
+ });
328
+ return reader;
329
+ }
330
+ anchor.assign(reader.state, result.state);
331
+ const unsubscribe = result.subscribe((_, event) => {
332
+ if (event.type === "init") return;
333
+ const [rootKey] = event.keys;
334
+ if (rootKey === "status") {
335
+ reader.status = event.value;
336
+ if (reader.status === IRPC_STATUS.SUCCESS || reader.status === IRPC_STATUS.ERROR) {
337
+ signal?.removeEventListener("abort", subAbort);
338
+ unsubscribe();
339
+ }
340
+ return;
341
+ }
342
+ replay(reader.state, event);
343
+ });
344
+ const subAbort = () => {
345
+ unsubscribe();
346
+ reader.abort();
347
+ result.abort();
348
+ };
349
+ signal?.addEventListener("abort", subAbort, { once: true });
350
+ signal?.removeEventListener("abort", abort);
351
+ } catch (error) {
352
+ reader.reject(error);
353
+ signal?.removeEventListener("abort", abort);
354
+ }
355
+ return reader;
356
+ }
186
357
 
187
358
  //#endregion
188
- export { IRPCPackage, createPackage };
359
+ export { IRPCPackage, createPackage, intercept };
@@ -0,0 +1,32 @@
1
+ import { IRPCFileMeta, IRPCFileStream } from "./file.js";
2
+ import { IRPCData } from "./types.js";
3
+
4
+ //#region src/packet.d.ts
5
+ declare const IRPC_FILE_IDENTIFIER: "IRPC_PACKET_FILE";
6
+ type IRPCFilePointer = {
7
+ id: string;
8
+ type: typeof IRPC_FILE_IDENTIFIER;
9
+ meta: IRPCFileMeta;
10
+ };
11
+ type IRPCFileQueue = {
12
+ file: IRPCFilePointer;
13
+ data: Blob;
14
+ };
15
+ type IRPCPacketJson = {
16
+ data: IRPCData;
17
+ files: IRPCFilePointer[];
18
+ };
19
+ type IRPCPacketQueues = {
20
+ json: IRPCPacketJson;
21
+ queues: IRPCFileQueue[];
22
+ };
23
+ type PacketStream = {
24
+ data: IRPCData;
25
+ files: Map<string, IRPCFileStream>;
26
+ resolved: number;
27
+ };
28
+ declare function isFilePointer(data: IRPCData): boolean;
29
+ declare function encode(data: IRPCData): IRPCPacketQueues;
30
+ declare function decode(packet: IRPCPacketJson): PacketStream;
31
+ //#endregion
32
+ export { IRPCFilePointer, IRPCFileQueue, IRPCPacketJson, IRPCPacketQueues, IRPC_FILE_IDENTIFIER, PacketStream, decode, encode, isFilePointer };
package/dist/packet.js ADDED
@@ -0,0 +1,100 @@
1
+ import { IRPCFile, IRPCFileStream } from "./file.js";
2
+ import { isArray, isObject, uuid } from "@anchorlib/core";
3
+
4
+ //#region src/packet.ts
5
+ const IRPC_FILE_IDENTIFIER = "IRPC_PACKET_FILE";
6
+ function isFilePointer(data) {
7
+ return isObject(data) && data.type === IRPC_FILE_IDENTIFIER;
8
+ }
9
+ function encode(data) {
10
+ const json = {
11
+ data,
12
+ files: []
13
+ };
14
+ const packet = {
15
+ json,
16
+ queues: []
17
+ };
18
+ if (data instanceof IRPCFile) {
19
+ const { pointer, queue } = createPointer(data);
20
+ json.data = pointer;
21
+ json.files.push(pointer);
22
+ packet.queues.push(queue);
23
+ } else if (isObject(data) || isArray(data)) encodePointers(data, json.files, packet.queues);
24
+ return packet;
25
+ }
26
+ function decode(packet) {
27
+ const files = packet.files.map((file) => {
28
+ return [file.id, new IRPCFileStream(file.meta)];
29
+ });
30
+ const stream = {
31
+ data: packet.data,
32
+ files: new Map(files),
33
+ resolved: 0
34
+ };
35
+ if (isFilePointer(packet.data)) {
36
+ const { id } = packet.data;
37
+ stream.data = stream.files.get(id);
38
+ } else if (isObject(packet.data) || isArray(packet.data)) decodePointers(packet.data, stream.files);
39
+ return stream;
40
+ }
41
+ function createPointer(file) {
42
+ const pointer = {
43
+ id: uuid(),
44
+ meta: file.meta,
45
+ type: IRPC_FILE_IDENTIFIER
46
+ };
47
+ return {
48
+ pointer,
49
+ queue: {
50
+ file: pointer,
51
+ data: file.data
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Replace all IRPCFile inside an object with IRPCPacketFile.
57
+ * @param {Record<string, unknown> | unknown[]} data - The object to encode.
58
+ * @param {IRPCFilePointer[]} pointers - The array of IRPCPacketFile to replace.
59
+ * @param {IRPCFileQueue[]} queues - The array of IRPCPacketFileQueue to replace.
60
+ */
61
+ function encodePointers(data, pointers, queues) {
62
+ if (isArray(data)) data.forEach((item, i) => {
63
+ if (item instanceof IRPCFile) {
64
+ const { pointer, queue } = createPointer(item);
65
+ data[i] = pointer;
66
+ pointers.push(pointer);
67
+ queues.push(queue);
68
+ } else if (isObject(item) || isArray(item)) encodePointers(item, pointers, queues);
69
+ });
70
+ else if (isObject(data)) Object.entries(data).forEach(([key, value]) => {
71
+ if (value instanceof IRPCFile) {
72
+ const { pointer, queue } = createPointer(value);
73
+ data[key] = pointer;
74
+ pointers.push(pointer);
75
+ queues.push(queue);
76
+ } else if (isObject(value) || isArray(value)) encodePointers(value, pointers, queues);
77
+ });
78
+ }
79
+ /**
80
+ * Replace all IRPCPacketFile inside an object with IRPCFileStream.
81
+ * @param data - The object to decode.
82
+ * @param files - The map of IRPCFileStream to replace.
83
+ */
84
+ function decodePointers(data, files) {
85
+ if (isArray(data)) data.forEach((item, i) => {
86
+ if (isFilePointer(item)) {
87
+ const { id } = item;
88
+ data[i] = files.get(id);
89
+ } else if (isObject(item) || isArray(item)) decodePointers(item, files);
90
+ });
91
+ else if (isObject(data)) Object.entries(data).forEach(([key, value]) => {
92
+ if (isFilePointer(value)) {
93
+ const { id } = value;
94
+ data[key] = files.get(id);
95
+ } else if (isObject(value) || isArray(value)) decodePointers(value, files);
96
+ });
97
+ }
98
+
99
+ //#endregion
100
+ export { IRPC_FILE_IDENTIFIER, decode, encode, isFilePointer };
package/dist/reader.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IRPCData, IRPCPacketStream } from "./types.js";
1
+ import { IRPCData, IRPCPacketStream, IRPCStatus } from "./types.js";
2
2
  import { RemoteState } from "./state.js";
3
3
 
4
4
  //#region src/reader.d.ts
@@ -10,8 +10,16 @@ import { RemoteState } from "./state.js";
10
10
  */
11
11
  declare class IRPCReader<T extends IRPCData> extends RemoteState<T> {
12
12
  id: string;
13
- packets: Set<IRPCPacketStream<T>>;
14
- constructor(id: string, init?: T);
13
+ onClose?: () => void;
14
+ /**
15
+ * Initializes a new RemoteState with an optional initial payload.
16
+ *
17
+ * @param id - The unique identifier for this state instance.
18
+ * @param init - An optional starting value for the data payload.
19
+ * @param status - The initial status of the state (PENDING, SUCCESS, ERROR).
20
+ * @param resumable - Whether the state should be resumable after being closed.
21
+ */
22
+ constructor(id: string, init?: T, status?: IRPCStatus, resumable?: boolean);
15
23
  /**
16
24
  * Pushes incoming network packets into this reader, evaluating payload data
17
25
  * and subsequently updating the core state values locally.
@@ -19,6 +27,12 @@ declare class IRPCReader<T extends IRPCData> extends RemoteState<T> {
19
27
  * @param packet - The incoming unified Stream Packet structure (`ANSWER`, `EVENT`, or `CLOSE`).
20
28
  */
21
29
  push(packet: IRPCPacketStream<T>): void;
30
+ close(): void;
31
+ /**
32
+ * Ensures that chained Promise operations return standard Promises
33
+ * rather than instantiating new RemoteState subclasses.
34
+ */
35
+ static get [Symbol.species](): PromiseConstructor;
22
36
  }
23
37
  //#endregion
24
38
  export { IRPCReader };
package/dist/reader.js CHANGED
@@ -9,9 +9,17 @@ import { replay } from "@anchorlib/core";
9
9
  * @template T - The type of data yielded by the stream.
10
10
  */
11
11
  var IRPCReader = class extends RemoteState {
12
- packets = /* @__PURE__ */ new Set();
13
- constructor(id, init) {
14
- super(init);
12
+ onClose;
13
+ /**
14
+ * Initializes a new RemoteState with an optional initial payload.
15
+ *
16
+ * @param id - The unique identifier for this state instance.
17
+ * @param init - An optional starting value for the data payload.
18
+ * @param status - The initial status of the state (PENDING, SUCCESS, ERROR).
19
+ * @param resumable - Whether the state should be resumable after being closed.
20
+ */
21
+ constructor(id, init, status = IRPC_STATUS.PENDING, resumable) {
22
+ super(init, status, resumable);
15
23
  this.id = id;
16
24
  }
17
25
  /**
@@ -22,7 +30,6 @@ var IRPCReader = class extends RemoteState {
22
30
  */
23
31
  push(packet) {
24
32
  packet.arrivedAt = Date.now();
25
- this.packets.add(packet);
26
33
  if (packet.type === IRPC_PACKET_TYPE.ANSWER) if (packet.status === IRPC_STATUS.ERROR) this.error = new Error(packet.error.message);
27
34
  else this.data = packet.data;
28
35
  else if (packet.type === IRPC_PACKET_TYPE.EVENT) replay(this.state, packet.data);
@@ -31,6 +38,18 @@ var IRPCReader = class extends RemoteState {
31
38
  }
32
39
  this.status = packet.status;
33
40
  }
41
+ close() {
42
+ this.status = IRPC_STATUS.SUCCESS;
43
+ super.close();
44
+ this.onClose?.();
45
+ }
46
+ /**
47
+ * Ensures that chained Promise operations return standard Promises
48
+ * rather than instantiating new RemoteState subclasses.
49
+ */
50
+ static get [Symbol.species]() {
51
+ return Promise;
52
+ }
34
53
  };
35
54
 
36
55
  //#endregion
@@ -1,5 +1,5 @@
1
- import { IRPCDataSchema, IRPCInputs, IRPCOutput, IRPCRequest, IRPCResponse, IRPCSpec } from "./types.js";
2
1
  import { IRPCPackage } from "./module.js";
2
+ import { IRPCDataSchema, IRPCInputs, IRPCOutput, IRPCRequest, IRPCResponse, IRPCSpec } from "./types.js";
3
3
 
4
4
  //#region src/resolver.d.ts
5
5
 
package/dist/resolver.js CHANGED
@@ -70,6 +70,11 @@ var IRPCResolver = class {
70
70
  */
71
71
  async forward({ id, name, args }, schema) {
72
72
  try {
73
+ await this.module.resolveHooks({
74
+ id,
75
+ name,
76
+ args
77
+ });
73
78
  const result = this.module.resolve({
74
79
  id,
75
80
  name,
@@ -91,7 +96,25 @@ var IRPCResolver = class {
91
96
  result
92
97
  };
93
98
  }
94
- const output = parseOutput(await result, schema);
99
+ const data = await result;
100
+ if (data instanceof RemoteState) {
101
+ data.unpipe();
102
+ const output$1 = parseOutput(data.data, schema);
103
+ if (!output$1.success) return {
104
+ id,
105
+ name,
106
+ error: {
107
+ code: ERROR_CODE.INVALID_OUTPUT,
108
+ message: output$1.error?.message
109
+ }
110
+ };
111
+ return {
112
+ id,
113
+ name,
114
+ result: data
115
+ };
116
+ }
117
+ const output = parseOutput(data, schema);
95
118
  if (output.success) return {
96
119
  id,
97
120
  name,
@@ -110,7 +133,7 @@ var IRPCResolver = class {
110
133
  id,
111
134
  name,
112
135
  error: {
113
- code: ERROR_CODE.UNKNOWN,
136
+ code: ERROR_CODE.HANDLER_ERROR,
114
137
  message: error.message
115
138
  }
116
139
  };
@@ -0,0 +1,53 @@
1
+ import { IRPCPackage } from "./module.js";
2
+ import { IRPCTransport } from "./transport.js";
3
+ import { IRPCRequest } from "./types.js";
4
+
5
+ //#region src/router.d.ts
6
+ type IRPCHook = () => void | Promise<void>;
7
+ declare class IRPCRouter {
8
+ module: IRPCPackage;
9
+ transport: IRPCTransport;
10
+ /** Array of middleware functions to be executed */
11
+ hooks: IRPCHook[];
12
+ /**
13
+ * Creates a new Router instance
14
+ * @param {IRPCPackage} module - The IRPC package module to resolve requests against
15
+ * @param {IRPCTransport} transport - The transport mechanism to use for resolving requests
16
+ */
17
+ constructor(module: IRPCPackage, transport: IRPCTransport);
18
+ /**
19
+ * Adds a hook function to the router
20
+ * @param hook - The hook function to add
21
+ * @returns The current Router instance for chaining
22
+ */
23
+ use(hook: IRPCHook): this;
24
+ /**
25
+ * Run a function within an isolated IRPC Router context.
26
+ * This make sure any subsequent RPC calls will be seeded with the hooks added to the router.
27
+ *
28
+ * @param handler - The handler function to isolate
29
+ * @param controller - The AbortController to use for cancellation
30
+ * @param context - Additional context to pass to the handler
31
+ * @param preHook - A hook function to run before the router hooks
32
+ * @returns The result of the isolated handler function
33
+ */
34
+ isolate<T>(handler: () => T | Promise<T>, controller: AbortController, context?: Array<[string | symbol, unknown]>, preHook?: IRPCHook): Promise<Promise<T>>;
35
+ /**
36
+ * Resolves hook functions for a given request
37
+ * @param req - The IRPC request to process hook for
38
+ * @returns An error response if hook fails, undefined otherwise
39
+ */
40
+ protected resolveHooks(req: IRPCRequest): Promise<{
41
+ id: string;
42
+ name: string;
43
+ type: "close";
44
+ status: "error";
45
+ error: {
46
+ code: string;
47
+ message: string;
48
+ };
49
+ createdAt: number;
50
+ } | undefined>;
51
+ }
52
+ //#endregion
53
+ export { IRPCHook, IRPCRouter };