@nats-kit/rpc 0.3.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/commonjs/client.d.ts +9 -0
- package/dist/commonjs/client.d.ts.map +1 -0
- package/dist/commonjs/client.js +307 -0
- package/dist/commonjs/client.js.map +1 -0
- package/dist/commonjs/contract.d.ts +59 -0
- package/dist/commonjs/contract.d.ts.map +1 -0
- package/dist/commonjs/contract.js +56 -0
- package/dist/commonjs/contract.js.map +1 -0
- package/dist/commonjs/errors.d.ts +58 -0
- package/dist/commonjs/errors.d.ts.map +1 -0
- package/dist/commonjs/errors.js +74 -0
- package/dist/commonjs/errors.js.map +1 -0
- package/dist/commonjs/eslint/index.d.ts +22 -0
- package/dist/commonjs/eslint/index.d.ts.map +1 -0
- package/dist/commonjs/eslint/index.js +33 -0
- package/dist/commonjs/eslint/index.js.map +1 -0
- package/dist/commonjs/events.d.ts +55 -0
- package/dist/commonjs/events.d.ts.map +1 -0
- package/dist/commonjs/events.js +82 -0
- package/dist/commonjs/events.js.map +1 -0
- package/dist/commonjs/headers.d.ts +53 -0
- package/dist/commonjs/headers.d.ts.map +1 -0
- package/dist/commonjs/headers.js +116 -0
- package/dist/commonjs/headers.js.map +1 -0
- package/dist/commonjs/index.d.ts +15 -0
- package/dist/commonjs/index.d.ts.map +1 -0
- package/dist/commonjs/index.js +61 -0
- package/dist/commonjs/index.js.map +1 -0
- package/dist/commonjs/kv.d.ts +13 -0
- package/dist/commonjs/kv.d.ts.map +1 -0
- package/dist/commonjs/kv.js +23 -0
- package/dist/commonjs/kv.js.map +1 -0
- package/dist/commonjs/package.json +4 -0
- package/dist/commonjs/server.d.ts +45 -0
- package/dist/commonjs/server.d.ts.map +1 -0
- package/dist/commonjs/server.js +433 -0
- package/dist/commonjs/server.js.map +1 -0
- package/dist/commonjs/types.d.ts +100 -0
- package/dist/commonjs/types.d.ts.map +1 -0
- package/dist/commonjs/types.js +3 -0
- package/dist/commonjs/types.js.map +1 -0
- package/dist/esm/client.d.ts +9 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +304 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/contract.d.ts +59 -0
- package/dist/esm/contract.d.ts.map +1 -0
- package/dist/esm/contract.js +48 -0
- package/dist/esm/contract.js.map +1 -0
- package/dist/esm/errors.d.ts +58 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +67 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/eslint/index.d.ts +22 -0
- package/dist/esm/eslint/index.d.ts.map +1 -0
- package/dist/esm/eslint/index.js +30 -0
- package/dist/esm/eslint/index.js.map +1 -0
- package/dist/esm/events.d.ts +55 -0
- package/dist/esm/events.d.ts.map +1 -0
- package/dist/esm/events.js +77 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/headers.d.ts +53 -0
- package/dist/esm/headers.d.ts.map +1 -0
- package/dist/esm/headers.js +108 -0
- package/dist/esm/headers.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/kv.d.ts +13 -0
- package/dist/esm/kv.d.ts.map +1 -0
- package/dist/esm/kv.js +20 -0
- package/dist/esm/kv.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/server.d.ts +45 -0
- package/dist/esm/server.d.ts.map +1 -0
- package/dist/esm/server.js +426 -0
- package/dist/esm/server.js.map +1 -0
- package/dist/esm/types.d.ts +100 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +103 -0
- package/src/client.ts +377 -0
- package/src/contract.ts +105 -0
- package/src/errors.ts +100 -0
- package/src/eslint/index.ts +30 -0
- package/src/events.ts +139 -0
- package/src/headers.ts +147 -0
- package/src/index.ts +76 -0
- package/src/kv.ts +33 -0
- package/src/server.ts +500 -0
- package/src/types.ts +119 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @nats-kit/rpc
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add `@nats-kit/rpc`: typed request/reply RPC over NATS, built on
|
|
8
|
+
`@nats-io/services` (ADR-32) with a reconnect supervisor, zod contracts,
|
|
9
|
+
and error normalisation per the new `docs/spec/nats-kit-wire-spec.md`.
|
|
10
|
+
|
|
11
|
+
`@nats-kit/nestjs` adds `RpcService` (server-side handler discovery via
|
|
12
|
+
`@RpcController` / `@RpcHandler`), `RpcClientModule.forFeature` +
|
|
13
|
+
`@InjectRpcClient`, `NatsService.registerBeforeStop` for graceful RPC
|
|
14
|
+
drain on shutdown, and re-exports the full `@nats-kit/rpc` surface.
|
|
15
|
+
|
|
16
|
+
`@nats-kit/core`'s `NatsTelemetry` seam gains optional `onRpcRequest` /
|
|
17
|
+
`onRpcResponse` / `onRpcHandled` / `onRpcServiceStarted` /
|
|
18
|
+
`onRpcServiceStopped` hooks — purely additive, no behavior change for
|
|
19
|
+
existing consumers.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- @nats-kit/core@0.3.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anatoly Tarnavsky
|
|
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
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# `@nats-kit/rpc`
|
|
2
|
+
|
|
3
|
+
Typed request/reply RPC over NATS: zod contracts, an ADR-32
|
|
4
|
+
[`@nats-io/services`](https://www.npmjs.com/package/@nats-io/services)
|
|
5
|
+
server/client, and a reconnect supervisor. Built on
|
|
6
|
+
[`@nats-kit/core`](https://www.npmjs.com/package/@nats-kit/core)'s
|
|
7
|
+
`NatsConnectionLike` seam, so it works with the bare
|
|
8
|
+
`NatsConnectionRunner` or the [`@nats-kit/nestjs`](https://www.npmjs.com/package/@nats-kit/nestjs)
|
|
9
|
+
adapter's `NatsService`.
|
|
10
|
+
|
|
11
|
+
The full wire spec this package implements lives at
|
|
12
|
+
[`docs/spec/nats-kit-wire-spec.md`](../../docs/spec/nats-kit-wire-spec.md) —
|
|
13
|
+
implementable from that document alone, in any language.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @nats-kit/rpc
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
(`@nats-kit/core` and `zod` are regular dependencies of `@nats-kit/rpc`, so
|
|
22
|
+
npm installs them automatically.)
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { defineService, method, createRpcClient, createRpcServer, ok, fail } from "@nats-kit/rpc";
|
|
29
|
+
|
|
30
|
+
export const SandboxContract = defineService({
|
|
31
|
+
app: "anki",
|
|
32
|
+
service: "sandbox",
|
|
33
|
+
version: "1.0.0",
|
|
34
|
+
methods: {
|
|
35
|
+
"get-session": method({
|
|
36
|
+
input: z.object({ sessionId: z.uuid() }),
|
|
37
|
+
output: z.object({ cards: z.array(z.string()) }),
|
|
38
|
+
errors: ["NOT_FOUND"],
|
|
39
|
+
timeoutMs: 5_000,
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Server
|
|
45
|
+
const server = createRpcServer(SandboxContract, {
|
|
46
|
+
nats: runner, // a NatsConnectionRunner or NestJS NatsService
|
|
47
|
+
signal: abortController.signal,
|
|
48
|
+
handlers: {
|
|
49
|
+
"get-session": async (input, ctx) => {
|
|
50
|
+
const session = await repo.find(input.sessionId, ctx.caller.id);
|
|
51
|
+
return session ? ok({ cards: session.cards }) : fail("NOT_FOUND", "no such session");
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Client
|
|
57
|
+
const client = createRpcClient(SandboxContract, { nats: runner });
|
|
58
|
+
const { cards } = await client["get-session"]({ sessionId: "..." });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The client resolves with the parsed `output` on success and rejects with
|
|
62
|
+
`RpcError` in every other case (`isRpcError`, `isDomainError`,
|
|
63
|
+
`isTransportError` narrow it). See the wire spec for the exact header set,
|
|
64
|
+
error taxonomy, and dispatch order.
|
|
65
|
+
|
|
66
|
+
## ESLint boundary rules
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// eslint.config.js
|
|
70
|
+
import { rpcBoundaryRules } from "@nats-kit/rpc/eslint";
|
|
71
|
+
export default [{ files: ["src/**/*.ts"], rules: { ...rpcBoundaryRules } }];
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A lint-level nudge (not a guarantee) against bypassing the RPC layer with raw
|
|
75
|
+
`nc.request()` / `msg.respond()` / `msg.respondError()`.
|
|
76
|
+
|
|
77
|
+
## Known deviations from the design doc
|
|
78
|
+
|
|
79
|
+
- **`nats micro stats` `num_errors` does not increment for this package's
|
|
80
|
+
service errors.** ADR-32's own error counting (`@nats-io/services` and the
|
|
81
|
+
Go `micro` package alike) only fires when an endpoint handler throws
|
|
82
|
+
*synchronously*; this package's handler is `async` so it can build a
|
|
83
|
+
wire-accurate `{ok:false,...}` / ADR-32 response with a JSON `details`
|
|
84
|
+
body, and an async rejection never reaches that synchronous catch. This is
|
|
85
|
+
intentional (kept, not fixed) — every response this server sends is still
|
|
86
|
+
wire-correct (right status code, right headers, right body); only the
|
|
87
|
+
built-in `nats micro stats` counter under-reports. Don't rely on
|
|
88
|
+
`num_errors` for this service's error rate — use `onRpcHandled`/telemetry
|
|
89
|
+
instead.
|
|
90
|
+
- **A handler throw never puts the real error message on the wire.** The 500
|
|
91
|
+
response always carries the generic `Nats-Service-Error: internal error`;
|
|
92
|
+
the actual error is logged server-side (via the configured `logger`, with
|
|
93
|
+
`requestId` + `subject`) instead. This avoids leaking server-internal
|
|
94
|
+
detail (stack traces, DB errors, etc.) to the caller.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ServiceContract } from "./contract.js";
|
|
2
|
+
import { type RpcClient, type RpcClientOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a typed `RpcClient<C>` — one method per contract entry, request
|
|
5
|
+
* headers per §4.2, and error normalisation per §4.6. Rejects with
|
|
6
|
+
* {@link RpcError} in every non-success case; never leaks `{ok:false}`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createRpcClient<C extends ServiceContract>(contract: C, options: RpcClientOptions): RpcClient<C>;
|
|
9
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,eAAe,CAAC;AAmBpE,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAsUpB;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,gBAAgB,GACxB,SAAS,CAAC,CAAC,CAAC,CAMd"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRpcClient = createRpcClient;
|
|
4
|
+
const nuid_1 = require("@nats-io/nuid");
|
|
5
|
+
const nats_core_1 = require("@nats-io/nats-core");
|
|
6
|
+
const core_1 = require("@nats-kit/core");
|
|
7
|
+
const contract_js_1 = require("./contract.js");
|
|
8
|
+
const errors_js_1 = require("./errors.js");
|
|
9
|
+
const headers_js_1 = require("./headers.js");
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
|
+
const RESERVED_HEADER_NAMES = new Set([
|
|
12
|
+
headers_js_1.HEADER_REQUEST_ID,
|
|
13
|
+
headers_js_1.HEADER_CALLER_KIND,
|
|
14
|
+
headers_js_1.HEADER_CALLER_ID,
|
|
15
|
+
headers_js_1.HEADER_CALLER_ROLE,
|
|
16
|
+
headers_js_1.HEADER_DEADLINE,
|
|
17
|
+
headers_js_1.HEADER_CONTENT_TYPE,
|
|
18
|
+
headers_js_1.HEADER_TRACEPARENT,
|
|
19
|
+
headers_js_1.HEADER_TRACESTATE,
|
|
20
|
+
].map((name) => name.toLowerCase()));
|
|
21
|
+
function assertAllowedExtraHeaders(extra) {
|
|
22
|
+
if (!extra)
|
|
23
|
+
return;
|
|
24
|
+
for (const name of Object.keys(extra)) {
|
|
25
|
+
if (name.toLowerCase().startsWith("nats-")) {
|
|
26
|
+
throw new Error(`createRpcClient: cannot set a "Nats-*" header ("${name}")`);
|
|
27
|
+
}
|
|
28
|
+
if (RESERVED_HEADER_NAMES.has(name.toLowerCase())) {
|
|
29
|
+
throw new Error(`createRpcClient: "${name}" is set by the client, not overridable`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a JSON response body, tolerating an empty buffer (§4.5: service
|
|
35
|
+
* errors MAY have no body at all — uncaught handler throws emit one).
|
|
36
|
+
*/
|
|
37
|
+
function tryParseJson(data) {
|
|
38
|
+
if (data.length === 0)
|
|
39
|
+
return { ok: false };
|
|
40
|
+
try {
|
|
41
|
+
return { ok: true, value: JSON.parse(new TextDecoder().decode(data)) };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { ok: false };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Build the `createRpcClient` implementation for a single contract method. */
|
|
48
|
+
function buildMethodCaller(contract, methodName, options) {
|
|
49
|
+
const methodDef = contract.methods[methodName];
|
|
50
|
+
if (!methodDef) {
|
|
51
|
+
throw new Error(`createRpcClient: unknown method "${methodName}"`);
|
|
52
|
+
}
|
|
53
|
+
const subject = (0, contract_js_1.methodSubject)(contract, methodName);
|
|
54
|
+
const validateResponses = options.validateResponses ?? true;
|
|
55
|
+
const logger = options.logger;
|
|
56
|
+
const telemetry = options.telemetry ?? core_1.noopTelemetry;
|
|
57
|
+
return async (input, callOpts = {}) => {
|
|
58
|
+
assertAllowedExtraHeaders(callOpts.headers);
|
|
59
|
+
const requestId = callOpts.requestId ?? nuid_1.nuid.next();
|
|
60
|
+
const start = Date.now();
|
|
61
|
+
let outcome = "ok";
|
|
62
|
+
try {
|
|
63
|
+
telemetry.onRpcRequest?.(subject, requestId);
|
|
64
|
+
const parsedInputResult = methodDef.input.safeParse(input);
|
|
65
|
+
if (!parsedInputResult.success) {
|
|
66
|
+
outcome = "service-error";
|
|
67
|
+
throw new errors_js_1.RpcError({
|
|
68
|
+
kind: "service",
|
|
69
|
+
code: errors_js_1.SERVICE_ERROR_CODES.BAD_REQUEST,
|
|
70
|
+
message: "input failed local schema validation",
|
|
71
|
+
details: parsedInputResult.error.issues,
|
|
72
|
+
subject,
|
|
73
|
+
requestId,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const parsedInput = parsedInputResult.data;
|
|
77
|
+
const caller = callOpts.caller ?? options.caller ?? { kind: "service" };
|
|
78
|
+
const timeoutMs = callOpts.timeoutMs ?? methodDef.timeoutMs ?? options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
79
|
+
const deadlineMs = callOpts.deadlineMs ?? Date.now() + timeoutMs;
|
|
80
|
+
const reqHeaders = (0, headers_js_1.buildRequestHeaders)({
|
|
81
|
+
requestId,
|
|
82
|
+
caller,
|
|
83
|
+
deadlineMs,
|
|
84
|
+
traceparent: callOpts.traceparent,
|
|
85
|
+
extra: callOpts.headers,
|
|
86
|
+
});
|
|
87
|
+
const body = new TextEncoder().encode(JSON.stringify(parsedInput));
|
|
88
|
+
await options.nats.waitForReady();
|
|
89
|
+
const nc = options.nats.getConnection();
|
|
90
|
+
let reply;
|
|
91
|
+
try {
|
|
92
|
+
const requestPromise = nc.request(subject, body, {
|
|
93
|
+
timeout: timeoutMs,
|
|
94
|
+
headers: reqHeaders,
|
|
95
|
+
});
|
|
96
|
+
reply = callOpts.signal
|
|
97
|
+
? await raceWithSignal(requestPromise, callOpts.signal)
|
|
98
|
+
: await requestPromise;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw toTransportError(error, subject, requestId);
|
|
102
|
+
}
|
|
103
|
+
const svcErrorCode = reply.headers?.get("Nats-Service-Error-Code", nats_core_1.Match.IgnoreCase);
|
|
104
|
+
if (svcErrorCode) {
|
|
105
|
+
outcome = "service-error";
|
|
106
|
+
const status = Number(svcErrorCode);
|
|
107
|
+
const description = reply.headers?.get("Nats-Service-Error", nats_core_1.Match.IgnoreCase) || "service error";
|
|
108
|
+
const parsedBody = tryParseJson(reply.data);
|
|
109
|
+
const details = parsedBody.ok && typeof parsedBody.value === "object" && parsedBody.value !== null
|
|
110
|
+
? parsedBody.value.details
|
|
111
|
+
: undefined;
|
|
112
|
+
throw new errors_js_1.RpcError({
|
|
113
|
+
kind: "service",
|
|
114
|
+
code: errors_js_1.SERVICE_CODE_BY_STATUS[status] ?? errors_js_1.SERVICE_ERROR_CODES.SERVICE_ERROR,
|
|
115
|
+
message: description,
|
|
116
|
+
serviceCode: status,
|
|
117
|
+
details,
|
|
118
|
+
subject,
|
|
119
|
+
requestId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const parsedBody = tryParseJson(reply.data);
|
|
123
|
+
if (!parsedBody.ok || typeof parsedBody.value !== "object" || parsedBody.value === null) {
|
|
124
|
+
outcome = "service-error";
|
|
125
|
+
throw new errors_js_1.RpcError({
|
|
126
|
+
kind: "service",
|
|
127
|
+
code: errors_js_1.SERVICE_ERROR_CODES.MALFORMED_REPLY,
|
|
128
|
+
message: "reply body was not valid JSON",
|
|
129
|
+
subject,
|
|
130
|
+
requestId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const envelope = parsedBody.value;
|
|
134
|
+
if (envelope.ok === false) {
|
|
135
|
+
outcome = "domain-error";
|
|
136
|
+
throw new errors_js_1.RpcError({
|
|
137
|
+
kind: "domain",
|
|
138
|
+
code: envelope.error?.code ?? "UNKNOWN",
|
|
139
|
+
message: envelope.error?.message ?? "domain error",
|
|
140
|
+
details: envelope.error?.details,
|
|
141
|
+
subject,
|
|
142
|
+
requestId,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (envelope.ok !== true) {
|
|
146
|
+
outcome = "service-error";
|
|
147
|
+
throw new errors_js_1.RpcError({
|
|
148
|
+
kind: "service",
|
|
149
|
+
code: errors_js_1.SERVICE_ERROR_CODES.MALFORMED_REPLY,
|
|
150
|
+
message: 'reply body missing "ok"',
|
|
151
|
+
subject,
|
|
152
|
+
requestId,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (!validateResponses) {
|
|
156
|
+
return envelope.result;
|
|
157
|
+
}
|
|
158
|
+
// Wire spec §4.3: a method with no output sends `"result": null` on
|
|
159
|
+
// the wire, but `z.void()`/`z.undefined()` only accept `undefined` —
|
|
160
|
+
// retry with `undefined` before treating a `null` result as malformed.
|
|
161
|
+
let parsedOutput = methodDef.output.safeParse(envelope.result);
|
|
162
|
+
if (!parsedOutput.success && envelope.result === null) {
|
|
163
|
+
parsedOutput = methodDef.output.safeParse(undefined);
|
|
164
|
+
}
|
|
165
|
+
if (!parsedOutput.success) {
|
|
166
|
+
outcome = "service-error";
|
|
167
|
+
throw new errors_js_1.RpcError({
|
|
168
|
+
kind: "service",
|
|
169
|
+
code: errors_js_1.SERVICE_ERROR_CODES.MALFORMED_REPLY,
|
|
170
|
+
message: "reply result failed output schema validation",
|
|
171
|
+
details: parsedOutput.error.issues,
|
|
172
|
+
subject,
|
|
173
|
+
requestId,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return parsedOutput.data;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// Transport errors (timeout, no-responders, connection-lost, aborted)
|
|
180
|
+
// never flow through the `outcome = "..."` assignments above — derive
|
|
181
|
+
// it here from the normalised `RpcError` before it's telemetered.
|
|
182
|
+
if (outcome === "ok" && (0, errors_js_1.isRpcError)(error)) {
|
|
183
|
+
if (error.kind === "transport") {
|
|
184
|
+
outcome =
|
|
185
|
+
error.code === errors_js_1.TRANSPORT_ERROR_CODES.TIMEOUT
|
|
186
|
+
? "timeout"
|
|
187
|
+
: error.code === errors_js_1.TRANSPORT_ERROR_CODES.NO_RESPONDERS
|
|
188
|
+
? "no-responders"
|
|
189
|
+
: error.code === errors_js_1.TRANSPORT_ERROR_CODES.ABORTED
|
|
190
|
+
? "aborted"
|
|
191
|
+
: error.code === errors_js_1.TRANSPORT_ERROR_CODES.CONNECTION_LOST
|
|
192
|
+
? "connection-lost"
|
|
193
|
+
: "service-error";
|
|
194
|
+
}
|
|
195
|
+
else if (error.kind === "domain") {
|
|
196
|
+
outcome = "domain-error";
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
outcome = "service-error";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
logger?.debug?.({ subject, requestId, outcome, durationMs: Date.now() - start }, "RPC call completed");
|
|
206
|
+
telemetry.onRpcResponse?.(subject, outcome, Date.now() - start);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function toTransportError(error, subject, requestId) {
|
|
211
|
+
// `raceWithSignal` already builds a fully-formed `RpcError` (kind:
|
|
212
|
+
// "transport", code: ABORTED) when the caller's signal fires — pass it
|
|
213
|
+
// through unchanged instead of reclassifying it as CONNECTION_LOST.
|
|
214
|
+
if ((0, errors_js_1.isRpcError)(error))
|
|
215
|
+
return error;
|
|
216
|
+
if (error instanceof core_1.RequestError) {
|
|
217
|
+
if (error.isNoResponders() || error.cause instanceof core_1.NoRespondersError) {
|
|
218
|
+
return new errors_js_1.RpcError({
|
|
219
|
+
kind: "transport",
|
|
220
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.NO_RESPONDERS,
|
|
221
|
+
message: "no responders for subject",
|
|
222
|
+
subject,
|
|
223
|
+
requestId,
|
|
224
|
+
cause: error,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (error.cause instanceof core_1.TimeoutError) {
|
|
228
|
+
return new errors_js_1.RpcError({
|
|
229
|
+
kind: "transport",
|
|
230
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.TIMEOUT,
|
|
231
|
+
message: "request timed out",
|
|
232
|
+
subject,
|
|
233
|
+
requestId,
|
|
234
|
+
cause: error,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (error instanceof core_1.TimeoutError) {
|
|
239
|
+
return new errors_js_1.RpcError({
|
|
240
|
+
kind: "transport",
|
|
241
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.TIMEOUT,
|
|
242
|
+
message: "request timed out",
|
|
243
|
+
subject,
|
|
244
|
+
requestId,
|
|
245
|
+
cause: error,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (error instanceof core_1.NoRespondersError) {
|
|
249
|
+
return new errors_js_1.RpcError({
|
|
250
|
+
kind: "transport",
|
|
251
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.NO_RESPONDERS,
|
|
252
|
+
message: "no responders for subject",
|
|
253
|
+
subject,
|
|
254
|
+
requestId,
|
|
255
|
+
cause: error,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (error instanceof core_1.ClosedConnectionError || error instanceof core_1.ConnectionError) {
|
|
259
|
+
return new errors_js_1.RpcError({
|
|
260
|
+
kind: "transport",
|
|
261
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.CONNECTION_LOST,
|
|
262
|
+
message: "connection closed or lost",
|
|
263
|
+
subject,
|
|
264
|
+
requestId,
|
|
265
|
+
cause: error,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return new errors_js_1.RpcError({
|
|
269
|
+
kind: "transport",
|
|
270
|
+
code: errors_js_1.TRANSPORT_ERROR_CODES.CONNECTION_LOST,
|
|
271
|
+
message: error instanceof Error ? error.message : "request failed",
|
|
272
|
+
subject,
|
|
273
|
+
requestId,
|
|
274
|
+
cause: error,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function raceWithSignal(promise, signal) {
|
|
278
|
+
if (signal.aborted) {
|
|
279
|
+
return Promise.reject(new errors_js_1.RpcError({ kind: "transport", code: errors_js_1.TRANSPORT_ERROR_CODES.ABORTED, message: "aborted" }));
|
|
280
|
+
}
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const onAbort = () => {
|
|
283
|
+
reject(new errors_js_1.RpcError({ kind: "transport", code: errors_js_1.TRANSPORT_ERROR_CODES.ABORTED, message: "aborted" }));
|
|
284
|
+
};
|
|
285
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
286
|
+
promise.then((value) => {
|
|
287
|
+
signal.removeEventListener("abort", onAbort);
|
|
288
|
+
resolve(value);
|
|
289
|
+
}, (error) => {
|
|
290
|
+
signal.removeEventListener("abort", onAbort);
|
|
291
|
+
reject(error);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Build a typed `RpcClient<C>` — one method per contract entry, request
|
|
297
|
+
* headers per §4.2, and error normalisation per §4.6. Rejects with
|
|
298
|
+
* {@link RpcError} in every non-success case; never leaks `{ok:false}`.
|
|
299
|
+
*/
|
|
300
|
+
function createRpcClient(contract, options) {
|
|
301
|
+
const client = {};
|
|
302
|
+
for (const methodName of Object.keys(contract.methods)) {
|
|
303
|
+
client[methodName] = buildMethodCaller(contract, methodName, options);
|
|
304
|
+
}
|
|
305
|
+
return client;
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":";;AA+WA,0CASC;AAxXD,wCAAqC;AACrC,kDAA2C;AAC3C,yCAQwB;AAExB,+CAAoE;AACpE,2CAMqB;AACrB,6CAUsB;AAQtB,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CACnC;IACE,8BAAiB;IACjB,+BAAkB;IAClB,6BAAgB;IAChB,+BAAkB;IAClB,4BAAe;IACf,gCAAmB;IACnB,+BAAkB;IAClB,8BAAiB;CAClB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CACpC,CAAC;AAEF,SAAS,yBAAyB,CAAC,KAAyC;IAC1E,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,mDAAmD,IAAI,IAAI,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,yCAAyC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,IAAgB;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,iBAAiB,CACxB,QAAW,EACX,UAAkB,EAClB,OAAyB;IAEzB,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,oCAAoC,UAAU,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,OAAO,GAAG,IAAA,2BAAa,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,IAAI,CAAC;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,oBAAa,CAAC;IAErD,OAAO,KAAK,EAAE,KAAc,EAAE,WAA2B,EAAE,EAAoB,EAAE;QAC/E,yBAAyB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAE5C,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,WAAI,CAAC,IAAI,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,OAAO,GAOa,IAAI,CAAC;QAE7B,IAAI,CAAC;YACH,SAAS,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAE7C,MAAM,iBAAiB,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC3D,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;gBAC/B,OAAO,GAAG,eAAe,CAAC;gBAC1B,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,+BAAmB,CAAC,WAAW;oBACrC,OAAO,EAAE,sCAAsC;oBAC/C,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,MAAM;oBACvC,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC;YAC3C,MAAM,MAAM,GAAc,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YACnF,MAAM,SAAS,GACb,QAAQ,CAAC,SAAS,IAAI,SAAS,CAAC,SAAS,IAAI,OAAO,CAAC,gBAAgB,IAAI,kBAAkB,CAAC;YAC9F,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAEjE,MAAM,UAAU,GAAY,IAAA,gCAAmB,EAAC;gBAC9C,SAAS;gBACT,MAAM;gBACN,UAAU;gBACV,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,KAAK,EAAE,QAAQ,CAAC,OAAO;aACxB,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;YAEnE,MAAM,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAExC,IAAI,KAA8C,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,cAAc,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE;oBAC/C,OAAO,EAAE,SAAS;oBAClB,OAAO,EAAE,UAAU;iBACpB,CAAC,CAAC;gBACH,KAAK,GAAG,QAAQ,CAAC,MAAM;oBACrB,CAAC,CAAC,MAAM,cAAc,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC;oBACvD,CAAC,CAAC,MAAM,cAAc,CAAC;YAC3B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;YACpD,CAAC;YAED,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,yBAAyB,EAAE,iBAAK,CAAC,UAAU,CAAC,CAAC;YACrF,IAAI,YAAY,EAAE,CAAC;gBACjB,OAAO,GAAG,eAAe,CAAC;gBAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;gBACpC,MAAM,WAAW,GACf,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,oBAAoB,EAAE,iBAAK,CAAC,UAAU,CAAC,IAAI,eAAe,CAAC;gBAChF,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC5C,MAAM,OAAO,GACX,UAAU,CAAC,EAAE,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI;oBAChF,CAAC,CAAE,UAAU,CAAC,KAA+B,CAAC,OAAO;oBACrD,CAAC,CAAC,SAAS,CAAC;gBAChB,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,kCAAsB,CAAC,MAAM,CAAC,IAAI,+BAAmB,CAAC,aAAa;oBACzE,OAAO,EAAE,WAAW;oBACpB,WAAW,EAAE,MAAM;oBACnB,OAAO;oBACP,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACxF,OAAO,GAAG,eAAe,CAAC;gBAC1B,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,+BAAmB,CAAC,eAAe;oBACzC,OAAO,EAAE,+BAA+B;oBACxC,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAI3B,CAAC;YAEF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,OAAO,GAAG,cAAc,CAAC;gBACzB,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,IAAI,SAAS;oBACvC,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,cAAc;oBAClD,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO;oBAChC,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YAED,IAAI,QAAQ,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBACzB,OAAO,GAAG,eAAe,CAAC;gBAC1B,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,+BAAmB,CAAC,eAAe;oBACzC,OAAO,EAAE,yBAAyB;oBAClC,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,OAAO,QAAQ,CAAC,MAAM,CAAC;YACzB,CAAC;YAED,oEAAoE;YACpE,qEAAqE;YACrE,uEAAuE;YACvE,IAAI,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBACtD,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBAC1B,OAAO,GAAG,eAAe,CAAC;gBAC1B,MAAM,IAAI,oBAAQ,CAAC;oBACjB,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,+BAAmB,CAAC,eAAe;oBACzC,OAAO,EAAE,8CAA8C;oBACvD,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM;oBAClC,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;YAED,OAAO,YAAY,CAAC,IAAI,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,sEAAsE;YACtE,sEAAsE;YACtE,kEAAkE;YAClE,IAAI,OAAO,KAAK,IAAI,IAAI,IAAA,sBAAU,EAAC,KAAK,CAAC,EAAE,CAAC;gBAC1C,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC/B,OAAO;wBACL,KAAK,CAAC,IAAI,KAAK,iCAAqB,CAAC,OAAO;4BAC1C,CAAC,CAAC,SAAS;4BACX,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,iCAAqB,CAAC,aAAa;gCAClD,CAAC,CAAC,eAAe;gCACjB,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,iCAAqB,CAAC,OAAO;oCAC5C,CAAC,CAAC,SAAS;oCACX,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,iCAAqB,CAAC,eAAe;wCACpD,CAAC,CAAC,iBAAiB;wCACnB,CAAC,CAAC,eAAe,CAAC;gBAC9B,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACnC,OAAO,GAAG,cAAc,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,OAAO,GAAG,eAAe,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,KAAK,EAAE,CACb,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,EAC/D,oBAAoB,CACrB,CAAC;YACF,SAAS,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc,EAAE,OAAe,EAAE,SAAiB;IAC1E,mEAAmE;IACnE,uEAAuE;IACvE,oEAAoE;IACpE,IAAI,IAAA,sBAAU,EAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,KAAK,YAAY,mBAAY,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,KAAK,CAAC,KAAK,YAAY,wBAAiB,EAAE,CAAC;YACvE,OAAO,IAAI,oBAAQ,CAAC;gBAClB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,iCAAqB,CAAC,aAAa;gBACzC,OAAO,EAAE,2BAA2B;gBACpC,OAAO;gBACP,SAAS;gBACT,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,YAAY,mBAAY,EAAE,CAAC;YACxC,OAAO,IAAI,oBAAQ,CAAC;gBAClB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,iCAAqB,CAAC,OAAO;gBACnC,OAAO,EAAE,mBAAmB;gBAC5B,OAAO;gBACP,SAAS;gBACT,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,KAAK,YAAY,mBAAY,EAAE,CAAC;QAClC,OAAO,IAAI,oBAAQ,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,iCAAqB,CAAC,OAAO;YACnC,OAAO,EAAE,mBAAmB;YAC5B,OAAO;YACP,SAAS;YACT,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,YAAY,wBAAiB,EAAE,CAAC;QACvC,OAAO,IAAI,oBAAQ,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,iCAAqB,CAAC,aAAa;YACzC,OAAO,EAAE,2BAA2B;YACpC,OAAO;YACP,SAAS;YACT,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,YAAY,4BAAqB,IAAI,KAAK,YAAY,sBAAe,EAAE,CAAC;QAC/E,OAAO,IAAI,oBAAQ,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,iCAAqB,CAAC,eAAe;YAC3C,OAAO,EAAE,2BAA2B;YACpC,OAAO;YACP,SAAS;YACT,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,oBAAQ,CAAC;QAClB,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,iCAAqB,CAAC,eAAe;QAC3C,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB;QAClE,OAAO;QACP,SAAS;QACT,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAI,OAAmB,EAAE,MAAmB;IACjE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,oBAAQ,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,iCAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACtH,CAAC;IACD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,IAAI,oBAAQ,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,iCAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACvG,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CACV,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YACjB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAC7B,QAAW,EACX,OAAyB;IAEzB,MAAM,MAAM,GAAgF,EAAE,CAAC;IAC/F,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,UAAU,CAAC,GAAG,iBAAiB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,MAAsB,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type z } from "zod";
|
|
2
|
+
/** Segment grammar shared by `app`, `service` and every method name (§4.1). */
|
|
3
|
+
export declare const SUBJECT_SEGMENT_RE: RegExp;
|
|
4
|
+
/**
|
|
5
|
+
* Definition of a single RPC method within a {@link ServiceContract}.
|
|
6
|
+
*
|
|
7
|
+
* See design §2.1. `errors` is a closed set of domain error codes the method
|
|
8
|
+
* may return via `fail()` — kept as a literal tuple by {@link method} so
|
|
9
|
+
* `RpcHandlers<C>` and `isDomainError` can narrow against it.
|
|
10
|
+
*/
|
|
11
|
+
export interface RpcMethodDef<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType, E extends readonly string[] = readonly string[]> {
|
|
12
|
+
input: I;
|
|
13
|
+
output: O;
|
|
14
|
+
/** Closed set of domain error codes this method may return. */
|
|
15
|
+
errors?: E;
|
|
16
|
+
/**
|
|
17
|
+
* Roles the caller must hold. Empty/absent means no role requirement.
|
|
18
|
+
* Enforced by the library BEFORE dispatch: a caller missing any of these
|
|
19
|
+
* gets a 403 service error and the handler is never invoked.
|
|
20
|
+
*/
|
|
21
|
+
requiredRoles?: readonly string[];
|
|
22
|
+
/** Default client timeout for this method. Falls back to 10_000. */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/** Free-form endpoint metadata; surfaces in `nats micro info`. */
|
|
25
|
+
metadata?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Identity helper that preserves the literal tuple type of `errors` — a bare
|
|
29
|
+
* object literal would widen `errors: ["NOT_FOUND"]` to `string[]`, which
|
|
30
|
+
* would flatten every domain error union to `string` throughout the contract.
|
|
31
|
+
*/
|
|
32
|
+
export declare function method<I extends z.ZodType, O extends z.ZodType, const E extends readonly string[] = readonly []>(def: RpcMethodDef<I, O, E>): RpcMethodDef<I, O, E>;
|
|
33
|
+
/** A named collection of RPC methods, addressed at `<app>.<service>.<method>` (§2.1, §4.1). */
|
|
34
|
+
export interface ServiceContract<M extends Record<string, RpcMethodDef> = Record<string, RpcMethodDef>> {
|
|
35
|
+
/** Subject segment 1. `[a-z0-9-]+`. */
|
|
36
|
+
app: string;
|
|
37
|
+
/** Subject segment 2. `[a-z0-9-]+`. */
|
|
38
|
+
service: string;
|
|
39
|
+
/** SemVer — `@nats-io/services` rejects anything else (`parseSemVer`). */
|
|
40
|
+
version: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
metadata?: Record<string, string>;
|
|
43
|
+
/** Keys are method names; each must be `[a-z0-9-]+`. */
|
|
44
|
+
methods: M;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate and return a {@link ServiceContract}. Throws synchronously on a
|
|
48
|
+
* malformed `app`/`service`/`version`/method name — a contract is authored
|
|
49
|
+
* once and imported everywhere, so failing fast at module-load time is
|
|
50
|
+
* preferable to a runtime surprise deep inside `createRpcServer`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function defineService<const M extends Record<string, RpcMethodDef>>(def: ServiceContract<M>): ServiceContract<M>;
|
|
53
|
+
/** The subject a method is served/called on: `<app>.<service>.<method>` (§4.1). */
|
|
54
|
+
export declare function methodSubject(contract: ServiceContract, methodName: string): string;
|
|
55
|
+
/** The micro service `name` (dots are rejected by `Svcm.add`, so `-` joins app/service). */
|
|
56
|
+
export declare function serviceName(contract: ServiceContract): string;
|
|
57
|
+
/** The group subject prefix every endpoint is registered under. */
|
|
58
|
+
export declare function groupSubject(contract: ServiceContract): string;
|
|
59
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,QAAoC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,WAAW,YAAY,CAC3B,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,EAC/B,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,EAC/B,CAAC,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE;IAE/C,KAAK,EAAE,CAAC,CAAC;IACT,MAAM,EAAE,CAAC,CAAC;IACV,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,CAAC;IACX;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CACpB,CAAC,SAAS,CAAC,CAAC,OAAO,EACnB,CAAC,SAAS,CAAC,CAAC,OAAO,EACnB,KAAK,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,EAC/C,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAEnD;AAED,+FAA+F;AAC/F,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAErE,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,wDAAwD;IACxD,OAAO,EAAE,CAAC,CAAC;CACZ;AAKD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACxE,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,GACtB,eAAe,CAAC,CAAC,CAAC,CAgBpB;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAE7D;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUBJECT_SEGMENT_RE = void 0;
|
|
4
|
+
exports.method = method;
|
|
5
|
+
exports.defineService = defineService;
|
|
6
|
+
exports.methodSubject = methodSubject;
|
|
7
|
+
exports.serviceName = serviceName;
|
|
8
|
+
exports.groupSubject = groupSubject;
|
|
9
|
+
/** Segment grammar shared by `app`, `service` and every method name (§4.1). */
|
|
10
|
+
exports.SUBJECT_SEGMENT_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
11
|
+
/**
|
|
12
|
+
* Identity helper that preserves the literal tuple type of `errors` — a bare
|
|
13
|
+
* object literal would widen `errors: ["NOT_FOUND"]` to `string[]`, which
|
|
14
|
+
* would flatten every domain error union to `string` throughout the contract.
|
|
15
|
+
*/
|
|
16
|
+
function method(def) {
|
|
17
|
+
return def;
|
|
18
|
+
}
|
|
19
|
+
/** SemVer as required by `@nats-io/services`' `ServiceConfig.version` (`parseSemVer`). */
|
|
20
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
|
|
21
|
+
/**
|
|
22
|
+
* Validate and return a {@link ServiceContract}. Throws synchronously on a
|
|
23
|
+
* malformed `app`/`service`/`version`/method name — a contract is authored
|
|
24
|
+
* once and imported everywhere, so failing fast at module-load time is
|
|
25
|
+
* preferable to a runtime surprise deep inside `createRpcServer`.
|
|
26
|
+
*/
|
|
27
|
+
function defineService(def) {
|
|
28
|
+
if (!exports.SUBJECT_SEGMENT_RE.test(def.app)) {
|
|
29
|
+
throw new Error(`defineService: invalid app segment "${def.app}"`);
|
|
30
|
+
}
|
|
31
|
+
if (!exports.SUBJECT_SEGMENT_RE.test(def.service)) {
|
|
32
|
+
throw new Error(`defineService: invalid service segment "${def.service}"`);
|
|
33
|
+
}
|
|
34
|
+
if (!SEMVER_RE.test(def.version)) {
|
|
35
|
+
throw new Error(`defineService: invalid semver version "${def.version}"`);
|
|
36
|
+
}
|
|
37
|
+
for (const name of Object.keys(def.methods)) {
|
|
38
|
+
if (!exports.SUBJECT_SEGMENT_RE.test(name)) {
|
|
39
|
+
throw new Error(`defineService: invalid method name "${name}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return def;
|
|
43
|
+
}
|
|
44
|
+
/** The subject a method is served/called on: `<app>.<service>.<method>` (§4.1). */
|
|
45
|
+
function methodSubject(contract, methodName) {
|
|
46
|
+
return `${contract.app}.${contract.service}.${methodName}`;
|
|
47
|
+
}
|
|
48
|
+
/** The micro service `name` (dots are rejected by `Svcm.add`, so `-` joins app/service). */
|
|
49
|
+
function serviceName(contract) {
|
|
50
|
+
return `${contract.app}-${contract.service}`;
|
|
51
|
+
}
|
|
52
|
+
/** The group subject prefix every endpoint is registered under. */
|
|
53
|
+
function groupSubject(contract) {
|
|
54
|
+
return `${contract.app}.${contract.service}`;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contract.ts"],"names":[],"mappings":";;;AAsCA,wBAMC;AA2BD,sCAkBC;AAGD,sCAEC;AAGD,kCAEC;AAGD,oCAEC;AAtGD,+EAA+E;AAClE,QAAA,kBAAkB,GAAG,iCAAiC,CAAC;AA8BpE;;;;GAIG;AACH,SAAgB,MAAM,CAIpB,GAA0B;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC;AAkBD,0FAA0F;AAC1F,MAAM,SAAS,GAAG,0DAA0D,CAAC;AAE7E;;;;;GAKG;AACH,SAAgB,aAAa,CAC3B,GAAuB;IAEvB,IAAI,CAAC,0BAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,0BAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,2CAA2C,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,0CAA0C,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;IAC5E,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,0BAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,GAAG,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,mFAAmF;AACnF,SAAgB,aAAa,CAAC,QAAyB,EAAE,UAAkB;IACzE,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,OAAO,IAAI,UAAU,EAAE,CAAC;AAC7D,CAAC;AAED,4FAA4F;AAC5F,SAAgB,WAAW,CAAC,QAAyB;IACnD,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;AAC/C,CAAC;AAED,mEAAmE;AACnE,SAAgB,YAAY,CAAC,QAAyB;IACpD,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;AAC/C,CAAC"}
|