@cybermp/rpc-core 0.1.0-rc.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CyberMP-RPC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,15 @@
1
+ export { M as MpEnv } from '../shared/rpc-core.cYKWyseL.mjs';
2
+ export { C as CommonRpcErrorCode, R as RpcApplyType, a as RpcEnv, b as RpcError, d as RpcPacket, c as RpcPacketType, g as getRpcError } from '../shared/rpc-core.IrPp0Y0i.mjs';
3
+
4
+ class RpcContext {
5
+ data;
6
+ meta;
7
+ packet;
8
+ constructor({ data, meta, packet }) {
9
+ this.data = data;
10
+ this.meta = meta;
11
+ this.packet = packet;
12
+ }
13
+ }
14
+
15
+ export { RpcContext };
package/dist/index.mjs ADDED
@@ -0,0 +1,384 @@
1
+ import { C as CURRENT_ENVIRONMENT } from './shared/rpc-core.cYKWyseL.mjs';
2
+ import { b as RpcError, d as RpcPacket, c as RpcPacketType, R as RpcApplyType, g as getRpcError } from './shared/rpc-core.IrPp0Y0i.mjs';
3
+
4
+ class RpcInterceptorManager {
5
+ handlers = [];
6
+ use(handler) {
7
+ this.handlers.push(handler);
8
+ return this.handlers.length - 1;
9
+ }
10
+ eject(id) {
11
+ if (this.handlers[id]) {
12
+ this.handlers[id] = null;
13
+ }
14
+ }
15
+ getHandlers() {
16
+ return this.handlers.filter((h) => h !== null);
17
+ }
18
+ }
19
+
20
+ class RpcOptions {
21
+ throwUnknownMethods = true;
22
+ maxPending = 1e3;
23
+ pendingTimeout = 1e4;
24
+ prefix = "";
25
+ serializer;
26
+ }
27
+
28
+ const anyAbortSignal = (iterable) => {
29
+ const controller = new AbortController();
30
+ function abort() {
31
+ controller.abort(this.reason);
32
+ clean();
33
+ }
34
+ function clean() {
35
+ for (const signal of iterable) {
36
+ signal.removeEventListener("abort", abort);
37
+ }
38
+ }
39
+ for (const signal of iterable) {
40
+ if (signal.aborted) {
41
+ controller.abort(signal.reason);
42
+ break;
43
+ } else {
44
+ signal.addEventListener("abort", abort);
45
+ }
46
+ }
47
+ return controller.signal;
48
+ };
49
+ const timeoutAbortSignal = (ms) => {
50
+ const controller = new AbortController();
51
+ setTimeout(() => {
52
+ controller.abort("TimeoutError");
53
+ }, ms);
54
+ return controller.signal;
55
+ };
56
+
57
+ class RpcPendingStore {
58
+ constructor(options) {
59
+ this.options = options;
60
+ }
61
+ pendingRequests = /* @__PURE__ */ new Map();
62
+ processPending({ resolve, reject, packet }) {
63
+ if (!packet) {
64
+ return;
65
+ }
66
+ if (packet.error) {
67
+ reject(packet.error);
68
+ } else {
69
+ resolve(packet.data);
70
+ }
71
+ }
72
+ addPending({
73
+ packet,
74
+ reject,
75
+ resolve,
76
+ // TODO: sync abort signals both ways, so if request was aborted from client, it execution would also abort in registry
77
+ signal: externalSignal,
78
+ timeout
79
+ }) {
80
+ const { id, method } = packet;
81
+ if (!id) {
82
+ return;
83
+ }
84
+ const timeoutValue = timeout || this.options.pendingTimeout;
85
+ const combinedSignal = anyAbortSignal([
86
+ ...externalSignal ? [externalSignal] : [],
87
+ ...timeoutValue ? [timeoutAbortSignal(timeoutValue)] : []
88
+ ]);
89
+ const onAbort = () => {
90
+ this.pendingRequests.delete(id);
91
+ const isTimeout = combinedSignal.reason?.name === "TimeoutError" || combinedSignal.reason === "TimeoutError";
92
+ const error = isTimeout ? RpcError.timeout({
93
+ message: `RPC call ${method}:${id} timed out after ${timeoutValue}ms`,
94
+ method,
95
+ data: combinedSignal.reason
96
+ }) : new RpcError({
97
+ method,
98
+ data: combinedSignal.reason,
99
+ message: combinedSignal.reason
100
+ });
101
+ this.processPending({
102
+ reject,
103
+ resolve,
104
+ packet: new RpcPacket(
105
+ { id, method, error, type: RpcPacketType.RESPONSE },
106
+ { target: CURRENT_ENVIRONMENT }
107
+ )
108
+ });
109
+ };
110
+ if (combinedSignal.aborted) {
111
+ onAbort();
112
+ return;
113
+ }
114
+ if (this.options.maxPending && this.pendingRequests.size >= this.options.maxPending) {
115
+ const error = RpcError.tooManyPendingRequests();
116
+ this.processPending({
117
+ reject,
118
+ resolve,
119
+ packet: new RpcPacket(
120
+ { id, method, error, type: RpcPacketType.RESPONSE },
121
+ { target: CURRENT_ENVIRONMENT }
122
+ )
123
+ });
124
+ return;
125
+ }
126
+ combinedSignal.addEventListener("abort", onAbort, { once: true });
127
+ this.pendingRequests.set(id, async (packet2) => {
128
+ combinedSignal.removeEventListener("abort", onAbort);
129
+ this.pendingRequests.delete(id);
130
+ this.processPending({ reject, resolve, packet: packet2 });
131
+ });
132
+ }
133
+ resolvePending(packet) {
134
+ if (!packet.id || packet.type !== RpcPacketType.RESPONSE) {
135
+ return;
136
+ }
137
+ const handler = this.pendingRequests.get(packet.id);
138
+ if (!handler) {
139
+ return;
140
+ }
141
+ handler(packet);
142
+ }
143
+ }
144
+
145
+ class RpcRegistry {
146
+ constructor(options) {
147
+ this.options = options;
148
+ }
149
+ globalMiddlewares = [];
150
+ callHandlers = /* @__PURE__ */ new Map();
151
+ triggerHandlers = /* @__PURE__ */ new Map();
152
+ use(...middlewares) {
153
+ this.globalMiddlewares.push(...middlewares);
154
+ }
155
+ isApplied(method, type) {
156
+ if (type === RpcApplyType.ON) {
157
+ return this.triggerHandlers.has(method);
158
+ } else if (type === RpcApplyType.REGISTER) {
159
+ return this.callHandlers.has(method);
160
+ }
161
+ }
162
+ register(method, ...stack) {
163
+ this.callHandlers.set(method, stack);
164
+ }
165
+ unregister(method) {
166
+ this.callHandlers.delete(method);
167
+ }
168
+ on(eventName, ...stack) {
169
+ if (!this.triggerHandlers.has(eventName)) {
170
+ this.triggerHandlers.set(eventName, []);
171
+ }
172
+ this.triggerHandlers.get(eventName).push(stack);
173
+ }
174
+ off(eventName, handler) {
175
+ const containers = this.triggerHandlers.get(eventName);
176
+ if (!containers) {
177
+ return;
178
+ }
179
+ const index = containers.findIndex(
180
+ (c) => c[c.length - 1] === handler
181
+ );
182
+ if (index !== -1) {
183
+ containers.splice(index, 1);
184
+ }
185
+ if (containers.length === 0) {
186
+ this.triggerHandlers.delete(eventName);
187
+ }
188
+ }
189
+ offAll(eventName) {
190
+ this.triggerHandlers.delete(eventName);
191
+ }
192
+ async execute(ctx) {
193
+ const { type, method } = ctx.packet;
194
+ if (type === RpcPacketType.CALL) {
195
+ const stack = this.callHandlers.get(method);
196
+ if (!stack) {
197
+ if (this.options.throwUnknownMethods) {
198
+ throw RpcError.methodNotFound({ method });
199
+ }
200
+ return;
201
+ }
202
+ return this.runStack(ctx, stack);
203
+ }
204
+ if (type === RpcPacketType.TRIGGER) {
205
+ const listenerStacks = this.triggerHandlers.get(method);
206
+ if (!listenerStacks) {
207
+ return;
208
+ }
209
+ for (const stack of listenerStacks) {
210
+ this.runStack(ctx, stack).catch((err) => {
211
+ console.error("RPC UNHANDLDED REJECTION", err);
212
+ });
213
+ }
214
+ }
215
+ }
216
+ async runStack(ctx, handlers) {
217
+ const fullStack = [...this.globalMiddlewares, ...handlers];
218
+ let index = 0;
219
+ const next = async () => {
220
+ if (index >= fullStack.length) {
221
+ return;
222
+ }
223
+ const current = fullStack[index++];
224
+ try {
225
+ return await current?.(ctx, next);
226
+ } catch (err) {
227
+ throw getRpcError(err, ctx.packet.method);
228
+ }
229
+ };
230
+ return next();
231
+ }
232
+ }
233
+
234
+ class RpcBase {
235
+ options;
236
+ registry;
237
+ pendingStore;
238
+ interceptors = {
239
+ request: new RpcInterceptorManager(),
240
+ response: new RpcInterceptorManager()
241
+ };
242
+ constructor(options = {}) {
243
+ this.options = Object.assign(new RpcOptions(), options);
244
+ this.registry = new RpcRegistry(this.options);
245
+ this.pendingStore = new RpcPendingStore(this.options);
246
+ }
247
+ async call(packet, options, remoteInfo) {
248
+ const interceptedPacket = await this.runRequestInterceptors(packet);
249
+ return this.internalCall(interceptedPacket, options, remoteInfo);
250
+ }
251
+ async trigger(packet, remoteInfo) {
252
+ const interceptedPacket = await this.runRequestInterceptors(packet);
253
+ this.internalTrigger(interceptedPacket, remoteInfo);
254
+ }
255
+ init() {
256
+ this.databus.listen(
257
+ (packet, remoteInfo) => this.handleIncoming(packet, remoteInfo)
258
+ );
259
+ }
260
+ async runRequestInterceptors(request) {
261
+ let req = { ...request };
262
+ for (const handler of this.interceptors.request.getHandlers()) {
263
+ req = await handler(req);
264
+ }
265
+ return req;
266
+ }
267
+ async runResponseInterceptors(response) {
268
+ let res = { ...response };
269
+ if (res.error) {
270
+ let error = res.error;
271
+ for (const handler of this.interceptors.response.getHandlers()) {
272
+ if (handler.onRejected) {
273
+ error = await handler.onRejected(error);
274
+ }
275
+ }
276
+ res.error = error;
277
+ return res;
278
+ }
279
+ for (const handler of this.interceptors.response.getHandlers()) {
280
+ if (handler.onFulfilled) {
281
+ res = await handler.onFulfilled(res);
282
+ }
283
+ }
284
+ return res;
285
+ }
286
+ async handleIncoming(packet, remoteInfo) {
287
+ if (packet.env.target !== CURRENT_ENVIRONMENT) {
288
+ return this.forward(packet, remoteInfo);
289
+ }
290
+ if (packet.type === RpcPacketType.RESPONSE) {
291
+ this.pendingStore.resolvePending(
292
+ await this.runResponseInterceptors(packet)
293
+ );
294
+ return;
295
+ }
296
+ const ctx = this.createContext(packet, remoteInfo);
297
+ try {
298
+ const result = await this.registry.execute(ctx);
299
+ if (packet.type === RpcPacketType.CALL && packet.id) {
300
+ this.sendResponse(packet, result, remoteInfo);
301
+ }
302
+ } catch (err) {
303
+ if (packet.type === RpcPacketType.CALL && packet.id) {
304
+ this.sendResponse(packet, null, remoteInfo, err);
305
+ }
306
+ }
307
+ }
308
+ sendResponse(original, data, remoteInfo, error) {
309
+ const response = new RpcPacket(
310
+ {
311
+ id: original.id,
312
+ method: original.method,
313
+ type: RpcPacketType.RESPONSE,
314
+ data,
315
+ error: error ? getRpcError(error, original.method) : void 0
316
+ },
317
+ { target: original.env.source }
318
+ );
319
+ this.databus.send(response, remoteInfo);
320
+ }
321
+ parseCallArgs(args) {
322
+ const [first, second, third] = args;
323
+ const isConfig = (obj) => {
324
+ return obj && typeof obj === "object" && ("timeout" in obj || "signal" in obj || "data" in obj || "meta" in obj);
325
+ };
326
+ if (args.length === 1 && isConfig(first)) {
327
+ return {
328
+ data: first.data,
329
+ meta: first.meta,
330
+ options: first
331
+ };
332
+ }
333
+ return {
334
+ data: first,
335
+ meta: second,
336
+ options: third
337
+ };
338
+ }
339
+ on(eventName, ...stack) {
340
+ this.registry.on(eventName, ...stack);
341
+ }
342
+ register(method, ...stack) {
343
+ this.registry.register(method, ...stack);
344
+ }
345
+ unregister(method) {
346
+ this.registry.unregister(method);
347
+ }
348
+ isApplied(method, type) {
349
+ return this.registry.isApplied(method, type);
350
+ }
351
+ off(eventName, handler) {
352
+ this.registry.off(eventName, handler);
353
+ }
354
+ offAll(eventName) {
355
+ this.registry.offAll(eventName);
356
+ }
357
+ use(...middlewares) {
358
+ this.registry.use(...middlewares);
359
+ }
360
+ }
361
+
362
+ class RpcDatabus {
363
+ constructor(options) {
364
+ this.options = options;
365
+ }
366
+ get RPC_INVOKE_EVENT() {
367
+ return `${this.options.prefix}__rpc:invoke`;
368
+ }
369
+ get RPC_RESPONSE_EVENT() {
370
+ return `${this.options.prefix}__rpc:response`;
371
+ }
372
+ serialize(obj) {
373
+ return this.options.serializer?.serialize(obj) ?? obj;
374
+ }
375
+ deserialize(obj) {
376
+ return this.options.serializer?.deserialize(obj) ?? obj;
377
+ }
378
+ onPacketReceived;
379
+ listen(cb) {
380
+ this.onPacketReceived = cb;
381
+ }
382
+ }
383
+
384
+ export { RpcBase, RpcDatabus, RpcOptions, RpcPendingStore, RpcRegistry };
@@ -0,0 +1,146 @@
1
+ import { C as CURRENT_ENVIRONMENT, g as generateUUID } from './rpc-core.cYKWyseL.mjs';
2
+
3
+ var RpcApplyType = /* @__PURE__ */ ((RpcApplyType2) => {
4
+ RpcApplyType2["ON"] = "on";
5
+ RpcApplyType2["REGISTER"] = "register";
6
+ return RpcApplyType2;
7
+ })(RpcApplyType || {});
8
+
9
+ class RpcEnv {
10
+ source = CURRENT_ENVIRONMENT;
11
+ target;
12
+ constructor(opts) {
13
+ this.source = opts?.source ?? CURRENT_ENVIRONMENT;
14
+ this.target = opts.target;
15
+ }
16
+ }
17
+
18
+ var CommonRpcErrorCode = /* @__PURE__ */ ((CommonRpcErrorCode2) => {
19
+ CommonRpcErrorCode2["UNKNOWN"] = "UNKNOWN";
20
+ CommonRpcErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
21
+ CommonRpcErrorCode2["FORBIDDEN"] = "FORBIDDEN";
22
+ CommonRpcErrorCode2["NOT_FOUND"] = "NOT_FOUND";
23
+ CommonRpcErrorCode2["METHOD_NOT_FOUND"] = "METHOD_NOT_FOUND";
24
+ CommonRpcErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
25
+ CommonRpcErrorCode2["TIMEOUT"] = "TIMEOUT";
26
+ CommonRpcErrorCode2["INVALID_DATA"] = "INVALID_DATA";
27
+ CommonRpcErrorCode2["TOO_MANY_PENDING_REQUESTS"] = "TOO_MANY_PENDING_REQUESTS";
28
+ return CommonRpcErrorCode2;
29
+ })(CommonRpcErrorCode || {});
30
+ class RpcError extends Error {
31
+ code;
32
+ data;
33
+ method;
34
+ constructor(options = {}) {
35
+ super(options.message);
36
+ this.code = options.code ?? "UNKNOWN" /* UNKNOWN */;
37
+ this.data = options.data;
38
+ this.method = options.method;
39
+ }
40
+ static unknown(options = {}) {
41
+ return new RpcError({
42
+ message: "Unknown error happened",
43
+ ...options,
44
+ code: "UNKNOWN" /* UNKNOWN */
45
+ });
46
+ }
47
+ static tooManyPendingRequests(options = {}) {
48
+ return new RpcError({
49
+ message: "Too many pending requests",
50
+ ...options,
51
+ code: "TOO_MANY_PENDING_REQUESTS" /* TOO_MANY_PENDING_REQUESTS */
52
+ });
53
+ }
54
+ static unauthorized(options = {}) {
55
+ return new RpcError({
56
+ message: "Unauthorized",
57
+ ...options,
58
+ code: "UNAUTHORIZED" /* UNAUTHORIZED */
59
+ });
60
+ }
61
+ static forbidden(options = {}) {
62
+ return new RpcError({
63
+ message: "Forbidden",
64
+ ...options,
65
+ code: "FORBIDDEN" /* FORBIDDEN */
66
+ });
67
+ }
68
+ static notFound(options = {}) {
69
+ return new RpcError({
70
+ message: "Not found",
71
+ ...options,
72
+ code: "NOT_FOUND" /* NOT_FOUND */
73
+ });
74
+ }
75
+ static methodNotFound(options = {}) {
76
+ return new RpcError({
77
+ message: "Method not found",
78
+ ...options,
79
+ code: "METHOD_NOT_FOUND" /* METHOD_NOT_FOUND */
80
+ });
81
+ }
82
+ static internalError(options = {}) {
83
+ return new RpcError({
84
+ message: "An unexpected error occurred",
85
+ ...options,
86
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */
87
+ });
88
+ }
89
+ static timeout(options = {}) {
90
+ return new RpcError({
91
+ message: "Request timed out",
92
+ ...options,
93
+ code: "TIMEOUT" /* TIMEOUT */
94
+ });
95
+ }
96
+ static invalidData(options = {}) {
97
+ return new RpcError({
98
+ message: "Invalid data provided",
99
+ ...options,
100
+ code: "INVALID_DATA" /* INVALID_DATA */
101
+ });
102
+ }
103
+ }
104
+ const getRpcError = (err, method) => {
105
+ if (!(err instanceof RpcError)) {
106
+ return RpcError.internalError({ method });
107
+ }
108
+ if (!err.method) {
109
+ err.method = method;
110
+ }
111
+ return err;
112
+ };
113
+
114
+ var RpcPacketType = /* @__PURE__ */ ((RpcPacketType2) => {
115
+ RpcPacketType2["CALL"] = "call";
116
+ RpcPacketType2["TRIGGER"] = "trigger";
117
+ RpcPacketType2["RESPONSE"] = "response";
118
+ return RpcPacketType2;
119
+ })(RpcPacketType || {});
120
+ class RpcPacket {
121
+ id;
122
+ type;
123
+ method;
124
+ data;
125
+ meta;
126
+ error;
127
+ env;
128
+ constructor({
129
+ id,
130
+ type,
131
+ data = {},
132
+ method,
133
+ error,
134
+ meta = {}
135
+ }, env) {
136
+ this.id = id ?? (type === "call" /* CALL */ ? generateUUID() : null);
137
+ this.type = type;
138
+ this.method = method;
139
+ this.data = data;
140
+ this.meta = meta;
141
+ this.error = error;
142
+ this.env = new RpcEnv(env);
143
+ }
144
+ }
145
+
146
+ export { CommonRpcErrorCode as C, RpcApplyType as R, RpcEnv as a, RpcError as b, RpcPacketType as c, RpcPacket as d, getRpcError as g };
@@ -0,0 +1,41 @@
1
+ var MpEnv = /* @__PURE__ */ ((MpEnv2) => {
2
+ MpEnv2["BROWSER"] = "browser";
3
+ MpEnv2["CLIENT"] = "client";
4
+ MpEnv2["SERVER"] = "server";
5
+ return MpEnv2;
6
+ })(MpEnv || {});
7
+
8
+ const getEnvironment = () => {
9
+ const mp = globalThis.mp;
10
+ if (!mp) {
11
+ throw new Error('Unresolved environment: global "mp" object not found');
12
+ }
13
+ if (mp.isServer?.()) {
14
+ return MpEnv.SERVER;
15
+ }
16
+ if (mp.events?.onServer !== void 0) {
17
+ return MpEnv.CLIENT;
18
+ }
19
+ if (mp.events?.emit !== void 0) {
20
+ return MpEnv.BROWSER;
21
+ }
22
+ throw new Error("Unresolved environment: mp object properties unrecognized");
23
+ };
24
+ const CURRENT_ENVIRONMENT = getEnvironment();
25
+
26
+ const generateUUID = () => {
27
+ let d = Date.now(), d2 = typeof performance !== "undefined" && performance.now && performance.now() * 1e3 || 0;
28
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
29
+ let r = Math.random() * 16;
30
+ if (d > 0) {
31
+ r = (d + r) % 16 | 0;
32
+ d = Math.floor(d / 16);
33
+ } else {
34
+ r = (d2 + r) % 16 | 0;
35
+ d2 = Math.floor(d2 / 16);
36
+ }
37
+ return (c === "x" ? r : r & 3 | 8).toString(16);
38
+ });
39
+ };
40
+
41
+ export { CURRENT_ENVIRONMENT as C, MpEnv as M, getEnvironment as a, generateUUID as g };
@@ -0,0 +1 @@
1
+ export { C as CURRENT_ENVIRONMENT, g as generateUUID, a as getEnvironment } from '../shared/rpc-core.cYKWyseL.mjs';
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@cybermp/rpc-core",
3
+ "version": "0.1.0-rc.1",
4
+ "keywords": [],
5
+ "license": "MIT",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./utils": "./src/utils/index.ts",
9
+ "./definitions": "./src/definitions/index.ts"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "unbuild": {
15
+ "failOnWarn": false
16
+ },
17
+ "scripts": {
18
+ "dev": "pnpm run build --watch",
19
+ "build": "unbuild",
20
+ "type:check": "tsc -b"
21
+ }
22
+ }