@push-rpc/next 2.0.0-beta.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.
Files changed (97) hide show
  1. package/.prettierrc.json +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +26 -0
  4. package/dist/client/HttpClient.d.ts +10 -0
  5. package/dist/client/HttpClient.js +67 -0
  6. package/dist/client/HttpClient.js.map +1 -0
  7. package/dist/client/RemoteSubscriptions.d.ts +14 -0
  8. package/dist/client/RemoteSubscriptions.js +84 -0
  9. package/dist/client/RemoteSubscriptions.js.map +1 -0
  10. package/dist/client/RpcClientImpl.d.ts +22 -0
  11. package/dist/client/RpcClientImpl.js +96 -0
  12. package/dist/client/RpcClientImpl.js.map +1 -0
  13. package/dist/client/WebSocketConnection.d.ts +33 -0
  14. package/dist/client/WebSocketConnection.js +152 -0
  15. package/dist/client/WebSocketConnection.js.map +1 -0
  16. package/dist/client/index.d.ts +22 -0
  17. package/dist/client/index.js +28 -0
  18. package/dist/client/index.js.map +1 -0
  19. package/dist/client/remote.d.ts +14 -0
  20. package/dist/client/remote.js +66 -0
  21. package/dist/client/remote.js.map +1 -0
  22. package/dist/index.d.ts +12 -0
  23. package/dist/index.js +20 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/logger.d.ts +8 -0
  26. package/dist/logger.js +9 -0
  27. package/dist/logger.js.map +1 -0
  28. package/dist/rpc.d.ts +36 -0
  29. package/dist/rpc.js +32 -0
  30. package/dist/rpc.js.map +1 -0
  31. package/dist/server/ConnectionsServer.d.ts +13 -0
  32. package/dist/server/ConnectionsServer.js +60 -0
  33. package/dist/server/ConnectionsServer.js.map +1 -0
  34. package/dist/server/LocalSubscriptions.d.ts +12 -0
  35. package/dist/server/LocalSubscriptions.js +113 -0
  36. package/dist/server/LocalSubscriptions.js.map +1 -0
  37. package/dist/server/RpcServerImpl.d.ts +23 -0
  38. package/dist/server/RpcServerImpl.js +164 -0
  39. package/dist/server/RpcServerImpl.js.map +1 -0
  40. package/dist/server/http.d.ts +9 -0
  41. package/dist/server/http.js +83 -0
  42. package/dist/server/http.js.map +1 -0
  43. package/dist/server/index.d.ts +29 -0
  44. package/dist/server/index.js +31 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/dist/server/local.d.ts +15 -0
  47. package/dist/server/local.js +46 -0
  48. package/dist/server/local.js.map +1 -0
  49. package/dist/utils/json.d.ts +2 -0
  50. package/dist/utils/json.js +34 -0
  51. package/dist/utils/json.js.map +1 -0
  52. package/dist/utils/middleware.d.ts +2 -0
  53. package/dist/utils/middleware.js +31 -0
  54. package/dist/utils/middleware.js.map +1 -0
  55. package/dist/utils/promises.d.ts +5 -0
  56. package/dist/utils/promises.js +29 -0
  57. package/dist/utils/promises.js.map +1 -0
  58. package/dist/utils/throttle.d.ts +4 -0
  59. package/dist/utils/throttle.js +40 -0
  60. package/dist/utils/throttle.js.map +1 -0
  61. package/dist/utils/types.d.ts +1 -0
  62. package/dist/utils/types.js +3 -0
  63. package/dist/utils/types.js.map +1 -0
  64. package/example/api.ts +15 -0
  65. package/example/client.ts +16 -0
  66. package/example/server.ts +37 -0
  67. package/package.json +34 -0
  68. package/src/client/HttpClient.ts +80 -0
  69. package/src/client/RemoteSubscriptions.ts +121 -0
  70. package/src/client/RpcClientImpl.ts +177 -0
  71. package/src/client/WebSocketConnection.ts +183 -0
  72. package/src/client/index.ts +56 -0
  73. package/src/client/remote.ts +118 -0
  74. package/src/index.ts +18 -0
  75. package/src/logger.ts +12 -0
  76. package/src/rpc.ts +51 -0
  77. package/src/server/ConnectionsServer.ts +78 -0
  78. package/src/server/LocalSubscriptions.ts +155 -0
  79. package/src/server/RpcServerImpl.ts +252 -0
  80. package/src/server/http.ts +109 -0
  81. package/src/server/index.ts +65 -0
  82. package/src/server/local.ts +80 -0
  83. package/src/utils/json.ts +32 -0
  84. package/src/utils/middleware.ts +38 -0
  85. package/src/utils/promises.ts +25 -0
  86. package/src/utils/throttle.ts +48 -0
  87. package/src/utils/types.ts +1 -0
  88. package/tests/calls.ts +215 -0
  89. package/tests/connection.ts +107 -0
  90. package/tests/context.ts +176 -0
  91. package/tests/middleware.ts +112 -0
  92. package/tests/misc.ts +187 -0
  93. package/tests/subscriptions.ts +442 -0
  94. package/tests/testUtils.ts +52 -0
  95. package/tests/triggers.ts +138 -0
  96. package/tsconfig.cjs.json +20 -0
  97. package/tsconfig.json +26 -0
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serveHttpRequest = void 0;
4
+ const json_js_1 = require("../utils/json.js");
5
+ const logger_js_1 = require("../logger.js");
6
+ async function serveHttpRequest(req, res, path, hooks, createConnectionContext) {
7
+ // if port is in options - response 404 on other URLs
8
+ // oherwise just handle request
9
+ if (req.url?.startsWith(path)) {
10
+ try {
11
+ const ctx = await createConnectionContext(req);
12
+ const itemName = req.url.slice(path.length + 1);
13
+ const isJson = req.headersDistinct["content-type"]?.includes("application/json") ?? false;
14
+ const body = isJson ? (0, json_js_1.safeParseJson)(await readBody(req)) : [];
15
+ let result;
16
+ switch (req.method) {
17
+ case "POST":
18
+ result = await hooks.call(ctx, itemName, body);
19
+ break;
20
+ case "PUT":
21
+ result = await hooks.subscribe(ctx, itemName, body);
22
+ break;
23
+ case "PATCH":
24
+ result = await hooks.unsubscribe(ctx, itemName, body);
25
+ break;
26
+ default:
27
+ res.statusCode = 404;
28
+ res.end();
29
+ return;
30
+ }
31
+ if (typeof result == "undefined") {
32
+ res.statusCode = 204;
33
+ res.end();
34
+ return;
35
+ }
36
+ if (typeof result == "string") {
37
+ res.setHeader("Content-Type", "text/plain");
38
+ res.write(result);
39
+ res.end();
40
+ return;
41
+ }
42
+ res.setHeader("Content-Type", "application/json");
43
+ res.write((0, json_js_1.safeStringify)(result));
44
+ res.end();
45
+ }
46
+ catch (e) {
47
+ if (e.code) {
48
+ res.statusCode = e.code;
49
+ res.statusMessage = e.message;
50
+ const { code, message, stack, ...rest } = e;
51
+ if (Object.keys(rest).length > 0) {
52
+ res.setHeader("Content-Type", "application/json");
53
+ res.write((0, json_js_1.safeStringify)(rest));
54
+ }
55
+ res.end();
56
+ return;
57
+ }
58
+ else {
59
+ logger_js_1.log.warn(`Error in ${req.url}.`, e);
60
+ res.statusCode = 500;
61
+ res.statusMessage = e["message"] || "Internal Server Error";
62
+ res.end();
63
+ return;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ exports.serveHttpRequest = serveHttpRequest;
69
+ function readBody(req) {
70
+ return new Promise((resolve, reject) => {
71
+ let body = "";
72
+ req.on("data", (chunk) => {
73
+ body += chunk.toString();
74
+ });
75
+ req.on("end", () => {
76
+ resolve(body);
77
+ });
78
+ req.on("error", (error) => {
79
+ reject(error);
80
+ });
81
+ });
82
+ }
83
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/server/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /// <reference types="node" />
2
+ import { RpcConnectionContext, RpcContext, Services } from "../rpc.js";
3
+ import { ServicesWithTriggers } from "./local.js";
4
+ import { Middleware } from "../utils/middleware.js";
5
+ import http, { IncomingMessage } from "http";
6
+ export declare function publishServices<S extends Services<S>, C extends RpcContext>(services: S, overrideOptions: Partial<PublishServicesOptions<C>> & ({
7
+ port: number;
8
+ } | {
9
+ server: http.Server;
10
+ })): Promise<{
11
+ server: RpcServer;
12
+ services: ServicesWithTriggers<S>;
13
+ httpServer: http.Server;
14
+ }>;
15
+ export type RpcServer = {
16
+ close(): Promise<void>;
17
+ _allSubscriptions(): Array<any[]>;
18
+ };
19
+ export type PublishServicesOptions<C extends RpcContext> = {
20
+ host: string;
21
+ path: string;
22
+ middleware: Middleware<C>[];
23
+ pingInterval: number;
24
+ createConnectionContext(req: IncomingMessage): Promise<RpcConnectionContext>;
25
+ } & ({
26
+ server: http.Server;
27
+ } | {
28
+ port: number;
29
+ } | {});
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.publishServices = void 0;
4
+ const rpc_js_1 = require("../rpc.js");
5
+ const RpcServerImpl_js_1 = require("./RpcServerImpl.js");
6
+ async function publishServices(services, overrideOptions) {
7
+ const options = {
8
+ ...defaultOptions,
9
+ ...overrideOptions,
10
+ };
11
+ const rpcServer = new RpcServerImpl_js_1.RpcServerImpl(services, options);
12
+ await rpcServer.start();
13
+ return {
14
+ services: rpcServer.createServicesWithTriggers(),
15
+ server: rpcServer,
16
+ httpServer: rpcServer.httpServer,
17
+ };
18
+ }
19
+ exports.publishServices = publishServices;
20
+ const defaultOptions = {
21
+ path: "",
22
+ host: "0.0.0.0",
23
+ middleware: [],
24
+ pingInterval: 30 * 1000, // should be in-sync with client
25
+ async createConnectionContext(req) {
26
+ return {
27
+ clientId: req.headersDistinct[rpc_js_1.CLIENT_ID_HEADER]?.[0] || "anon",
28
+ };
29
+ },
30
+ };
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;AAAA,sCAAsF;AAGtF,yDAAgD;AAGzC,KAAK,UAAU,eAAe,CACnC,QAAW,EACX,eAA8F;IAM9F,MAAM,OAAO,GAAG;QACd,GAAG,cAAc;QACjB,GAAG,eAAe;KACnB,CAAA;IAED,MAAM,SAAS,GAAG,IAAI,gCAAa,CAAO,QAAQ,EAAE,OAAO,CAAC,CAAA;IAE5D,MAAM,SAAS,CAAC,KAAK,EAAE,CAAA;IAEvB,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC,0BAA0B,EAAE;QAChD,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,SAAS,CAAC,UAAU;KACjC,CAAA;AACH,CAAC;AAtBD,0CAsBC;AAyBD,MAAM,cAAc,GAAqD;IACvE,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,EAAE;IACd,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,gCAAgC;IAEzD,KAAK,CAAC,uBAAuB,CAAC,GAAoB;QAChD,OAAO;YACL,QAAQ,EAAE,GAAG,CAAC,eAAe,CAAC,yBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM;SAC/D,CAAA;IACH,CAAC;CACF,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { RemoteFunction, Services } from "../rpc.js";
2
+ import { LocalSubscriptions } from "./LocalSubscriptions.js";
3
+ import { ExtractPromiseResult } from "../utils/types.js";
4
+ import { ThrottleArgsReducer } from "../utils/throttle.js";
5
+ export type ThrottleSettings<D> = {
6
+ timeout: number;
7
+ reducer?: ThrottleArgsReducer<D>;
8
+ };
9
+ export declare function withTriggers<T extends Services<T>>(localSubscriptions: LocalSubscriptions, services: T, name?: string): ServicesWithTriggers<T>;
10
+ export type ServicesWithTriggers<T extends Services<T>> = {
11
+ [K in keyof T]: T[K] extends RemoteFunction ? T[K] & {
12
+ trigger(filter?: Partial<Parameters<T[K]>[0]>, suppliedData?: ExtractPromiseResult<ReturnType<T[K]>>): void;
13
+ throttle(settings: ThrottleSettings<ExtractPromiseResult<ReturnType<T[K]>>>): void;
14
+ } : T[K] extends object ? ServicesWithTriggers<T[K]> : never;
15
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTriggers = void 0;
4
+ function withTriggers(localSubscriptions, services, name = "") {
5
+ const cachedItems = {};
6
+ const skippedProps = ["length", "name", "prototype", "arguments", "caller"];
7
+ return new Proxy(services, {
8
+ get(target, propName) {
9
+ // skip internal props
10
+ if (typeof propName != "string")
11
+ return target[propName];
12
+ // skip other system props
13
+ if (["then", "catch", "toJSON", ...skippedProps].includes(propName))
14
+ return target[propName];
15
+ const itemName = name ? name + "/" + propName : propName;
16
+ if (typeof target[propName] == "function") {
17
+ const delegate = (...params) => {
18
+ return target[propName](...params);
19
+ };
20
+ delegate.trigger = (filter = {}, suppliedData) => {
21
+ // triggers are delayed for consumers to receive updates after the current call ends.
22
+ setTimeout(() => {
23
+ localSubscriptions.trigger(itemName, filter, suppliedData);
24
+ }, 0);
25
+ };
26
+ delegate.throttle = (settings) => {
27
+ localSubscriptions.throttleItem(itemName, settings);
28
+ };
29
+ return delegate;
30
+ }
31
+ else if (!cachedItems[propName]) {
32
+ cachedItems[propName] = withTriggers(localSubscriptions, services[propName], itemName);
33
+ }
34
+ return cachedItems[propName];
35
+ },
36
+ set(target, propName, value) {
37
+ cachedItems[propName] = value;
38
+ return true;
39
+ },
40
+ ownKeys() {
41
+ return [...skippedProps, ...Object.keys(cachedItems)];
42
+ },
43
+ });
44
+ }
45
+ exports.withTriggers = withTriggers;
46
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/server/local.ts"],"names":[],"mappings":";;;AAUA,SAAgB,YAAY,CAC1B,kBAAsC,EACtC,QAAW,EACX,IAAI,GAAG,EAAE;IAET,MAAM,WAAW,GAAQ,EAAE,CAAA;IAC3B,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;IAE3E,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE;QACzB,GAAG,CAAC,MAAW,EAAE,QAAgB;YAC/B,sBAAsB;YACtB,IAAI,OAAO,QAAQ,IAAI,QAAQ;gBAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAA;YAExD,0BAA0B;YAC1B,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAA;YAE5F,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;YAExD,IAAI,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC1C,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAiB,EAAE,EAAE;oBACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;gBACpC,CAAC,CAAA;gBAED,QAAQ,CAAC,OAAO,GAAG,CAAC,SAAkC,EAAE,EAAE,YAAsB,EAAE,EAAE;oBAClF,qFAAqF;oBACrF,UAAU,CAAC,GAAG,EAAE;wBACd,kBAAkB,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAA;oBAC5D,CAAC,EAAE,CAAC,CAAC,CAAA;gBACP,CAAC,CAAA;gBAED,QAAQ,CAAC,QAAQ,GAAG,CAAC,QAAmC,EAAE,EAAE;oBAC1D,kBAAkB,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;gBACrD,CAAC,CAAA;gBAED,OAAO,QAAQ,CAAA;YACjB,CAAC;iBAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,WAAW,CAAC,QAAQ,CAAC,GAAG,YAAY,CAClC,kBAAkB,EAClB,QAAQ,CAAC,QAAmB,CAAQ,EACpC,QAAQ,CACT,CAAA;YACH,CAAC;YAED,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAA;QAC9B,CAAC;QAED,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK;YACzB,WAAW,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAA;YAC7B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO;YACL,OAAO,CAAC,GAAG,YAAY,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAA;QACvD,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAvDD,oCAuDC"}
@@ -0,0 +1,2 @@
1
+ export declare function safeStringify(value: any): string;
2
+ export declare function safeParseJson(json: string): any;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.safeParseJson = exports.safeStringify = void 0;
7
+ const fast_stringify_1 = __importDefault(require("fast-stringify"));
8
+ function safeStringify(value) {
9
+ // @ts-ignore
10
+ return (0, fast_stringify_1.default)(value);
11
+ }
12
+ exports.safeStringify = safeStringify;
13
+ function safeParseJson(json) {
14
+ return JSON.parse(json, dateReviver);
15
+ }
16
+ exports.safeParseJson = safeParseJson;
17
+ function dateReviver(key, val) {
18
+ if (typeof val == "string") {
19
+ if (ISO8601_secs.test(val)) {
20
+ return new Date(val);
21
+ }
22
+ if (ISO8601.test(val)) {
23
+ return new Date(val);
24
+ }
25
+ if (ISO8601_date.test(val)) {
26
+ return new Date(val);
27
+ }
28
+ }
29
+ return val;
30
+ }
31
+ const ISO8601 = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/;
32
+ const ISO8601_secs = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/;
33
+ const ISO8601_date = /^\d\d\d\d-\d\d-\d\d$/;
34
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/utils/json.ts"],"names":[],"mappings":";;;;;;AAAA,oEAAsC;AAEtC,SAAgB,aAAa,CAAC,KAAU;IACtC,aAAa;IACb,OAAO,IAAA,wBAAS,EAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAHD,sCAGC;AAED,SAAgB,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;AACtC,CAAC;AAFD,sCAEC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,GAAQ;IACxC,IAAI,OAAO,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;QACtB,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;QACtB,CAAC;QAED,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,OAAO,GAAG,6CAA6C,CAAA;AAC7D,MAAM,YAAY,GAAG,sCAAsC,CAAA;AAC3D,MAAM,YAAY,GAAG,sBAAsB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export type Middleware<Context> = (ctx: Context, next: (...params: unknown[]) => Promise<unknown>, ...params: unknown[]) => Promise<unknown>;
2
+ export declare function withMiddlewares<Context>(ctx: Context, middlewares: Middleware<Context>[], final: (...params: unknown[]) => Promise<unknown>, ...params: any): Promise<unknown>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withMiddlewares = void 0;
4
+ function withMiddlewares(ctx, middlewares, final, ...params) {
5
+ return (function (next, ...params) {
6
+ let index = -1;
7
+ return dispatch(0, ...params);
8
+ function dispatch(i, ...p) {
9
+ if (i <= index)
10
+ return Promise.reject(new Error("next() called multiple times"));
11
+ // use previous invocation params
12
+ if (!p.length) {
13
+ p = params;
14
+ }
15
+ index = i;
16
+ try {
17
+ if (i === middlewares.length) {
18
+ return Promise.resolve(next(...p));
19
+ }
20
+ else {
21
+ return Promise.resolve(middlewares[i](ctx, dispatch.bind(null, i + 1), ...p));
22
+ }
23
+ }
24
+ catch (err) {
25
+ return Promise.reject(err);
26
+ }
27
+ }
28
+ })(final, ...params);
29
+ }
30
+ exports.withMiddlewares = withMiddlewares;
31
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/utils/middleware.ts"],"names":[],"mappings":";;;AAMA,SAAgB,eAAe,CAC7B,GAAY,EACZ,WAAkC,EAClC,KAAiD,EACjD,GAAG,MAAW;IAEd,OAAO,CAAC,UAAU,IAAI,EAAE,GAAG,MAAM;QAC/B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAA;QACd,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,CAAA;QAE7B,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAG,CAAY;YAC1C,IAAI,CAAC,IAAI,KAAK;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAA;YAEhF,iCAAiC;YACjC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;gBACd,CAAC,GAAG,MAAM,CAAA;YACZ,CAAC;YAED,KAAK,GAAG,CAAC,CAAA;YAET,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;oBAC7B,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;gBACpC,CAAC;qBAAM,CAAC;oBACN,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;gBAC/E,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,CAAA;AACtB,CAAC;AA/BD,0CA+BC"}
@@ -0,0 +1,5 @@
1
+ export declare class PromiseCache {
2
+ invoke<T>(cacheKey: unknown, supplier: () => Promise<T>): Promise<T>;
3
+ private cache;
4
+ }
5
+ export declare function adelay(ms: number): Promise<unknown>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adelay = exports.PromiseCache = void 0;
4
+ class PromiseCache {
5
+ constructor() {
6
+ this.cache = {};
7
+ }
8
+ invoke(cacheKey, supplier) {
9
+ const key = JSON.stringify(cacheKey);
10
+ if (!this.cache[key]) {
11
+ this.cache[key] = supplier()
12
+ .then((r) => {
13
+ delete this.cache[key];
14
+ return r;
15
+ })
16
+ .catch((e) => {
17
+ delete this.cache[key];
18
+ throw e;
19
+ });
20
+ }
21
+ return this.cache[key];
22
+ }
23
+ }
24
+ exports.PromiseCache = PromiseCache;
25
+ async function adelay(ms) {
26
+ return new Promise((r) => setTimeout(r, ms));
27
+ }
28
+ exports.adelay = adelay;
29
+ //# sourceMappingURL=promises.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promises.js","sourceRoot":"","sources":["../../src/utils/promises.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAY;IAAzB;QAmBU,UAAK,GAAkC,EAAE,CAAA;IACnD,CAAC;IAnBC,MAAM,CAAI,QAAiB,EAAE,QAA0B;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAEpC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE;iBACzB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;gBACV,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBACtB,OAAO,CAAC,CAAA;YACV,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACX,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBACtB,MAAM,CAAC,CAAA;YACT,CAAC,CAAC,CAAA;QACN,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;CAGF;AApBD,oCAoBC;AAEM,KAAK,UAAU,MAAM,CAAC,EAAU;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAC9C,CAAC;AAFD,wBAEC"}
@@ -0,0 +1,4 @@
1
+ export type ThrottleArgsReducer<D> = (prevValue: D, newValue: D) => D;
2
+ export declare function lastValueReducer<D>(prevValue: D, newValue: D): D;
3
+ export declare function groupReducer<D>(prevValue: D[], newValue: D[]): D[];
4
+ export declare function throttle<D>(callback: (d: D) => void, delay: number, reducer: ThrottleArgsReducer<D>): (d: D) => void;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.throttle = exports.groupReducer = exports.lastValueReducer = void 0;
4
+ function lastValueReducer(prevValue, newValue) {
5
+ return newValue;
6
+ }
7
+ exports.lastValueReducer = lastValueReducer;
8
+ function groupReducer(prevValue, newValue) {
9
+ if (!Array.isArray(newValue))
10
+ throw new Error("groupReducer should only be used with topics that return arrays");
11
+ return prevValue ? [...prevValue, ...newValue] : newValue;
12
+ }
13
+ exports.groupReducer = groupReducer;
14
+ function throttle(callback, delay, reducer) {
15
+ let timer;
16
+ let lastExec = 0;
17
+ let reducedArg;
18
+ function wrapper(d) {
19
+ let self = this;
20
+ let elapsed = Date.now() - lastExec;
21
+ function exec() {
22
+ lastExec = Date.now();
23
+ callback.call(self, reducedArg);
24
+ reducedArg = undefined;
25
+ }
26
+ if (timer) {
27
+ clearTimeout(timer);
28
+ }
29
+ reducedArg = reducer(reducedArg, d);
30
+ if (elapsed > delay) {
31
+ exec();
32
+ }
33
+ else {
34
+ timer = setTimeout(exec, delay - elapsed);
35
+ }
36
+ }
37
+ return wrapper;
38
+ }
39
+ exports.throttle = throttle;
40
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/utils/throttle.ts"],"names":[],"mappings":";;;AAEA,SAAgB,gBAAgB,CAAI,SAAY,EAAE,QAAW;IAC3D,OAAO,QAAQ,CAAA;AACjB,CAAC;AAFD,4CAEC;AAED,SAAgB,YAAY,CAAI,SAAc,EAAE,QAAa;IAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;IAEpF,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC3D,CAAC;AALD,oCAKC;AAED,SAAgB,QAAQ,CACtB,QAAwB,EACxB,KAAa,EACb,OAA+B;IAE/B,IAAI,KAAqB,CAAA;IACzB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAEhB,IAAI,UAAe,CAAA;IAEnB,SAAS,OAAO,CAAY,CAAI;QAC9B,IAAI,IAAI,GAAG,IAAI,CAAA;QACf,IAAI,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAA;QAEnC,SAAS,IAAI;YACX,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACrB,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;YAC/B,UAAU,GAAG,SAAS,CAAA;QACxB,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC;QAED,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;QAEnC,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC;YACpB,IAAI,EAAE,CAAA;QACR,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAlCD,4BAkCC"}
@@ -0,0 +1 @@
1
+ export type ExtractPromiseResult<Type> = Type extends Promise<infer X> ? X : never;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":""}
package/example/api.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type Services = {
2
+ todo: TodoService
3
+ }
4
+
5
+ export type TodoService = {
6
+ addTodo(req: {text: string}, ctx?: any): Promise<void>
7
+ getTodos(ctx?: any): Promise<Todo[]>
8
+ }
9
+
10
+ export type Todo = {
11
+ id: string
12
+ text: string
13
+ status: "open" | "closed"
14
+ }
15
+
@@ -0,0 +1,16 @@
1
+ import {Services} from "./api"
2
+ import {consumeServices} from "../src/client/index"
3
+
4
+ async function startClient() {
5
+ const {remote} = await consumeServices<Services>("http://127.0.0.1:8080/rpc")
6
+
7
+ console.log("Client created")
8
+
9
+ await remote.todo.getTodos.subscribe((todos) => {
10
+ console.log("Got todo items", todos)
11
+ }, null)
12
+
13
+ await remote.todo.addTodo({text: "Buy groceries"})
14
+ }
15
+
16
+ startClient()
@@ -0,0 +1,37 @@
1
+ import {Todo, TodoService} from "./api"
2
+ import {publishServices} from "../src/server/index"
3
+
4
+ const storage: Todo[] = []
5
+
6
+ async function startServer() {
7
+ class TodoServiceImpl implements TodoService {
8
+ async addTodo({text}: {text: string}) {
9
+ storage.push({
10
+ id: "" + Math.random(),
11
+ text,
12
+ status: "open",
13
+ })
14
+
15
+ console.log("New todo item added")
16
+ services.todo.getTodos.trigger()
17
+ }
18
+
19
+ async getTodos() {
20
+ return storage
21
+ }
22
+ }
23
+
24
+ const {services} = await publishServices(
25
+ {
26
+ todo: new TodoServiceImpl(),
27
+ },
28
+ {
29
+ port: 8080,
30
+ path: "/rpc",
31
+ }
32
+ )
33
+
34
+ console.log("RPC Server started at http://localhost:8080/rpc")
35
+ }
36
+
37
+ startServer()
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@push-rpc/next",
3
+ "version": "2.0.0-beta.1",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "prepublishOnly": "npm run build",
8
+ "build": "rm -fr dist && tsc -p tsconfig.cjs.json",
9
+ "test": "NODE_OPTIONS='--loader ts-node/esm' mocha tests/**/*.ts"
10
+ },
11
+ "repository": "https://github.com/vasyas/push-rpc.git",
12
+ "author": "vasyas <vasyl@stashuk.com>",
13
+ "license": "MIT",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "fast-stringify": "^2.0.0",
19
+ "nanoid": "^3.3.7"
20
+ },
21
+ "optionalDependencies": {
22
+ "ws": "^8.16.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/chai": "^4.3.11",
26
+ "@types/mocha": "^10.0.6",
27
+ "@types/ws": "^8.5.10",
28
+ "chai": "^4.4.1",
29
+ "mocha": "^10.2.0",
30
+ "prettier": "^3.2.4",
31
+ "ts-node": "^10.9.2",
32
+ "typescript": "^5.3.3"
33
+ }
34
+ }
@@ -0,0 +1,80 @@
1
+ import {CLIENT_ID_HEADER, RpcErrors} from "../rpc.js"
2
+ import {safeParseJson, safeStringify} from "../utils/json.js"
3
+
4
+ export class HttpClient {
5
+ constructor(
6
+ private url: string,
7
+ private clientId: string
8
+ ) {}
9
+
10
+ call(itemName: string, params: unknown[], callTimeout: number): Promise<unknown> {
11
+ return this.httpRequest("POST", itemName, params, callTimeout)
12
+ }
13
+
14
+ async subscribe(itemName: string, params: unknown[], callTimeout: number) {
15
+ return this.httpRequest("PUT", itemName, params, callTimeout)
16
+ }
17
+
18
+ async unsubscribe(itemName: string, params: unknown[], callTimeout: number) {
19
+ await this.httpRequest("PATCH", itemName, params, callTimeout)
20
+ }
21
+
22
+ private getItemUrl(itemName: string): string {
23
+ return `${this.url}/${itemName}`
24
+ }
25
+
26
+ private async httpRequest(
27
+ method: "POST" | "PUT" | "PATCH",
28
+ itemName: string,
29
+ params: unknown[],
30
+ callTimeout: number
31
+ ): Promise<unknown> {
32
+ try {
33
+ const response = await fetch(this.getItemUrl(itemName), {
34
+ method,
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ [CLIENT_ID_HEADER]: this.clientId,
38
+ },
39
+ body: safeStringify(params),
40
+ signal: AbortSignal.timeout(callTimeout),
41
+ })
42
+
43
+ if (response.status == 204) {
44
+ return
45
+ }
46
+
47
+ const contentType = response.headers.get("content-type")
48
+
49
+ const text = await response.text()
50
+
51
+ const res =
52
+ contentType && contentType.includes("application/json") ? safeParseJson(text) : text
53
+
54
+ if (response.status < 200 || response.status >= 300) {
55
+ const error = new Error(response.statusText)
56
+
57
+ Object.assign(error, {code: response.status})
58
+
59
+ if (typeof res == "object") {
60
+ Object.assign(error, res)
61
+ }
62
+
63
+ throw error
64
+ } else {
65
+ return res
66
+ }
67
+ } catch (e: any) {
68
+ if (e.message == "fetch failed" && e.cause) {
69
+ e = e.cause
70
+ }
71
+ if (e.message?.toLowerCase()?.includes("timeout")) {
72
+ // NodeJS undici http client timeout
73
+ const error = new Error("Timeout")
74
+ Object.assign(error, {code: RpcErrors.Timeout})
75
+ throw error
76
+ }
77
+ throw e
78
+ }
79
+ }
80
+ }