@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/router.js ADDED
@@ -0,0 +1,81 @@
1
+ import { IRPC_BASE_CONTEXT, IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
2
+ import { ERROR_CODE, ERROR_MESSAGE } from "./error.js";
3
+ import { createContextStore, withContext } from "./context.js";
4
+ import { IRPC_STORE } from "./store.js";
5
+
6
+ //#region src/router.ts
7
+ var IRPCRouter = class {
8
+ /** Array of middleware functions to be executed */
9
+ hooks = [];
10
+ /**
11
+ * Creates a new Router instance
12
+ * @param {IRPCPackage} module - The IRPC package module to resolve requests against
13
+ * @param {IRPCTransport} transport - The transport mechanism to use for resolving requests
14
+ */
15
+ constructor(module, transport) {
16
+ this.module = module;
17
+ this.transport = transport;
18
+ IRPC_STORE.route(this);
19
+ }
20
+ /**
21
+ * Adds a hook function to the router
22
+ * @param hook - The hook function to add
23
+ * @returns The current Router instance for chaining
24
+ */
25
+ use(hook) {
26
+ if (typeof hook !== "function") {
27
+ const error = new Error(ERROR_MESSAGE[ERROR_CODE.INVALID_HOOK]);
28
+ IRPC_STORE.error(error);
29
+ return this;
30
+ }
31
+ this.hooks.push(hook);
32
+ return this;
33
+ }
34
+ /**
35
+ * Run a function within an isolated IRPC Router context.
36
+ * This make sure any subsequent RPC calls will be seeded with the hooks added to the router.
37
+ *
38
+ * @param handler - The handler function to isolate
39
+ * @param controller - The AbortController to use for cancellation
40
+ * @param context - Additional context to pass to the handler
41
+ * @param preHook - A hook function to run before the router hooks
42
+ * @returns The result of the isolated handler function
43
+ */
44
+ isolate(handler, controller, context = [], preHook) {
45
+ return withContext(createContextStore([
46
+ [IRPC_BASE_CONTEXT.ABORT_SIGNAL, controller.signal],
47
+ [IRPC_BASE_CONTEXT.ABORT_CONTROLLER, controller],
48
+ ...context
49
+ ]), async () => {
50
+ await preHook?.();
51
+ for (const hook of this.hooks) await hook();
52
+ return handler();
53
+ });
54
+ }
55
+ /**
56
+ * Resolves hook functions for a given request
57
+ * @param req - The IRPC request to process hook for
58
+ * @returns An error response if hook fails, undefined otherwise
59
+ */
60
+ async resolveHooks(req) {
61
+ for (const hook of this.hooks) try {
62
+ await hook();
63
+ } catch (error) {
64
+ IRPC_STORE.error(error, [req.id, req.name]);
65
+ return {
66
+ id: req.id,
67
+ name: req.name,
68
+ type: IRPC_PACKET_TYPE.CLOSE,
69
+ status: IRPC_STATUS.ERROR,
70
+ error: {
71
+ code: ERROR_CODE.UNKNOWN,
72
+ message: ERROR_MESSAGE[ERROR_CODE.UNKNOWN]
73
+ },
74
+ createdAt: Date.now()
75
+ };
76
+ }
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ export { IRPCRouter };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,15 @@
1
+ import { setAsyncScope, setReactive } from "@anchorlib/core";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+
4
+ //#region src/server/index.ts
5
+ var AnchorASL = class extends AsyncLocalStorage {
6
+ store = /* @__PURE__ */ new Map();
7
+ getStore() {
8
+ return super.getStore() ?? this.store;
9
+ }
10
+ };
11
+ setAsyncScope(new AnchorASL());
12
+ setReactive(false);
13
+
14
+ //#endregion
15
+ export { };
package/dist/state.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IRPCReadable, IRPCStatus } from "./types.js";
1
+ import { IRPCReadable, IRPCStatus, StreamConstructor } from "./types.js";
2
2
  import * as _anchorlib_core0 from "@anchorlib/core";
3
3
  import { StateSubscriber } from "@anchorlib/core";
4
4
 
@@ -14,9 +14,9 @@ import { StateSubscriber } from "@anchorlib/core";
14
14
  * @template T - The type of data held by the state.
15
15
  */
16
16
  declare class RemoteState<T> extends Promise<T> {
17
- protected readonly state: IRPCReadable<T>;
18
- protected readonly accept: (value: T) => void;
19
- protected readonly reject: (error: Error) => void;
17
+ #private;
18
+ private resumable?;
19
+ get state(): IRPCReadable<T>;
20
20
  /**
21
21
  * The current data payload of the state.
22
22
  */
@@ -37,8 +37,13 @@ declare class RemoteState<T> extends Promise<T> {
37
37
  * Initializes a new RemoteState with an optional initial payload.
38
38
  *
39
39
  * @param init - An optional starting value for the data payload.
40
+ * @param status - The initial status of the state (PENDING, SUCCESS, ERROR).
41
+ * @param resumable - Whether the state should be resumable after being closed.
40
42
  */
41
- constructor(init?: T);
43
+ constructor(init?: T, status?: IRPCStatus, resumable?: boolean | undefined);
44
+ accept(value?: T): void;
45
+ reject(error?: Error): void;
46
+ abort(): void;
42
47
  /**
43
48
  * Subscribes to changes emitted by the internal state.
44
49
  *
@@ -46,6 +51,13 @@ declare class RemoteState<T> extends Promise<T> {
46
51
  * @returns An unsubscribe function to terminate the listener.
47
52
  */
48
53
  subscribe(handler: StateSubscriber<IRPCReadable<T>>): _anchorlib_core0.StateUnsubscribe;
54
+ /**
55
+ * Closes the reactive state and terminates the underlying Promise.
56
+ */
57
+ close(): void;
58
+ protected resume(): void;
59
+ pipe(): this;
60
+ unpipe(): this;
49
61
  /**
50
62
  * Destroys the reactive state bindings.
51
63
  */
@@ -56,17 +68,6 @@ declare class RemoteState<T> extends Promise<T> {
56
68
  */
57
69
  static get [Symbol.species](): PromiseConstructor;
58
70
  }
59
- /**
60
- * A callback function type used to natively construct and drive a reactive stream.
61
- * It provides the initial reactive data reference and terminal resolution hooks
62
- * without forcing strict async/await boundaries, securely yielding stream operations.
63
- *
64
- * @template T - The type of data yielded globally by the stream.
65
- * @param data - The mutable data payload natively tracked by RemoteState.
66
- * @param resolve - Callback to statically mark the stream as successfully completed, optionally with a resolved value.
67
- * @param reject - Callback to forcefully throw a runtime error into the stream structure.
68
- */
69
- type StreamConstructor<T> = (data: T, resolve: (value?: T) => void, reject: (error: Error) => void) => void | Promise<void>;
70
71
  /**
71
72
  * A utility factory to structurally instantiate an active `RemoteState` pipeline natively
72
73
  * decoupled from standard Promise chains. This elegantly captures constructor functions
@@ -79,4 +80,4 @@ type StreamConstructor<T> = (data: T, resolve: (value?: T) => void, reject: (err
79
80
  */
80
81
  declare function stream<T>(construct: StreamConstructor<T>, init?: T): RemoteState<T>;
81
82
  //#endregion
82
- export { RemoteState, StreamConstructor, stream };
83
+ export { RemoteState, stream };
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,99 @@ 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
+ #locked;
22
+ get state() {
23
+ return this.#state;
24
+ }
18
25
  /**
19
26
  * The current data payload of the state.
20
27
  */
21
28
  get data() {
22
- return this.state.data;
29
+ return this.#state.data;
23
30
  }
24
31
  set data(data) {
25
- this.state.data = data;
32
+ if (this.#closed) return;
33
+ this.#state.data = data;
26
34
  }
27
35
  /**
28
36
  * The current error encountered by the state, if any.
29
37
  */
30
38
  get error() {
31
- return this.state.error;
39
+ return this.#state.error;
32
40
  }
33
41
  set error(error) {
34
- this.state.error = error;
42
+ if (this.#closed) return;
43
+ this.#state.error = error;
35
44
  }
36
45
  /**
37
46
  * The execution status of the state (PENDING, SUCCESS, ERROR).
38
47
  * Transitioning to a terminal status (SUCCESS or ERROR) will automatically resolve or reject the underlying Promise.
39
48
  */
40
49
  get status() {
41
- return this.state.status;
50
+ return this.#state.status;
42
51
  }
43
52
  set status(status) {
53
+ if (this.#closed) return;
44
54
  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
- }
55
+ if (status === IRPC_STATUS.ERROR) this.reject();
56
+ else if (status === IRPC_STATUS.SUCCESS) this.accept();
52
57
  }
53
58
  /**
54
59
  * Initializes a new RemoteState with an optional initial payload.
55
60
  *
56
61
  * @param init - An optional starting value for the data payload.
62
+ * @param status - The initial status of the state (PENDING, SUCCESS, ERROR).
63
+ * @param resumable - Whether the state should be resumable after being closed.
57
64
  */
58
- constructor(init) {
65
+ constructor(init, status = IRPC_STATUS.PENDING, resumable) {
59
66
  let acceptFn;
60
67
  let rejectFn;
61
68
  super((resolve, reject) => {
69
+ if (resumable) resolve(init);
62
70
  acceptFn = resolve;
63
71
  rejectFn = reject;
64
72
  });
65
- this.accept = acceptFn;
66
- this.reject = rejectFn;
67
- this.state = mutable({
73
+ this.resumable = resumable;
74
+ this.#accept = acceptFn;
75
+ this.#reject = rejectFn;
76
+ this.#state = mutable({
68
77
  data: init,
69
78
  error: void 0,
70
- status: IRPC_STATUS.PENDING
79
+ status
80
+ });
81
+ onCleanup(() => this.close());
82
+ }
83
+ accept(...args) {
84
+ $do(() => {
85
+ if (this.#closed) return;
86
+ const value = args.length ? args[0] : this.data;
87
+ this.#closed = true;
88
+ this.#state.status = IRPC_STATUS.SUCCESS;
89
+ this.#state.data = value;
90
+ this.#accept(value);
91
+ this.destroy();
92
+ });
93
+ }
94
+ reject(...args) {
95
+ $do(() => {
96
+ if (this.#closed) return;
97
+ if (args.length) this.#state.error = args[0];
98
+ this.#closed = true;
99
+ this.#state.status = IRPC_STATUS.ERROR;
100
+ this.#reject(this.error ?? new Error(ERROR_MESSAGE[ERROR_CODE.UNKNOWN]));
101
+ this.destroy();
102
+ });
103
+ }
104
+ abort() {
105
+ $do(() => {
106
+ this.#closed = true;
107
+ this.#state.status = IRPC_STATUS.ABORTED;
108
+ this.#accept(this.data);
109
+ this.destroy();
71
110
  });
72
111
  }
73
112
  /**
@@ -80,9 +119,33 @@ var RemoteState = class extends Promise {
80
119
  return subscribe(this.state, handler);
81
120
  }
82
121
  /**
122
+ * Closes the reactive state and terminates the underlying Promise.
123
+ */
124
+ close() {
125
+ if (this.#closed) return;
126
+ this.#closed = true;
127
+ this.#accept(this.data);
128
+ this.destroy();
129
+ }
130
+ resume() {
131
+ this.#closed = false;
132
+ }
133
+ pipe() {
134
+ this.#locked = this.then;
135
+ this.then = void 0;
136
+ return this;
137
+ }
138
+ unpipe() {
139
+ if (!this.#locked) return this;
140
+ this.then = this.#locked;
141
+ this.#locked = void 0;
142
+ return this;
143
+ }
144
+ /**
83
145
  * Destroys the reactive state bindings.
84
146
  */
85
147
  destroy() {
148
+ if (this.resumable) return;
86
149
  anchor.destroy(this.state);
87
150
  }
88
151
  /**
@@ -105,17 +168,27 @@ var RemoteState = class extends Promise {
105
168
  */
106
169
  function stream(construct, init) {
107
170
  const state = new RemoteState(init);
171
+ const abortSignal = getAbortSignal();
108
172
  const accept = ((...values) => {
109
173
  if (values.length > 0) state.data = values[0];
110
174
  state.status = IRPC_STATUS.SUCCESS;
175
+ abortSignal?.removeEventListener("abort", abort);
111
176
  });
112
177
  const reject = (error) => {
113
178
  state.error = error;
114
179
  state.status = IRPC_STATUS.ERROR;
180
+ abortSignal?.removeEventListener("abort", abort);
115
181
  };
182
+ const abort = () => state.abort();
183
+ abortSignal?.addEventListener("abort", abort, { once: true });
116
184
  try {
117
- const result = construct(state.data, accept, reject);
118
- if (result instanceof Promise) result.catch(reject);
185
+ const cleanup = construct(state, accept, reject);
186
+ if (cleanup instanceof Promise) cleanup.then((futureCleanup) => {
187
+ if (typeof futureCleanup === "function") if (abortSignal?.aborted || state.status !== IRPC_STATUS.PENDING) $do(futureCleanup);
188
+ else abortSignal?.addEventListener("abort", () => $do(futureCleanup), { once: true });
189
+ }).catch(reject);
190
+ else if (typeof cleanup === "function") if (abortSignal?.aborted || state.status !== IRPC_STATUS.PENDING) $do(cleanup);
191
+ else abortSignal?.addEventListener("abort", () => $do(cleanup), { once: true });
119
192
  } catch (error) {
120
193
  reject(error);
121
194
  }
@@ -0,0 +1,44 @@
1
+ import { IRPC_STORE_EVENT } from "./enum.js";
2
+ import { IRPCPackage } from "./module.js";
3
+ import { IRPCData } from "./types.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 };