@zudojs/rpc 0.1.0 → 1.1.0

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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -12
  3. package/dist/index.d.ts +8 -4
  4. package/dist/index.js +3 -4
  5. package/dist/rpc/client/index.d.ts +1 -1
  6. package/dist/rpc/client/rpcClient.core.d.ts +57 -3
  7. package/dist/rpc/client/rpcClient.core.js +189 -32
  8. package/dist/rpc/constants/index.d.ts +1 -1
  9. package/dist/rpc/constants/index.js +1 -1
  10. package/dist/rpc/constants/rpcConstants.core.d.ts +18 -0
  11. package/dist/rpc/constants/rpcConstants.core.js +18 -0
  12. package/dist/rpc/context/rpcContext.type.js +3 -1
  13. package/dist/rpc/dispatcher/index.d.ts +1 -0
  14. package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts +49 -2
  15. package/dist/rpc/dispatcher/rpcDispatcher.core.js +109 -14
  16. package/dist/rpc/middleware/rpcMiddleware.core.d.ts +12 -0
  17. package/dist/rpc/middleware/rpcMiddleware.core.js +29 -6
  18. package/dist/rpc/procedure/rpcProcedure.type.d.ts +25 -3
  19. package/dist/rpc/procedure/rpcProcedure.type.js +9 -0
  20. package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts +15 -1
  21. package/dist/rpc/procedure/rpcProcedureRegistry.core.js +26 -1
  22. package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts +1 -1
  23. package/dist/rpc/procedure/rpcProcedureRouter.core.js +5 -0
  24. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts +30 -3
  25. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js +60 -4
  26. package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts +14 -1
  27. package/dist/rpc/reliability/deadline/rpcDeadline.helper.js +23 -2
  28. package/dist/rpc/reliability/index.d.ts +5 -4
  29. package/dist/rpc/reliability/index.js +3 -3
  30. package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts +22 -0
  31. package/dist/rpc/reliability/retry/rpcRetry.helper.js +80 -8
  32. package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts +18 -2
  33. package/dist/rpc/reliability/timeout/rpcTimeout.helper.js +71 -7
  34. package/dist/rpc/server/index.d.ts +1 -0
  35. package/dist/rpc/server/rpcServer.core.d.ts +41 -2
  36. package/dist/rpc/server/rpcServer.core.js +117 -16
  37. package/dist/rpc/streaming/rpcStreaming.type.d.ts +9 -0
  38. package/dist/rpc/streaming/rpcStreaming.type.js +9 -0
  39. package/dist/rpc/transport/rpcTransport.type.d.ts +9 -1
  40. package/dist/rpc/validation/index.d.ts +3 -0
  41. package/dist/rpc/validation/index.js +2 -0
  42. package/dist/rpc/validation/rpcValidation.core.d.ts +86 -0
  43. package/dist/rpc/validation/rpcValidation.core.js +133 -0
  44. package/package.json +27 -16
  45. package/dist/.tsbuildinfo +0 -1
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/index.js.map +0 -1
  48. package/dist/rpc/client/index.d.ts.map +0 -1
  49. package/dist/rpc/client/index.js.map +0 -1
  50. package/dist/rpc/client/rpcClient.core.d.ts.map +0 -1
  51. package/dist/rpc/client/rpcClient.core.js.map +0 -1
  52. package/dist/rpc/constants/index.d.ts.map +0 -1
  53. package/dist/rpc/constants/index.js.map +0 -1
  54. package/dist/rpc/constants/rpcConstants.core.d.ts.map +0 -1
  55. package/dist/rpc/constants/rpcConstants.core.js.map +0 -1
  56. package/dist/rpc/context/index.d.ts.map +0 -1
  57. package/dist/rpc/context/index.js.map +0 -1
  58. package/dist/rpc/context/rpcContext.type.d.ts.map +0 -1
  59. package/dist/rpc/context/rpcContext.type.js.map +0 -1
  60. package/dist/rpc/dispatcher/index.d.ts.map +0 -1
  61. package/dist/rpc/dispatcher/index.js.map +0 -1
  62. package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts.map +0 -1
  63. package/dist/rpc/dispatcher/rpcDispatcher.core.js.map +0 -1
  64. package/dist/rpc/errors/index.d.ts.map +0 -1
  65. package/dist/rpc/errors/index.js.map +0 -1
  66. package/dist/rpc/errors/rpc.errors.d.ts.map +0 -1
  67. package/dist/rpc/errors/rpc.errors.js.map +0 -1
  68. package/dist/rpc/interceptor/index.d.ts.map +0 -1
  69. package/dist/rpc/interceptor/index.js.map +0 -1
  70. package/dist/rpc/interceptor/rpcInterceptor.type.d.ts.map +0 -1
  71. package/dist/rpc/interceptor/rpcInterceptor.type.js.map +0 -1
  72. package/dist/rpc/middleware/index.d.ts.map +0 -1
  73. package/dist/rpc/middleware/index.js.map +0 -1
  74. package/dist/rpc/middleware/rpcMiddleware.core.d.ts.map +0 -1
  75. package/dist/rpc/middleware/rpcMiddleware.core.js.map +0 -1
  76. package/dist/rpc/procedure/index.d.ts.map +0 -1
  77. package/dist/rpc/procedure/index.js.map +0 -1
  78. package/dist/rpc/procedure/rpcProcedure.type.d.ts.map +0 -1
  79. package/dist/rpc/procedure/rpcProcedure.type.js.map +0 -1
  80. package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts.map +0 -1
  81. package/dist/rpc/procedure/rpcProcedureRegistry.core.js.map +0 -1
  82. package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts.map +0 -1
  83. package/dist/rpc/procedure/rpcProcedureRouter.core.js.map +0 -1
  84. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts.map +0 -1
  85. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js.map +0 -1
  86. package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts.map +0 -1
  87. package/dist/rpc/reliability/deadline/rpcDeadline.helper.js.map +0 -1
  88. package/dist/rpc/reliability/index.d.ts.map +0 -1
  89. package/dist/rpc/reliability/index.js.map +0 -1
  90. package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts.map +0 -1
  91. package/dist/rpc/reliability/retry/rpcRetry.helper.js.map +0 -1
  92. package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts.map +0 -1
  93. package/dist/rpc/reliability/timeout/rpcTimeout.helper.js.map +0 -1
  94. package/dist/rpc/server/index.d.ts.map +0 -1
  95. package/dist/rpc/server/index.js.map +0 -1
  96. package/dist/rpc/server/rpcServer.core.d.ts.map +0 -1
  97. package/dist/rpc/server/rpcServer.core.js.map +0 -1
  98. package/dist/rpc/streaming/index.d.ts.map +0 -1
  99. package/dist/rpc/streaming/index.js.map +0 -1
  100. package/dist/rpc/streaming/rpcStreaming.type.d.ts.map +0 -1
  101. package/dist/rpc/streaming/rpcStreaming.type.js.map +0 -1
  102. package/dist/rpc/transport/index.d.ts.map +0 -1
  103. package/dist/rpc/transport/index.js.map +0 -1
  104. package/dist/rpc/transport/rpcTransport.type.d.ts.map +0 -1
  105. package/dist/rpc/transport/rpcTransport.type.js.map +0 -1
  106. package/dist/rpc/types/index.d.ts.map +0 -1
  107. package/dist/rpc/types/index.js.map +0 -1
  108. package/dist/rpc/types/rpcMetadata.type.d.ts.map +0 -1
  109. package/dist/rpc/types/rpcMetadata.type.js.map +0 -1
  110. package/dist/rpc/types/rpcProcedureName.type.d.ts.map +0 -1
  111. package/dist/rpc/types/rpcProcedureName.type.js.map +0 -1
  112. package/dist/rpc/types/rpcRequest.type.d.ts.map +0 -1
  113. package/dist/rpc/types/rpcRequest.type.js.map +0 -1
  114. package/dist/rpc/types/rpcResponse.type.d.ts.map +0 -1
  115. package/dist/rpc/types/rpcResponse.type.js.map +0 -1
  116. package/dist/testing/index.d.ts.map +0 -1
  117. package/dist/testing/index.js.map +0 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @zudojs/rpc/validation
3
+ *
4
+ * Request-level checks applied before a payload reaches a handler.
5
+ *
6
+ * Requests arrive from an untrusted peer over a transport, so these are
7
+ * the first checks the server runs, not the last.
8
+ */
9
+ import type { Schema, SchemaIssue } from "@zudojs/schema";
10
+ import type { RPCRequest } from "../types/rpcRequest.type.js";
11
+ /**
12
+ * Limits applied to an incoming request.
13
+ */
14
+ export interface RPCRequestLimits {
15
+ /**
16
+ * Maximum encoded payload size in bytes. Defaults to
17
+ * {@link MAX_RPC_PAYLOAD_SIZE}. Set to `0` to skip the check when the
18
+ * transport already enforces a frame limit.
19
+ */
20
+ readonly maxPayloadBytes?: number;
21
+ /**
22
+ * Whether procedure names must match {@link PROCEDURE_NAME_PATTERN}.
23
+ * Defaults to `true`.
24
+ */
25
+ readonly enforceProcedureNamePattern?: boolean;
26
+ }
27
+ /**
28
+ * Validates a procedure name.
29
+ *
30
+ * Length is checked before the pattern so a pathological name cannot
31
+ * drive regex backtracking.
32
+ */
33
+ export declare function assertValidProcedureName(name: unknown): asserts name is string;
34
+ /**
35
+ * Measures the encoded size of a payload in bytes.
36
+ *
37
+ * Returns `undefined` for a payload that cannot be encoded, which the
38
+ * caller reports as an invalid request rather than passing on.
39
+ */
40
+ export declare function measurePayloadBytes(payload: unknown): number | undefined;
41
+ /**
42
+ * Validates the shape and size of an incoming request.
43
+ *
44
+ * @throws {RPCInvalidRequestError} when the frame is malformed or the
45
+ * payload exceeds the configured limit.
46
+ */
47
+ export declare function assertValidRequest(request: unknown, limits?: RPCRequestLimits): asserts request is RPCRequest;
48
+ /**
49
+ * Converts schema issues into metadata safe to return to a caller.
50
+ *
51
+ * Only the path, code and message travel; the received input is dropped
52
+ * so a validation response cannot echo back data the caller sent.
53
+ */
54
+ export declare function toValidationIssues(issues: readonly SchemaIssue[]): readonly {
55
+ path: string;
56
+ code: string;
57
+ message: string;
58
+ }[];
59
+ /**
60
+ * The part of a `@zudojs/schema` Schema this package depends on.
61
+ *
62
+ * Structural so any schema-like validator can be supplied, and so the
63
+ * dependency stays to the parse contract rather than the class.
64
+ */
65
+ export type RPCSchema<TOutput = unknown> = Pick<Schema<TOutput, never>, "safeParse">;
66
+ /**
67
+ * Parses caller input against a schema.
68
+ *
69
+ * @throws {RPCValidationError} carrying the issues, which are safe to
70
+ * return: they describe the caller's own input.
71
+ */
72
+ export declare function parseInput<T>(schema: RPCSchema<T>, value: unknown, procedureName: string): T;
73
+ /**
74
+ * Parses a handler's result against its output schema.
75
+ *
76
+ * An invalid output is a server defect rather than a caller mistake, so
77
+ * it is thrown as an `RPCInternalError` (`expose: false`): the server
78
+ * answers with the generic internal error and hands the detail — the
79
+ * failing paths and codes — to `onInternalError`. Throwing a validation
80
+ * error here told the caller *its* request was invalid, with an empty
81
+ * issue list, for a fault that is entirely the handler's.
82
+ *
83
+ * @throws {RPCInternalError}
84
+ */
85
+ export declare function parseOutput<T>(schema: RPCSchema<T>, value: unknown, procedureName: string): T;
86
+ //# sourceMappingURL=rpcValidation.core.d.ts.map
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @zudojs/rpc/validation
3
+ *
4
+ * Request-level checks applied before a payload reaches a handler.
5
+ *
6
+ * Requests arrive from an untrusted peer over a transport, so these are
7
+ * the first checks the server runs, not the last.
8
+ */
9
+ import { RPCInternalError, RPCInvalidRequestError, RPCValidationError, } from "../errors/rpc.errors.js";
10
+ import { MAX_PROCEDURE_NAME_LENGTH, MAX_RPC_PAYLOAD_SIZE, PROCEDURE_NAME_PATTERN, } from "../constants/rpcConstants.core.js";
11
+ /**
12
+ * Validates a procedure name.
13
+ *
14
+ * Length is checked before the pattern so a pathological name cannot
15
+ * drive regex backtracking.
16
+ */
17
+ export function assertValidProcedureName(name) {
18
+ if (typeof name !== "string" || name.length === 0) {
19
+ throw new RPCInvalidRequestError("Procedure name must be a non-empty string.");
20
+ }
21
+ if (name.length > MAX_PROCEDURE_NAME_LENGTH) {
22
+ throw new RPCInvalidRequestError(`Procedure name exceeds ${MAX_PROCEDURE_NAME_LENGTH} characters.`);
23
+ }
24
+ if (!PROCEDURE_NAME_PATTERN.test(name)) {
25
+ throw new RPCInvalidRequestError(`Procedure name "${name}" must be dot-separated identifiers, e.g. "users.getUser".`);
26
+ }
27
+ }
28
+ /**
29
+ * Measures the encoded size of a payload in bytes.
30
+ *
31
+ * Returns `undefined` for a payload that cannot be encoded, which the
32
+ * caller reports as an invalid request rather than passing on.
33
+ */
34
+ export function measurePayloadBytes(payload) {
35
+ if (payload === undefined) {
36
+ return 0;
37
+ }
38
+ try {
39
+ const encoded = JSON.stringify(payload);
40
+ if (encoded === undefined) {
41
+ return undefined;
42
+ }
43
+ return Buffer.byteLength(encoded, "utf8");
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ /**
50
+ * Validates the shape and size of an incoming request.
51
+ *
52
+ * @throws {RPCInvalidRequestError} when the frame is malformed or the
53
+ * payload exceeds the configured limit.
54
+ */
55
+ export function assertValidRequest(request, limits = {}) {
56
+ if (typeof request !== "object" || request === null) {
57
+ throw new RPCInvalidRequestError("Request must be an object.");
58
+ }
59
+ const candidate = request;
60
+ if (typeof candidate.id !== "string" || candidate.id.length === 0) {
61
+ throw new RPCInvalidRequestError("Request id must be a non-empty string.");
62
+ }
63
+ if (limits.enforceProcedureNamePattern ?? true) {
64
+ assertValidProcedureName(candidate.procedure);
65
+ }
66
+ else if (typeof candidate.procedure !== "string" ||
67
+ candidate.procedure.length === 0) {
68
+ throw new RPCInvalidRequestError("Procedure name must be a non-empty string.", undefined);
69
+ }
70
+ if (candidate.metadata !== undefined &&
71
+ (typeof candidate.metadata !== "object" || candidate.metadata === null)) {
72
+ throw new RPCInvalidRequestError("Request metadata must be an object.", candidate.procedure);
73
+ }
74
+ const maxBytes = limits.maxPayloadBytes ?? MAX_RPC_PAYLOAD_SIZE;
75
+ if (maxBytes > 0) {
76
+ const size = measurePayloadBytes(candidate.payload);
77
+ if (size === undefined) {
78
+ throw new RPCInvalidRequestError("Request payload could not be encoded.", candidate.procedure);
79
+ }
80
+ if (size > maxBytes) {
81
+ throw new RPCInvalidRequestError(`Request payload of ${size} bytes exceeds the ${maxBytes} byte limit.`, candidate.procedure);
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Converts schema issues into metadata safe to return to a caller.
87
+ *
88
+ * Only the path, code and message travel; the received input is dropped
89
+ * so a validation response cannot echo back data the caller sent.
90
+ */
91
+ export function toValidationIssues(issues) {
92
+ return issues.map((issue) => ({
93
+ path: issue.path.map(String).join("."),
94
+ code: String(issue.code),
95
+ message: issue.message,
96
+ }));
97
+ }
98
+ /**
99
+ * Parses caller input against a schema.
100
+ *
101
+ * @throws {RPCValidationError} carrying the issues, which are safe to
102
+ * return: they describe the caller's own input.
103
+ */
104
+ export function parseInput(schema, value, procedureName) {
105
+ const result = schema.safeParse(value);
106
+ if (result.success) {
107
+ return result.data;
108
+ }
109
+ throw new RPCValidationError(`Invalid input for procedure "${procedureName}".`, toValidationIssues(result.issues), procedureName);
110
+ }
111
+ /**
112
+ * Parses a handler's result against its output schema.
113
+ *
114
+ * An invalid output is a server defect rather than a caller mistake, so
115
+ * it is thrown as an `RPCInternalError` (`expose: false`): the server
116
+ * answers with the generic internal error and hands the detail — the
117
+ * failing paths and codes — to `onInternalError`. Throwing a validation
118
+ * error here told the caller *its* request was invalid, with an empty
119
+ * issue list, for a fault that is entirely the handler's.
120
+ *
121
+ * @throws {RPCInternalError}
122
+ */
123
+ export function parseOutput(schema, value, procedureName) {
124
+ const result = schema.safeParse(value);
125
+ if (result.success) {
126
+ return result.data;
127
+ }
128
+ const where = toValidationIssues(result.issues)
129
+ .map((issue) => `${issue.path || "(root)"}: ${issue.code}`)
130
+ .join(", ");
131
+ throw new RPCInternalError(`Procedure "${procedureName}" produced a response that does not match its output schema at ${where}.`, procedureName);
132
+ }
133
+ //# sourceMappingURL=rpcValidation.core.js.map
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@zudojs/rpc",
3
- "version": "0.1.0",
3
+ "version": "1.1.0",
4
4
  "description": "Remote procedure call infrastructure for Zudojs applications.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "module": "./dist/index.js",
@@ -14,26 +18,22 @@
14
18
  }
15
19
  },
16
20
  "files": [
17
- "dist"
21
+ "dist",
22
+ "!dist/**/*.map",
23
+ "!dist/**/*.tsbuildinfo",
24
+ "!dist/.tsbuildinfo"
18
25
  ],
19
- "scripts": {
20
- "build": "tsc -p tsconfig.json",
21
- "typecheck": "tsc -p tsconfig.json --noEmit",
22
- "clean": "rm -rf dist",
23
- "test": "vitest run",
24
- "test:watch": "vitest"
25
- },
26
26
  "engines": {
27
27
  "node": ">=24.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@zudojs/errors": "0.1.0",
31
- "@zudojs/constants": "0.1.0",
32
- "@zudojs/types": "0.1.0",
33
- "@zudojs/schema": "0.1.0"
30
+ "@zudojs/errors": "1.0.1",
31
+ "@zudojs/constants": "1.0.1",
32
+ "@zudojs/types": "1.0.0",
33
+ "@zudojs/schema": "1.0.1"
34
34
  },
35
35
  "devDependencies": {
36
- "typescript": "^7.0.2",
36
+ "typescript": "7.0.2",
37
37
  "vitest": "^4.1.11"
38
38
  },
39
39
  "publishConfig": {
@@ -46,8 +46,19 @@
46
46
  "procedure"
47
47
  ],
48
48
  "homepage": "https://github.com/oyinlola-tech/zudo#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/oyinlola-tech/zudo/issues"
51
+ },
49
52
  "repository": {
50
53
  "type": "git",
51
- "url": "https://github.com/oyinlola-tech/zudo"
54
+ "url": "https://github.com/oyinlola-tech/zudo",
55
+ "directory": "packages/rpc"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.json",
59
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
60
+ "clean": "rm -rf dist",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest"
52
63
  }
53
- }
64
+ }