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

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.
@@ -0,0 +1,82 @@
1
+ import { IRPCReadable, IRPCStatus } from "./types.js";
2
+ import * as _anchorlib_core0 from "@anchorlib/core";
3
+ import { StateSubscriber } from "@anchorlib/core";
4
+
5
+ //#region src/state.d.ts
6
+
7
+ /**
8
+ * A reactive state wrapper that implements the standard Promise interface.
9
+ *
10
+ * RemoteState acts as a dual-layer abstraction:
11
+ * 1. For asynchronous execution, it operates as a `Promise<T>` that resolves upon completion or rejects upon failure.
12
+ * 2. For reactive environments, it exposes an `.subscribe()` method to react to intermediate data mutations.
13
+ *
14
+ * @template T - The type of data held by the state.
15
+ */
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;
20
+ /**
21
+ * The current data payload of the state.
22
+ */
23
+ get data(): T;
24
+ set data(data: T);
25
+ /**
26
+ * The current error encountered by the state, if any.
27
+ */
28
+ get error(): Error | undefined;
29
+ set error(error: Error | undefined);
30
+ /**
31
+ * The execution status of the state (PENDING, SUCCESS, ERROR).
32
+ * Transitioning to a terminal status (SUCCESS or ERROR) will automatically resolve or reject the underlying Promise.
33
+ */
34
+ get status(): IRPCStatus;
35
+ set status(status: IRPCStatus);
36
+ /**
37
+ * Initializes a new RemoteState with an optional initial payload.
38
+ *
39
+ * @param init - An optional starting value for the data payload.
40
+ */
41
+ constructor(init?: T);
42
+ /**
43
+ * Subscribes to changes emitted by the internal state.
44
+ *
45
+ * @param handler - A callback function invoked whenever the state mutates.
46
+ * @returns An unsubscribe function to terminate the listener.
47
+ */
48
+ subscribe(handler: StateSubscriber<IRPCReadable<T>>): _anchorlib_core0.StateUnsubscribe;
49
+ /**
50
+ * Destroys the reactive state bindings.
51
+ */
52
+ protected destroy(): void;
53
+ /**
54
+ * Ensures that chained Promise operations return standard Promises
55
+ * rather than instantiating new RemoteState subclasses.
56
+ */
57
+ static get [Symbol.species](): PromiseConstructor;
58
+ }
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
+ * A utility factory to structurally instantiate an active `RemoteState` pipeline natively
72
+ * decoupled from standard Promise chains. This elegantly captures constructor functions
73
+ * pushing events into the state before terminating mechanically via secure internal hooks.
74
+ *
75
+ * @template T - The type of the streamed payload data.
76
+ * @param construct - The isolated stream constructor callback that natively operates the pipeline.
77
+ * @param init - An optional initial value to prime the state payload inherently.
78
+ * @returns A fully active RemoteState inherently bound to the callbacks executing natively.
79
+ */
80
+ declare function stream<T>(construct: StreamConstructor<T>, init?: T): RemoteState<T>;
81
+ //#endregion
82
+ export { RemoteState, StreamConstructor, stream };
package/dist/state.js ADDED
@@ -0,0 +1,126 @@
1
+ import { IRPC_STATUS } from "./enum.js";
2
+ import { anchor, mutable, subscribe } from "@anchorlib/core";
3
+
4
+ //#region src/state.ts
5
+ /**
6
+ * A reactive state wrapper that implements the standard Promise interface.
7
+ *
8
+ * RemoteState acts as a dual-layer abstraction:
9
+ * 1. For asynchronous execution, it operates as a `Promise<T>` that resolves upon completion or rejects upon failure.
10
+ * 2. For reactive environments, it exposes an `.subscribe()` method to react to intermediate data mutations.
11
+ *
12
+ * @template T - The type of data held by the state.
13
+ */
14
+ var RemoteState = class extends Promise {
15
+ state;
16
+ accept;
17
+ reject;
18
+ /**
19
+ * The current data payload of the state.
20
+ */
21
+ get data() {
22
+ return this.state.data;
23
+ }
24
+ set data(data) {
25
+ this.state.data = data;
26
+ }
27
+ /**
28
+ * The current error encountered by the state, if any.
29
+ */
30
+ get error() {
31
+ return this.state.error;
32
+ }
33
+ set error(error) {
34
+ this.state.error = error;
35
+ }
36
+ /**
37
+ * The execution status of the state (PENDING, SUCCESS, ERROR).
38
+ * Transitioning to a terminal status (SUCCESS or ERROR) will automatically resolve or reject the underlying Promise.
39
+ */
40
+ get status() {
41
+ return this.state.status;
42
+ }
43
+ set status(status) {
44
+ 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
+ }
52
+ }
53
+ /**
54
+ * Initializes a new RemoteState with an optional initial payload.
55
+ *
56
+ * @param init - An optional starting value for the data payload.
57
+ */
58
+ constructor(init) {
59
+ let acceptFn;
60
+ let rejectFn;
61
+ super((resolve, reject) => {
62
+ acceptFn = resolve;
63
+ rejectFn = reject;
64
+ });
65
+ this.accept = acceptFn;
66
+ this.reject = rejectFn;
67
+ this.state = mutable({
68
+ data: init,
69
+ error: void 0,
70
+ status: IRPC_STATUS.PENDING
71
+ });
72
+ }
73
+ /**
74
+ * Subscribes to changes emitted by the internal state.
75
+ *
76
+ * @param handler - A callback function invoked whenever the state mutates.
77
+ * @returns An unsubscribe function to terminate the listener.
78
+ */
79
+ subscribe(handler) {
80
+ return subscribe(this.state, handler);
81
+ }
82
+ /**
83
+ * Destroys the reactive state bindings.
84
+ */
85
+ destroy() {
86
+ anchor.destroy(this.state);
87
+ }
88
+ /**
89
+ * Ensures that chained Promise operations return standard Promises
90
+ * rather than instantiating new RemoteState subclasses.
91
+ */
92
+ static get [Symbol.species]() {
93
+ return Promise;
94
+ }
95
+ };
96
+ /**
97
+ * A utility factory to structurally instantiate an active `RemoteState` pipeline natively
98
+ * decoupled from standard Promise chains. This elegantly captures constructor functions
99
+ * pushing events into the state before terminating mechanically via secure internal hooks.
100
+ *
101
+ * @template T - The type of the streamed payload data.
102
+ * @param construct - The isolated stream constructor callback that natively operates the pipeline.
103
+ * @param init - An optional initial value to prime the state payload inherently.
104
+ * @returns A fully active RemoteState inherently bound to the callbacks executing natively.
105
+ */
106
+ function stream(construct, init) {
107
+ const state = new RemoteState(init);
108
+ const accept = ((...values) => {
109
+ if (values.length > 0) state.data = values[0];
110
+ state.status = IRPC_STATUS.SUCCESS;
111
+ });
112
+ const reject = (error) => {
113
+ state.error = error;
114
+ state.status = IRPC_STATUS.ERROR;
115
+ };
116
+ try {
117
+ const result = construct(state.data, accept, reject);
118
+ if (result instanceof Promise) result.catch(reject);
119
+ } catch (error) {
120
+ reject(error);
121
+ }
122
+ return state;
123
+ }
124
+
125
+ //#endregion
126
+ export { RemoteState, stream };
@@ -0,0 +1,57 @@
1
+ import { IRPCData, IRPCError, IRPCPacketStream, IRPCResponse, IRPCStatus } from "./types.js";
2
+
3
+ //#region src/stream.d.ts
4
+
5
+ /**
6
+ * A server-side producer that normalizes and serializes RPC outputs into standard transport packets.
7
+ *
8
+ * Supports both standard asynchronous responses and reactive streams. When handling a continuous stream,
9
+ * it intercepts state mutations and emits sequential network packets (`ANSWER`, `EVENT`, `CLOSE`).
10
+ *
11
+ * @template T - The type of data yielded by the stream.
12
+ */
13
+ declare class IRPCStream<T extends IRPCData> {
14
+ private id;
15
+ private name;
16
+ private initializer;
17
+ private pipeHandlers;
18
+ private closeHandlers;
19
+ private errorHandlers;
20
+ value?: T;
21
+ error?: IRPCError;
22
+ status: IRPCStatus;
23
+ /**
24
+ * Initializes a stream wrapping an asynchronous RPC execution.
25
+ *
26
+ * @param id - The unique identifier of the RPC request.
27
+ * @param name - The name of the specification processing the execution.
28
+ * @param initializer - An execution callback that yields an IRPCResponse.
29
+ */
30
+ constructor(id: string, name: string, initializer: () => Promise<IRPCResponse>);
31
+ /**
32
+ * Evaluates the underlying initializer and propagates standard transport packets
33
+ * to all bound pipe handlers based on the output lifecycle.
34
+ */
35
+ private start;
36
+ /**
37
+ * Binds a handler to receive the outbound stream packets.
38
+ * If invoked after the stream has fulfilled or rejected natively, it automatically plays back the conclusive packet.
39
+ *
40
+ * @param handler - A callback function to receive packets.
41
+ */
42
+ pipe(handler: (event: IRPCPacketStream<T>) => void): void;
43
+ /**
44
+ * Binds a handler to trap any internal runtime failures independently.
45
+ *
46
+ * @param handler - A callback function to receive stream errors.
47
+ */
48
+ catch(handler: (error: IRPCError) => void): void;
49
+ /**
50
+ * Binds a handler triggered upon terminal completion of the stream process (success or error).
51
+ *
52
+ * @param handler - A callback function invoked at stream completion.
53
+ */
54
+ close(handler: () => void): void;
55
+ }
56
+ //#endregion
57
+ export { IRPCStream };
package/dist/stream.js ADDED
@@ -0,0 +1,204 @@
1
+ import { IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
2
+ import { ERROR_CODE } from "./error.js";
3
+ import { RemoteState } from "./state.js";
4
+
5
+ //#region src/stream.ts
6
+ /**
7
+ * A server-side producer that normalizes and serializes RPC outputs into standard transport packets.
8
+ *
9
+ * Supports both standard asynchronous responses and reactive streams. When handling a continuous stream,
10
+ * it intercepts state mutations and emits sequential network packets (`ANSWER`, `EVENT`, `CLOSE`).
11
+ *
12
+ * @template T - The type of data yielded by the stream.
13
+ */
14
+ var IRPCStream = class {
15
+ pipeHandlers = /* @__PURE__ */ new Set();
16
+ closeHandlers = /* @__PURE__ */ new Set();
17
+ errorHandlers = /* @__PURE__ */ new Set();
18
+ value;
19
+ error;
20
+ status = IRPC_STATUS.IDLE;
21
+ /**
22
+ * Initializes a stream wrapping an asynchronous RPC execution.
23
+ *
24
+ * @param id - The unique identifier of the RPC request.
25
+ * @param name - The name of the specification processing the execution.
26
+ * @param initializer - An execution callback that yields an IRPCResponse.
27
+ */
28
+ constructor(id, name, initializer) {
29
+ this.id = id;
30
+ this.name = name;
31
+ this.initializer = initializer;
32
+ }
33
+ /**
34
+ * Evaluates the underlying initializer and propagates standard transport packets
35
+ * to all bound pipe handlers based on the output lifecycle.
36
+ */
37
+ async start() {
38
+ if (this.status !== IRPC_STATUS.IDLE) return;
39
+ this.status = IRPC_STATUS.PENDING;
40
+ const { id, name } = this;
41
+ try {
42
+ const { result } = await this.initializer();
43
+ if (result instanceof RemoteState) {
44
+ this.value = result.data;
45
+ if (result.status === IRPC_STATUS.SUCCESS || result.status === IRPC_STATUS.ERROR) {
46
+ if (result.status === IRPC_STATUS.ERROR) {
47
+ this.error = {
48
+ code: ERROR_CODE.STREAM_ERROR,
49
+ message: result.error.message
50
+ };
51
+ this.status = IRPC_STATUS.ERROR;
52
+ } else this.status = IRPC_STATUS.SUCCESS;
53
+ const packet = {
54
+ id,
55
+ name,
56
+ type: IRPC_PACKET_TYPE.ANSWER,
57
+ data: this.value,
58
+ error: this.error,
59
+ status: this.status,
60
+ createdAt: Date.now()
61
+ };
62
+ this.pipeHandlers.forEach((handler) => handler(packet));
63
+ this.errorHandlers.forEach((handler) => handler(this.error));
64
+ this.closeHandlers.forEach((handler) => handler());
65
+ return;
66
+ }
67
+ this.pipeHandlers.forEach((handler) => {
68
+ handler({
69
+ id,
70
+ name,
71
+ type: IRPC_PACKET_TYPE.ANSWER,
72
+ data: result.data,
73
+ status: result.status,
74
+ createdAt: Date.now()
75
+ });
76
+ });
77
+ const unsubscribe = result.subscribe((state, { type, keys, value }) => {
78
+ if (type === "init") return;
79
+ const [rootKey] = keys;
80
+ if (rootKey === "data") this.pipeHandlers.forEach((handler) => {
81
+ handler({
82
+ id,
83
+ name,
84
+ type: IRPC_PACKET_TYPE.EVENT,
85
+ status: state.status,
86
+ data: {
87
+ type,
88
+ keys,
89
+ value
90
+ },
91
+ createdAt: Date.now()
92
+ });
93
+ });
94
+ else if (rootKey === "status") {
95
+ if (state.status !== IRPC_STATUS.SUCCESS && state.status !== IRPC_STATUS.ERROR) return;
96
+ this.status = state.status;
97
+ if (state.status === IRPC_STATUS.ERROR) {
98
+ this.error = {
99
+ code: ERROR_CODE.STREAM_ERROR,
100
+ message: state.error.message
101
+ };
102
+ this.errorHandlers.forEach((handler) => handler(this.error));
103
+ }
104
+ this.pipeHandlers.forEach((handler) => {
105
+ handler({
106
+ id,
107
+ name,
108
+ type: IRPC_PACKET_TYPE.CLOSE,
109
+ error: this.error,
110
+ status: this.status,
111
+ createdAt: Date.now()
112
+ });
113
+ });
114
+ this.closeHandlers.forEach((handler) => handler());
115
+ unsubscribe();
116
+ }
117
+ });
118
+ } else {
119
+ this.value = result;
120
+ this.status = IRPC_STATUS.SUCCESS;
121
+ const packet = {
122
+ id,
123
+ name,
124
+ type: IRPC_PACKET_TYPE.ANSWER,
125
+ status: IRPC_STATUS.SUCCESS,
126
+ data: this.value,
127
+ createdAt: Date.now()
128
+ };
129
+ this.pipeHandlers.forEach((handler) => handler(packet));
130
+ this.closeHandlers.forEach((handler) => handler());
131
+ }
132
+ } catch (error) {
133
+ this.error = {
134
+ code: ERROR_CODE.STREAM_ERROR,
135
+ message: error.message
136
+ };
137
+ this.status = IRPC_STATUS.ERROR;
138
+ this.pipeHandlers.forEach((handler) => {
139
+ handler({
140
+ id,
141
+ name,
142
+ type: IRPC_PACKET_TYPE.ANSWER,
143
+ status: IRPC_STATUS.ERROR,
144
+ error,
145
+ createdAt: Date.now()
146
+ });
147
+ });
148
+ this.errorHandlers.forEach((handler) => handler(this.error));
149
+ this.closeHandlers.forEach((handler) => handler());
150
+ return;
151
+ }
152
+ }
153
+ /**
154
+ * Binds a handler to receive the outbound stream packets.
155
+ * If invoked after the stream has fulfilled or rejected natively, it automatically plays back the conclusive packet.
156
+ *
157
+ * @param handler - A callback function to receive packets.
158
+ */
159
+ pipe(handler) {
160
+ if (this.status === IRPC_STATUS.SUCCESS || this.status === IRPC_STATUS.ERROR) {
161
+ handler({
162
+ id: this.id,
163
+ name: this.name,
164
+ type: IRPC_PACKET_TYPE.ANSWER,
165
+ data: this.value,
166
+ error: this.error,
167
+ status: this.status,
168
+ createdAt: Date.now()
169
+ });
170
+ return;
171
+ }
172
+ this.pipeHandlers.add(handler);
173
+ this.start().catch(() => {});
174
+ }
175
+ /**
176
+ * Binds a handler to trap any internal runtime failures independently.
177
+ *
178
+ * @param handler - A callback function to receive stream errors.
179
+ */
180
+ catch(handler) {
181
+ if (this.status === IRPC_STATUS.ERROR) {
182
+ handler(this.error);
183
+ return;
184
+ }
185
+ this.errorHandlers.add(handler);
186
+ this.start().catch(() => {});
187
+ }
188
+ /**
189
+ * Binds a handler triggered upon terminal completion of the stream process (success or error).
190
+ *
191
+ * @param handler - A callback function invoked at stream completion.
192
+ */
193
+ close(handler) {
194
+ if (this.status === IRPC_STATUS.SUCCESS || this.status === IRPC_STATUS.ERROR) {
195
+ handler();
196
+ return;
197
+ }
198
+ this.closeHandlers.add(handler);
199
+ this.start().catch(() => {});
200
+ }
201
+ };
202
+
203
+ //#endregion
204
+ export { IRPCStream };
@@ -1,4 +1,5 @@
1
- import { IRPCData, IRPCInputs, IRPCOutput, IRPCSpec, TransportConfig } from "./types.js";
1
+ import { IRPCCallConfig, IRPCData, IRPCInputs, IRPCOutput, IRPCSpec, TransportConfig } from "./types.js";
2
+ import { IRPCReader } from "./reader.js";
2
3
  import { IRPCCall } from "./call.js";
3
4
 
4
5
  //#region src/transport.d.ts
@@ -22,16 +23,16 @@ declare class IRPCTransport {
22
23
  * Initiates an RPC call with the given specification and arguments.
23
24
  * @param spec - The RPC specification defining the method to call.
24
25
  * @param args - An array of arguments to pass to the RPC method.
25
- * @param timeout - Optional timeout value for the RPC call.
26
+ * @param config - Optional call configuration, including timeout, retry settings, and more.
26
27
  * @returns A promise that resolves with the RPC response data or rejects with an error.
27
28
  */
28
- call(spec: IRPCSpec<IRPCInputs, IRPCOutput>, args: IRPCData[], timeout?: number | undefined): Promise<IRPCData>;
29
+ call(spec: IRPCSpec<IRPCInputs, IRPCOutput>, args: IRPCData[], config?: IRPCCallConfig): IRPCReader<IRPCData>;
29
30
  /**
30
31
  * Schedules an RPC call for execution, implementing debouncing logic.
31
32
  * Queued calls will be dispatched after the configured debounce delay.
32
33
  * @param call - The RPC call to schedule.
33
34
  */
34
- protected schedule(call: IRPCCall): void;
35
+ schedule(call: IRPCCall): void;
35
36
  /**
36
37
  * Dispatches a batch of RPC calls. This base implementation rejects all calls
37
38
  * with a "not implemented" error. Subclasses should override this method to
package/dist/transport.js CHANGED
@@ -1,5 +1,6 @@
1
- import { IRPCCall } from "./call.js";
1
+ import { IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
2
2
  import { ERROR_CODE, ERROR_MESSAGE } from "./error.js";
3
+ import { IRPCCall } from "./call.js";
3
4
 
4
5
  //#region src/transport.ts
5
6
  /**
@@ -22,27 +23,26 @@ var IRPCTransport = class {
22
23
  * Initiates an RPC call with the given specification and arguments.
23
24
  * @param spec - The RPC specification defining the method to call.
24
25
  * @param args - An array of arguments to pass to the RPC method.
25
- * @param timeout - Optional timeout value for the RPC call.
26
+ * @param config - Optional call configuration, including timeout, retry settings, and more.
26
27
  * @returns A promise that resolves with the RPC response data or rejects with an error.
27
28
  */
28
- call(spec, args, timeout = this.config?.timeout) {
29
+ call(spec, args, config) {
29
30
  const payload = {
30
31
  name: spec.name,
31
32
  args
32
33
  };
33
- return new Promise((resolve, reject) => {
34
- const timer = timeout ? setTimeout(() => {
35
- call.reject(new Error(ERROR_MESSAGE[ERROR_CODE.TIMEOUT]));
36
- }, timeout) : void 0;
37
- const call = new IRPCCall(payload, (value) => {
38
- resolve(value);
39
- clearTimeout(timer);
40
- }, (reason) => {
41
- reject(reason);
42
- clearTimeout(timer);
43
- }, timeout);
44
- this.schedule(call);
34
+ const { timeout, maxRetries, retryMode, retryDelay } = {
35
+ ...this.config,
36
+ ...config
37
+ };
38
+ const call = new IRPCCall(this, payload, {
39
+ timeout,
40
+ maxRetries,
41
+ retryMode,
42
+ retryDelay
45
43
  });
44
+ this.schedule(call);
45
+ return call.reader;
46
46
  }
47
47
  /**
48
48
  * Schedules an RPC call for execution, implementing debouncing logic.
@@ -50,10 +50,18 @@ var IRPCTransport = class {
50
50
  * @param call - The RPC call to schedule.
51
51
  */
52
52
  schedule(call) {
53
- if (!this.queue.size) setTimeout(() => {
54
- this.dispatch(Array.from(this.queue));
53
+ const { debounce } = this.config ?? {};
54
+ if (debounce === false) {
55
+ this.dispatch([call]).finally(() => {}).catch(() => {});
56
+ return;
57
+ }
58
+ const timeout = typeof debounce === "number" && !Number.isNaN(debounce) ? debounce : 0;
59
+ const dispatch = () => {
60
+ this.dispatch(Array.from(this.queue)).finally(() => {}).catch(() => {});
55
61
  this.queue.clear();
56
- }, this.config?.debounce ?? 0);
62
+ };
63
+ if (!this.queue.size) if (timeout === 0) queueMicrotask(dispatch);
64
+ else setTimeout(dispatch, timeout);
57
65
  this.queue.add(call);
58
66
  }
59
67
  /**
@@ -65,7 +73,17 @@ var IRPCTransport = class {
65
73
  */
66
74
  async dispatch(calls) {
67
75
  calls.forEach((call) => {
68
- call.reject(new Error(ERROR_MESSAGE[ERROR_CODE.TRANSPORT_NOT_IMPLEMENTED]));
76
+ call.enqueue({
77
+ id: call.id,
78
+ name: call.payload.name,
79
+ type: IRPC_PACKET_TYPE.CLOSE,
80
+ status: IRPC_STATUS.ERROR,
81
+ error: {
82
+ code: ERROR_CODE.TRANSPORT_NOT_IMPLEMENTED,
83
+ message: ERROR_MESSAGE[ERROR_CODE.TRANSPORT_NOT_IMPLEMENTED]
84
+ },
85
+ createdAt: Date.now()
86
+ });
69
87
  });
70
88
  }
71
89
  };