@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.
- package/LICENSE +21 -0
- package/README.md +104 -12
- package/dist/index.d.ts +8 -4
- package/dist/index.js +3 -4
- package/dist/rpc/client/index.d.ts +1 -1
- package/dist/rpc/client/rpcClient.core.d.ts +57 -3
- package/dist/rpc/client/rpcClient.core.js +189 -32
- package/dist/rpc/constants/index.d.ts +1 -1
- package/dist/rpc/constants/index.js +1 -1
- package/dist/rpc/constants/rpcConstants.core.d.ts +18 -0
- package/dist/rpc/constants/rpcConstants.core.js +18 -0
- package/dist/rpc/context/rpcContext.type.js +3 -1
- package/dist/rpc/dispatcher/index.d.ts +1 -0
- package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts +49 -2
- package/dist/rpc/dispatcher/rpcDispatcher.core.js +109 -14
- package/dist/rpc/middleware/rpcMiddleware.core.d.ts +12 -0
- package/dist/rpc/middleware/rpcMiddleware.core.js +29 -6
- package/dist/rpc/procedure/rpcProcedure.type.d.ts +25 -3
- package/dist/rpc/procedure/rpcProcedure.type.js +9 -0
- package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts +15 -1
- package/dist/rpc/procedure/rpcProcedureRegistry.core.js +26 -1
- package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts +1 -1
- package/dist/rpc/procedure/rpcProcedureRouter.core.js +5 -0
- package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts +30 -3
- package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js +60 -4
- package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts +14 -1
- package/dist/rpc/reliability/deadline/rpcDeadline.helper.js +23 -2
- package/dist/rpc/reliability/index.d.ts +5 -4
- package/dist/rpc/reliability/index.js +3 -3
- package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts +22 -0
- package/dist/rpc/reliability/retry/rpcRetry.helper.js +80 -8
- package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts +18 -2
- package/dist/rpc/reliability/timeout/rpcTimeout.helper.js +71 -7
- package/dist/rpc/server/index.d.ts +1 -0
- package/dist/rpc/server/rpcServer.core.d.ts +41 -2
- package/dist/rpc/server/rpcServer.core.js +117 -16
- package/dist/rpc/streaming/rpcStreaming.type.d.ts +9 -0
- package/dist/rpc/streaming/rpcStreaming.type.js +9 -0
- package/dist/rpc/transport/rpcTransport.type.d.ts +9 -1
- package/dist/rpc/validation/index.d.ts +3 -0
- package/dist/rpc/validation/index.js +2 -0
- package/dist/rpc/validation/rpcValidation.core.d.ts +86 -0
- package/dist/rpc/validation/rpcValidation.core.js +133 -0
- package/package.json +27 -16
- package/dist/.tsbuildinfo +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/rpc/client/index.d.ts.map +0 -1
- package/dist/rpc/client/index.js.map +0 -1
- package/dist/rpc/client/rpcClient.core.d.ts.map +0 -1
- package/dist/rpc/client/rpcClient.core.js.map +0 -1
- package/dist/rpc/constants/index.d.ts.map +0 -1
- package/dist/rpc/constants/index.js.map +0 -1
- package/dist/rpc/constants/rpcConstants.core.d.ts.map +0 -1
- package/dist/rpc/constants/rpcConstants.core.js.map +0 -1
- package/dist/rpc/context/index.d.ts.map +0 -1
- package/dist/rpc/context/index.js.map +0 -1
- package/dist/rpc/context/rpcContext.type.d.ts.map +0 -1
- package/dist/rpc/context/rpcContext.type.js.map +0 -1
- package/dist/rpc/dispatcher/index.d.ts.map +0 -1
- package/dist/rpc/dispatcher/index.js.map +0 -1
- package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts.map +0 -1
- package/dist/rpc/dispatcher/rpcDispatcher.core.js.map +0 -1
- package/dist/rpc/errors/index.d.ts.map +0 -1
- package/dist/rpc/errors/index.js.map +0 -1
- package/dist/rpc/errors/rpc.errors.d.ts.map +0 -1
- package/dist/rpc/errors/rpc.errors.js.map +0 -1
- package/dist/rpc/interceptor/index.d.ts.map +0 -1
- package/dist/rpc/interceptor/index.js.map +0 -1
- package/dist/rpc/interceptor/rpcInterceptor.type.d.ts.map +0 -1
- package/dist/rpc/interceptor/rpcInterceptor.type.js.map +0 -1
- package/dist/rpc/middleware/index.d.ts.map +0 -1
- package/dist/rpc/middleware/index.js.map +0 -1
- package/dist/rpc/middleware/rpcMiddleware.core.d.ts.map +0 -1
- package/dist/rpc/middleware/rpcMiddleware.core.js.map +0 -1
- package/dist/rpc/procedure/index.d.ts.map +0 -1
- package/dist/rpc/procedure/index.js.map +0 -1
- package/dist/rpc/procedure/rpcProcedure.type.d.ts.map +0 -1
- package/dist/rpc/procedure/rpcProcedure.type.js.map +0 -1
- package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts.map +0 -1
- package/dist/rpc/procedure/rpcProcedureRegistry.core.js.map +0 -1
- package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts.map +0 -1
- package/dist/rpc/procedure/rpcProcedureRouter.core.js.map +0 -1
- package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts.map +0 -1
- package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js.map +0 -1
- package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts.map +0 -1
- package/dist/rpc/reliability/deadline/rpcDeadline.helper.js.map +0 -1
- package/dist/rpc/reliability/index.d.ts.map +0 -1
- package/dist/rpc/reliability/index.js.map +0 -1
- package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts.map +0 -1
- package/dist/rpc/reliability/retry/rpcRetry.helper.js.map +0 -1
- package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts.map +0 -1
- package/dist/rpc/reliability/timeout/rpcTimeout.helper.js.map +0 -1
- package/dist/rpc/server/index.d.ts.map +0 -1
- package/dist/rpc/server/index.js.map +0 -1
- package/dist/rpc/server/rpcServer.core.d.ts.map +0 -1
- package/dist/rpc/server/rpcServer.core.js.map +0 -1
- package/dist/rpc/streaming/index.d.ts.map +0 -1
- package/dist/rpc/streaming/index.js.map +0 -1
- package/dist/rpc/streaming/rpcStreaming.type.d.ts.map +0 -1
- package/dist/rpc/streaming/rpcStreaming.type.js.map +0 -1
- package/dist/rpc/transport/index.d.ts.map +0 -1
- package/dist/rpc/transport/index.js.map +0 -1
- package/dist/rpc/transport/rpcTransport.type.d.ts.map +0 -1
- package/dist/rpc/transport/rpcTransport.type.js.map +0 -1
- package/dist/rpc/types/index.d.ts.map +0 -1
- package/dist/rpc/types/index.js.map +0 -1
- package/dist/rpc/types/rpcMetadata.type.d.ts.map +0 -1
- package/dist/rpc/types/rpcMetadata.type.js.map +0 -1
- package/dist/rpc/types/rpcProcedureName.type.d.ts.map +0 -1
- package/dist/rpc/types/rpcProcedureName.type.js.map +0 -1
- package/dist/rpc/types/rpcRequest.type.d.ts.map +0 -1
- package/dist/rpc/types/rpcRequest.type.js.map +0 -1
- package/dist/rpc/types/rpcResponse.type.d.ts.map +0 -1
- package/dist/rpc/types/rpcResponse.type.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zudojs Contributors
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zudojs/rpc
|
|
2
2
|
|
|
3
|
-
Type-safe RPC — define procedures, apply middleware, dispatch calls, and serve over
|
|
3
|
+
Type-safe RPC — define procedures, apply middleware, dispatch calls, and serve them over your own transport.
|
|
4
4
|
|
|
5
5
|
## When to use
|
|
6
6
|
|
|
@@ -8,9 +8,14 @@ Import this when you need:
|
|
|
8
8
|
|
|
9
9
|
- a typed RPC layer between services (gateway ↔ microservice, frontend ↔ backend)
|
|
10
10
|
- procedure-level middleware (auth, tracing, rate limit)
|
|
11
|
-
- a
|
|
11
|
+
- a server that validates frames and maps every failure to a wire code
|
|
12
12
|
- structured RPC errors
|
|
13
13
|
|
|
14
|
+
The package is transport-agnostic: `RPCServer.handle` takes a request frame
|
|
15
|
+
and returns a response frame, and `RPCClient` sends through any object
|
|
16
|
+
implementing `RPCTransport`. It ships no HTTP or WebSocket transport of its
|
|
17
|
+
own.
|
|
18
|
+
|
|
14
19
|
For request/response inside one process, prefer `@zudojs/api`.
|
|
15
20
|
|
|
16
21
|
## Installation
|
|
@@ -24,8 +29,11 @@ npm install @zudojs/rpc
|
|
|
24
29
|
```typescript
|
|
25
30
|
import {
|
|
26
31
|
createRPCProcedure,
|
|
27
|
-
|
|
32
|
+
createRPCRequest,
|
|
28
33
|
RPCServer,
|
|
34
|
+
RPCClient,
|
|
35
|
+
RPCDispatcher,
|
|
36
|
+
RPCProcedureRegistry,
|
|
29
37
|
RPCMiddlewareStack,
|
|
30
38
|
createRPCContext,
|
|
31
39
|
type RPCContext,
|
|
@@ -33,24 +41,108 @@ import {
|
|
|
33
41
|
type RPCProcedure,
|
|
34
42
|
type RPCRequest,
|
|
35
43
|
type RPCResponse,
|
|
44
|
+
type RPCTransport,
|
|
36
45
|
type RPCErrorOptions,
|
|
37
46
|
} from "@zudojs/rpc";
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
## Usage
|
|
41
50
|
|
|
51
|
+
A procedure is `createRPCProcedure(name, handler, options?)`. Names are
|
|
52
|
+
dot-separated identifiers (`"math.sum"`); the handler is positional,
|
|
53
|
+
`(input, context)`; `options.input` / `options.output` are `@zudojs/schema`
|
|
54
|
+
schemas (anything with `safeParse`) the payload and result are checked
|
|
55
|
+
against.
|
|
56
|
+
|
|
42
57
|
```typescript
|
|
43
|
-
import { createRPCProcedure,
|
|
58
|
+
import { createRPCProcedure, createRPCRequest, RPCServer } from "@zudojs/rpc";
|
|
59
|
+
import { schema } from "@zudojs/schema";
|
|
44
60
|
|
|
45
|
-
const sum = createRPCProcedure(
|
|
46
|
-
|
|
47
|
-
input: { a:
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
const sum = createRPCProcedure(
|
|
62
|
+
"math.sum",
|
|
63
|
+
async (input: { a: number; b: number }) => input.a + input.b,
|
|
64
|
+
{ input: schema.object({ a: schema.number(), b: schema.number() }) },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const server = new RPCServer();
|
|
68
|
+
server.register(sum);
|
|
69
|
+
|
|
70
|
+
const response = await server.handle(
|
|
71
|
+
createRPCRequest({ id: "req-1", procedure: "math.sum", payload: { a: 1, b: 2 } }),
|
|
72
|
+
);
|
|
73
|
+
// { id: "req-1", success: true, result: 3 }
|
|
74
|
+
```
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
`handle` never throws for a bad request: a malformed frame, an unknown
|
|
77
|
+
procedure, a payload the input schema rejects, a timeout or a handler error
|
|
78
|
+
each come back as `{ success: false, error: { code, message, details? } }`.
|
|
79
|
+
The frame's `metadata` is optional.
|
|
80
|
+
|
|
81
|
+
### Calling through a transport
|
|
82
|
+
|
|
83
|
+
`RPCClient` needs an `RPCTransport` — an object whose `send(request, options)`
|
|
84
|
+
delivers the frame and resolves with the response. `options.signal` aborts the
|
|
85
|
+
call and `options.timeout` is the deadline in milliseconds, so a transport can
|
|
86
|
+
set its own socket timeout. The in-process transport below is the smallest
|
|
87
|
+
possible one:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { RPCClient, type RPCTransport } from "@zudojs/rpc";
|
|
91
|
+
|
|
92
|
+
const transport: RPCTransport = { send: (request) => server.handle(request) };
|
|
93
|
+
const client = new RPCClient(transport, { timeout: 5_000 });
|
|
94
|
+
|
|
95
|
+
const total = await client.call<{ a: number; b: number }, number>("math.sum", { a: 1, b: 2 });
|
|
96
|
+
// 3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A failed call rejects with a typed error rebuilt from the wire code
|
|
100
|
+
(`RPCTimeoutError`, `RPCCancelledError`, `RPCUnavailableError`, or an
|
|
101
|
+
`RPCError` carrying the server's `code` and `details`).
|
|
102
|
+
|
|
103
|
+
### Middleware
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { RPCMiddlewareStack, RPCAuthenticationError } from "@zudojs/rpc";
|
|
107
|
+
|
|
108
|
+
const stack = new RPCMiddlewareStack([
|
|
109
|
+
async (context, next) => {
|
|
110
|
+
if (context.metadata.userId === undefined) {
|
|
111
|
+
throw new RPCAuthenticationError("Sign in first.");
|
|
112
|
+
}
|
|
113
|
+
return next();
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const server = new RPCServer(undefined, stack);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Each middleware may call `next()` once. Input validation runs before the
|
|
121
|
+
stack, so middleware sees a payload the procedure's schema has accepted.
|
|
122
|
+
|
|
123
|
+
## Errors
|
|
124
|
+
|
|
125
|
+
Every error class from `@zudojs/errors`' RPC family is re-exported. The
|
|
126
|
+
server maps them to wire codes (`RPC_PROCEDURE_NOT_FOUND`,
|
|
127
|
+
`RPC_VALIDATION_ERROR`, `RPC_UNAUTHENTICATED`, `RPC_FORBIDDEN`,
|
|
128
|
+
`RPC_RATE_LIMITED`, `RPC_TIMEOUT`, …). A custom `RPCError` subclass keeps its
|
|
129
|
+
own `code`.
|
|
130
|
+
|
|
131
|
+
What reaches the caller follows the error's `expose` flag. Anything thrown
|
|
132
|
+
with `expose: false` — an `RPCInternalError`, an `RPCSerializationError`, a
|
|
133
|
+
plain `new RPCError(...)` (whose default is `expose: false`), or any
|
|
134
|
+
non-RPC error — is answered with the fixed `INTERNAL_ERROR_MESSAGE`; the
|
|
135
|
+
original error is handed to `onInternalError(error, requestId)` so it can be
|
|
136
|
+
logged against the request id. A handler result that fails the procedure's
|
|
137
|
+
`output` schema is treated the same way: it is the server's fault, not the
|
|
138
|
+
caller's.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const server = new RPCServer(undefined, undefined, {
|
|
142
|
+
limits: { maxPayloadBytes: 256 * 1024 },
|
|
143
|
+
dispatch: { defaultTimeout: 10_000 },
|
|
144
|
+
onInternalError: (error, requestId) => logger.error({ requestId, error }),
|
|
145
|
+
});
|
|
54
146
|
```
|
|
55
147
|
|
|
56
148
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
export type { RPCProcedureName, RPCMetadata, RPCMetadataOptions, RPCRequest, RPCRequestOptions, RPCErrorPayload, RPCResponse, } from "./rpc/types/index.js";
|
|
23
23
|
export { createRPCMetadata, createRPCRequest, createRPCResponse, createRPCErrorResponse, } from "./rpc/types/index.js";
|
|
24
|
-
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpc/constants/index.js";
|
|
24
|
+
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpc/constants/index.js";
|
|
25
|
+
export type { RPCRequestLimits, RPCSchema } from "./rpc/validation/index.js";
|
|
26
|
+
export { assertValidProcedureName, assertValidRequest, measurePayloadBytes, toValidationIssues, parseInput, parseOutput, } from "./rpc/validation/index.js";
|
|
25
27
|
export type { RPCErrorOptions } from "./rpc/errors/index.js";
|
|
26
28
|
export { RPCError, RPCProcedureNotFoundError, RPCInvalidRequestError, RPCValidationError, RPCAuthenticationError, RPCForbiddenError, RPCTimeoutError, RPCCancelledError, RPCInternalError, RPCTransportError, RPCSerializationError, RPCDeserializationError, RPCUnavailableError, RPCRateLimitedError, RPCDeadlineExceededError, RPCDuplicateProcedureError, createRPCError, isRPCError, } from "./rpc/errors/index.js";
|
|
27
29
|
export type { RPCHandler, RPCProcedure, RPCProcedureOptions, } from "./rpc/procedure/index.js";
|
|
@@ -31,13 +33,15 @@ export type { RPCContext } from "./rpc/context/index.js";
|
|
|
31
33
|
export { createRPCContext } from "./rpc/context/index.js";
|
|
32
34
|
export type { RPCMiddleware } from "./rpc/middleware/index.js";
|
|
33
35
|
export { RPCMiddlewareStack } from "./rpc/middleware/index.js";
|
|
36
|
+
export type { RPCDispatcherOptions } from "./rpc/dispatcher/index.js";
|
|
34
37
|
export { RPCDispatcher } from "./rpc/dispatcher/index.js";
|
|
38
|
+
export type { RPCServerOptions } from "./rpc/server/index.js";
|
|
35
39
|
export { RPCServer } from "./rpc/server/index.js";
|
|
36
40
|
export type { RPCTransport, RPCTransportRequestOptions, } from "./rpc/transport/index.js";
|
|
37
|
-
export type { RPCCallOptions } from "./rpc/client/index.js";
|
|
41
|
+
export type { RPCCallOptions, RPCClientOptions } from "./rpc/client/index.js";
|
|
38
42
|
export { RPCClient } from "./rpc/client/index.js";
|
|
39
|
-
export { createTimeout, withTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, createCancellableSignal, cancelSignal, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
|
|
40
|
-
export type { RPCBackoff, RPCRetryOptions } from "./rpc/reliability/index.js";
|
|
43
|
+
export { createTimeout, withTimeout, runWithTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, readDeadline, createCancellableSignal, cancelSignal, throwIfCancelled, combineSignals, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
|
|
44
|
+
export type { RPCBackoff, RPCJitter, RPCRetryOptions, CancellableSignal, } from "./rpc/reliability/index.js";
|
|
41
45
|
export type { RPCInterceptor } from "./rpc/interceptor/index.js";
|
|
42
46
|
export { createNoopRPCInterceptor } from "./rpc/interceptor/index.js";
|
|
43
47
|
export type { RPCStreamingHandler, RPCStreamingProcedure, } from "./rpc/streaming/index.js";
|
package/dist/index.js
CHANGED
|
@@ -21,19 +21,18 @@
|
|
|
21
21
|
*/
|
|
22
22
|
export { createRPCMetadata, createRPCRequest, createRPCResponse, createRPCErrorResponse, } from "./rpc/types/index.js";
|
|
23
23
|
// Constants
|
|
24
|
-
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpc/constants/index.js";
|
|
24
|
+
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpc/constants/index.js";
|
|
25
|
+
export { assertValidProcedureName, assertValidRequest, measurePayloadBytes, toValidationIssues, parseInput, parseOutput, } from "./rpc/validation/index.js";
|
|
25
26
|
export { RPCError, RPCProcedureNotFoundError, RPCInvalidRequestError, RPCValidationError, RPCAuthenticationError, RPCForbiddenError, RPCTimeoutError, RPCCancelledError, RPCInternalError, RPCTransportError, RPCSerializationError, RPCDeserializationError, RPCUnavailableError, RPCRateLimitedError, RPCDeadlineExceededError, RPCDuplicateProcedureError, createRPCError, isRPCError, } from "./rpc/errors/index.js";
|
|
26
27
|
export { createRPCProcedure } from "./rpc/procedure/index.js";
|
|
27
28
|
export { RPCProcedureRegistry, RPCProcedureRouter, } from "./rpc/procedure/index.js";
|
|
28
29
|
export { createRPCContext } from "./rpc/context/index.js";
|
|
29
30
|
export { RPCMiddlewareStack } from "./rpc/middleware/index.js";
|
|
30
|
-
// Dispatcher
|
|
31
31
|
export { RPCDispatcher } from "./rpc/dispatcher/index.js";
|
|
32
|
-
// Server
|
|
33
32
|
export { RPCServer } from "./rpc/server/index.js";
|
|
34
33
|
export { RPCClient } from "./rpc/client/index.js";
|
|
35
34
|
// Reliability
|
|
36
|
-
export { createTimeout, withTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, createCancellableSignal, cancelSignal, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
|
|
35
|
+
export { createTimeout, withTimeout, runWithTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, readDeadline, createCancellableSignal, cancelSignal, throwIfCancelled, combineSignals, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
|
|
37
36
|
export { createNoopRPCInterceptor } from "./rpc/interceptor/index.js";
|
|
38
37
|
export { createRPCStreamingProcedure } from "./rpc/streaming/index.js";
|
|
39
38
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,23 +4,77 @@ import type { RPCTransport } from "../transport/rpcTransport.type.js";
|
|
|
4
4
|
*/
|
|
5
5
|
export interface RPCCallOptions {
|
|
6
6
|
readonly timeout?: number;
|
|
7
|
+
/** Cancels the call. An already-aborted signal fails immediately. */
|
|
7
8
|
readonly signal?: AbortSignal;
|
|
8
9
|
readonly metadata?: Record<string, string | number | boolean | undefined>;
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Options for constructing an RPC client.
|
|
13
|
+
*/
|
|
14
|
+
export interface RPCClientOptions {
|
|
15
|
+
/** Default timeout for calls that do not specify one. */
|
|
16
|
+
readonly timeout?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of calls in flight at once. Defaults to
|
|
19
|
+
* {@link MAX_PENDING_REQUESTS}. Additional calls fail fast rather than
|
|
20
|
+
* queueing without bound.
|
|
21
|
+
*/
|
|
22
|
+
readonly maxPending?: number;
|
|
23
|
+
}
|
|
10
24
|
/**
|
|
11
25
|
* RPC client for invoking remote procedures.
|
|
26
|
+
*
|
|
27
|
+
* Every call is tracked while it is genuinely in flight and removed the
|
|
28
|
+
* moment it settles, so the concurrency limit reflects real in-flight
|
|
29
|
+
* work rather than accumulating entries from calls that already
|
|
30
|
+
* succeeded.
|
|
12
31
|
*/
|
|
13
32
|
export declare class RPCClient {
|
|
14
33
|
private readonly transport;
|
|
34
|
+
private readonly options;
|
|
15
35
|
private readonly pending;
|
|
16
|
-
|
|
36
|
+
private closed;
|
|
37
|
+
constructor(transport: RPCTransport, options?: RPCClientOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Number of calls currently in flight.
|
|
40
|
+
*/
|
|
41
|
+
get pendingCount(): number;
|
|
17
42
|
/**
|
|
18
43
|
* Calls a remote procedure.
|
|
19
44
|
*/
|
|
20
45
|
call<TInput = unknown, TOutput = unknown>(procedure: string, input: TInput, options?: RPCCallOptions): Promise<TOutput>;
|
|
21
46
|
/**
|
|
22
|
-
*
|
|
47
|
+
* Cancels every in-flight call and stops accepting new ones.
|
|
48
|
+
*/
|
|
49
|
+
close(reason?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Describes the calls currently in flight, for diagnostics.
|
|
52
|
+
*/
|
|
53
|
+
inspectPending(): readonly {
|
|
54
|
+
procedure: string;
|
|
55
|
+
elapsedMs: number;
|
|
56
|
+
}[];
|
|
57
|
+
/**
|
|
58
|
+
* A promise that rejects when the signal aborts.
|
|
59
|
+
*/
|
|
60
|
+
private abortPromise;
|
|
61
|
+
private abortReason;
|
|
62
|
+
private cancellationError;
|
|
63
|
+
/**
|
|
64
|
+
* Normalises an abort reason into an `RPCCancelledError`.
|
|
65
|
+
*
|
|
66
|
+
* A bare `controller.abort()` yields a `DOMException`, so callers that
|
|
67
|
+
* branch on error type would never see a cancellation. The original
|
|
68
|
+
* reason is preserved as `cause`; a reason that is already an RPC
|
|
69
|
+
* error is passed through untouched.
|
|
70
|
+
*/
|
|
71
|
+
private toCancellation;
|
|
72
|
+
/**
|
|
73
|
+
* Reconstructs a typed error from an error response.
|
|
74
|
+
*
|
|
75
|
+
* The wire code drives the type, so a caller can tell an
|
|
76
|
+
* authentication failure from a timeout without string matching.
|
|
23
77
|
*/
|
|
24
|
-
private
|
|
78
|
+
private toError;
|
|
25
79
|
}
|
|
26
80
|
//# sourceMappingURL=rpcClient.core.d.ts.map
|
|
@@ -1,60 +1,217 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { createRPCRequest } from "../types/rpcRequest.type.js";
|
|
2
|
-
import {
|
|
3
|
+
import { isRPCError, RPCCancelledError, RPCError, RPCTimeoutError, RPCUnavailableError, } from "../errors/rpc.errors.js";
|
|
3
4
|
import { DEFAULT_RPC_TIMEOUT, MAX_PENDING_REQUESTS, } from "../constants/rpcConstants.core.js";
|
|
5
|
+
import { createTimeout } from "../reliability/timeout/rpcTimeout.helper.js";
|
|
4
6
|
/**
|
|
5
7
|
* RPC client for invoking remote procedures.
|
|
8
|
+
*
|
|
9
|
+
* Every call is tracked while it is genuinely in flight and removed the
|
|
10
|
+
* moment it settles, so the concurrency limit reflects real in-flight
|
|
11
|
+
* work rather than accumulating entries from calls that already
|
|
12
|
+
* succeeded.
|
|
6
13
|
*/
|
|
7
14
|
export class RPCClient {
|
|
8
15
|
transport;
|
|
16
|
+
options;
|
|
9
17
|
pending = new Map();
|
|
10
|
-
|
|
18
|
+
closed = false;
|
|
19
|
+
constructor(transport, options = {}) {
|
|
11
20
|
this.transport = transport;
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Number of calls currently in flight.
|
|
25
|
+
*/
|
|
26
|
+
get pendingCount() {
|
|
27
|
+
return this.pending.size;
|
|
12
28
|
}
|
|
13
29
|
/**
|
|
14
30
|
* Calls a remote procedure.
|
|
15
31
|
*/
|
|
16
32
|
async call(procedure, input, options = {}) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
if (this.closed) {
|
|
34
|
+
throw new RPCUnavailableError("RPC client has been closed.", procedure);
|
|
35
|
+
}
|
|
36
|
+
const maxPending = this.options.maxPending ?? MAX_PENDING_REQUESTS;
|
|
37
|
+
if (this.pending.size >= maxPending) {
|
|
38
|
+
throw new RPCUnavailableError(`Too many RPC requests in flight (${this.pending.size}/${maxPending}).`, procedure);
|
|
21
39
|
}
|
|
22
|
-
|
|
23
|
-
this.
|
|
40
|
+
if (options.signal?.aborted) {
|
|
41
|
+
throw this.cancellationError(options.signal, procedure);
|
|
42
|
+
}
|
|
43
|
+
const id = randomUUID();
|
|
44
|
+
const timeoutMs = options.timeout ?? this.options.timeout ?? DEFAULT_RPC_TIMEOUT;
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timeout = timeoutMs > 0 ? createTimeout(timeoutMs, procedure) : undefined;
|
|
47
|
+
const onCallerAbort = () => {
|
|
48
|
+
controller.abort(this.cancellationError(options.signal, procedure));
|
|
49
|
+
};
|
|
50
|
+
options.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
51
|
+
this.pending.set(id, {
|
|
52
|
+
procedure,
|
|
53
|
+
startedAt: Date.now(),
|
|
54
|
+
cancel: (reason) => {
|
|
55
|
+
if (!controller.signal.aborted) {
|
|
56
|
+
controller.abort(reason);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const request = createRPCRequest({
|
|
24
62
|
id,
|
|
25
63
|
procedure,
|
|
26
64
|
payload: input,
|
|
27
65
|
metadata: options.metadata,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
});
|
|
67
|
+
// The transport is told the deadline as well as given the signal:
|
|
68
|
+
// `RPCTransportRequestOptions.timeout` exists so a transport can set
|
|
69
|
+
// its own socket or request timeout, and it was never populated.
|
|
70
|
+
const races = [
|
|
71
|
+
this.transport.send(request, {
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
...(timeoutMs > 0 ? { timeout: timeoutMs } : {}),
|
|
74
|
+
}),
|
|
75
|
+
this.abortPromise(controller.signal, procedure),
|
|
76
|
+
];
|
|
77
|
+
if (timeout) {
|
|
78
|
+
races.push(timeout.promise.catch((error) => {
|
|
79
|
+
if (!controller.signal.aborted) {
|
|
80
|
+
controller.abort(error);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
const response = await Promise.race(races);
|
|
86
|
+
if (!response.success) {
|
|
87
|
+
throw this.toError(response, procedure);
|
|
88
|
+
}
|
|
89
|
+
return response.result;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
// Removing the entry here — rather than leaving it for a timer to
|
|
93
|
+
// reap — is what keeps `pendingCount` equal to the number of calls
|
|
94
|
+
// actually in flight.
|
|
95
|
+
this.pending.delete(id);
|
|
96
|
+
timeout?.cancel();
|
|
97
|
+
options.signal?.removeEventListener("abort", onCallerAbort);
|
|
98
|
+
if (!controller.signal.aborted) {
|
|
99
|
+
controller.abort(new RPCCancelledError("Call settled.", procedure));
|
|
100
|
+
}
|
|
35
101
|
}
|
|
36
|
-
return response.result;
|
|
37
102
|
}
|
|
38
103
|
/**
|
|
39
|
-
*
|
|
104
|
+
* Cancels every in-flight call and stops accepting new ones.
|
|
105
|
+
*/
|
|
106
|
+
async close(reason) {
|
|
107
|
+
this.closed = true;
|
|
108
|
+
const error = new RPCCancelledError(reason ?? "RPC client closed.");
|
|
109
|
+
for (const call of this.pending.values()) {
|
|
110
|
+
call.cancel(error);
|
|
111
|
+
}
|
|
112
|
+
this.pending.clear();
|
|
113
|
+
await this.transport.close?.();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Describes the calls currently in flight, for diagnostics.
|
|
117
|
+
*/
|
|
118
|
+
inspectPending() {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
return [...this.pending.values()].map((call) => ({
|
|
121
|
+
procedure: call.procedure,
|
|
122
|
+
elapsedMs: now - call.startedAt,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A promise that rejects when the signal aborts.
|
|
40
127
|
*/
|
|
41
|
-
|
|
42
|
-
return new Promise((
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
128
|
+
abortPromise(signal, procedure) {
|
|
129
|
+
return new Promise((_, reject) => {
|
|
130
|
+
if (signal.aborted) {
|
|
131
|
+
reject(this.abortReason(signal, procedure));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
signal.addEventListener("abort", () => reject(this.abortReason(signal, procedure)), { once: true });
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
abortReason(signal, procedure) {
|
|
138
|
+
return this.toCancellation(signal.reason, procedure);
|
|
139
|
+
}
|
|
140
|
+
cancellationError(signal, procedure) {
|
|
141
|
+
return this.toCancellation(signal?.reason, procedure);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Normalises an abort reason into an `RPCCancelledError`.
|
|
145
|
+
*
|
|
146
|
+
* A bare `controller.abort()` yields a `DOMException`, so callers that
|
|
147
|
+
* branch on error type would never see a cancellation. The original
|
|
148
|
+
* reason is preserved as `cause`; a reason that is already an RPC
|
|
149
|
+
* error is passed through untouched.
|
|
150
|
+
*/
|
|
151
|
+
toCancellation(reason, procedure) {
|
|
152
|
+
if (isRPCError(reason)) {
|
|
153
|
+
return reason;
|
|
154
|
+
}
|
|
155
|
+
const message = reason instanceof Error && reason.name !== "AbortError"
|
|
156
|
+
? reason.message
|
|
157
|
+
: "Call cancelled by caller.";
|
|
158
|
+
const error = new RPCCancelledError(message, procedure);
|
|
159
|
+
if (reason !== undefined) {
|
|
160
|
+
Object.defineProperty(error, "cause", {
|
|
161
|
+
value: reason,
|
|
162
|
+
enumerable: false,
|
|
163
|
+
configurable: true,
|
|
164
|
+
writable: true,
|
|
56
165
|
});
|
|
166
|
+
}
|
|
167
|
+
return error;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Reconstructs a typed error from an error response.
|
|
171
|
+
*
|
|
172
|
+
* The wire code drives the type, so a caller can tell an
|
|
173
|
+
* authentication failure from a timeout without string matching.
|
|
174
|
+
*/
|
|
175
|
+
toError(response, procedure) {
|
|
176
|
+
const message = response.error?.message ?? "RPC call failed.";
|
|
177
|
+
const code = response.error?.code;
|
|
178
|
+
switch (code) {
|
|
179
|
+
case "RPC_TIMEOUT": {
|
|
180
|
+
// The constructor derives its message from a duration the wire
|
|
181
|
+
// does not carry; keep the type and restore the server's message
|
|
182
|
+
// rather than reporting "timed out after 0ms".
|
|
183
|
+
const timeout = new RPCTimeoutError(0, procedure);
|
|
184
|
+
Object.defineProperty(timeout, "message", {
|
|
185
|
+
value: message,
|
|
186
|
+
enumerable: false,
|
|
187
|
+
configurable: true,
|
|
188
|
+
writable: true,
|
|
189
|
+
});
|
|
190
|
+
return timeout;
|
|
191
|
+
}
|
|
192
|
+
case "RPC_CANCELLED":
|
|
193
|
+
return new RPCCancelledError(message, procedure);
|
|
194
|
+
case "RPC_UNAVAILABLE":
|
|
195
|
+
return new RPCUnavailableError(message, procedure);
|
|
196
|
+
default:
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const error = new RPCError(message, { procedureName: procedure });
|
|
200
|
+
// Preserve the server's code and any details for callers that
|
|
201
|
+
// branch on them.
|
|
202
|
+
Object.defineProperty(error, "code", {
|
|
203
|
+
value: code ?? error.code,
|
|
204
|
+
enumerable: true,
|
|
205
|
+
configurable: true,
|
|
57
206
|
});
|
|
207
|
+
if (response.error?.details !== undefined) {
|
|
208
|
+
Object.defineProperty(error, "details", {
|
|
209
|
+
value: response.error.details,
|
|
210
|
+
enumerable: true,
|
|
211
|
+
configurable: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return error;
|
|
58
215
|
}
|
|
59
216
|
}
|
|
60
217
|
//# sourceMappingURL=rpcClient.core.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpcConstants.core.js";
|
|
1
|
+
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpcConstants.core.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpcConstants.core.js";
|
|
1
|
+
export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpcConstants.core.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -27,4 +27,22 @@ export declare const MAX_PROCEDURES = 4096;
|
|
|
27
27
|
* Procedure name pattern: dot-separated lowercase identifiers.
|
|
28
28
|
*/
|
|
29
29
|
export declare const PROCEDURE_NAME_PATTERN: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum length of a procedure name, checked before the pattern so a
|
|
32
|
+
* pathological name cannot drive regex backtracking.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAX_PROCEDURE_NAME_LENGTH = 256;
|
|
35
|
+
/**
|
|
36
|
+
* Largest delay Node's timer subsystem accepts. A larger delay overflows
|
|
37
|
+
* a signed 32-bit integer and is silently clamped to `1`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_TIMER_DELAY = 2147483647;
|
|
40
|
+
/**
|
|
41
|
+
* Message returned to callers for an unexpected server-side failure.
|
|
42
|
+
*
|
|
43
|
+
* Internal exception text may name hosts, paths, credentials or queries,
|
|
44
|
+
* so it is logged rather than returned. Callers correlate with the
|
|
45
|
+
* request id instead.
|
|
46
|
+
*/
|
|
47
|
+
export declare const INTERNAL_ERROR_MESSAGE = "The server encountered an internal error while handling this request.";
|
|
30
48
|
//# sourceMappingURL=rpcConstants.core.d.ts.map
|
|
@@ -27,4 +27,22 @@ export const MAX_PROCEDURES = 4096;
|
|
|
27
27
|
* Procedure name pattern: dot-separated lowercase identifiers.
|
|
28
28
|
*/
|
|
29
29
|
export const PROCEDURE_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum length of a procedure name, checked before the pattern so a
|
|
32
|
+
* pathological name cannot drive regex backtracking.
|
|
33
|
+
*/
|
|
34
|
+
export const MAX_PROCEDURE_NAME_LENGTH = 256;
|
|
35
|
+
/**
|
|
36
|
+
* Largest delay Node's timer subsystem accepts. A larger delay overflows
|
|
37
|
+
* a signed 32-bit integer and is silently clamped to `1`.
|
|
38
|
+
*/
|
|
39
|
+
export const MAX_TIMER_DELAY = 2_147_483_647;
|
|
40
|
+
/**
|
|
41
|
+
* Message returned to callers for an unexpected server-side failure.
|
|
42
|
+
*
|
|
43
|
+
* Internal exception text may name hosts, paths, credentials or queries,
|
|
44
|
+
* so it is logged rather than returned. Callers correlate with the
|
|
45
|
+
* request id instead.
|
|
46
|
+
*/
|
|
47
|
+
export const INTERNAL_ERROR_MESSAGE = "The server encountered an internal error while handling this request.";
|
|
30
48
|
//# sourceMappingURL=rpcConstants.core.js.map
|
|
@@ -5,7 +5,9 @@ export function createRPCContext(request, signal) {
|
|
|
5
5
|
const state = new Map();
|
|
6
6
|
const context = {
|
|
7
7
|
request,
|
|
8
|
-
metadata
|
|
8
|
+
// A frame decoded from JSON may omit `metadata`; middleware reads
|
|
9
|
+
// `context.metadata.userId` and the like without guarding.
|
|
10
|
+
metadata: request.metadata ?? {},
|
|
9
11
|
signal,
|
|
10
12
|
state,
|
|
11
13
|
get(key) {
|