@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -0
  4. package/dist/commonjs/client.d.ts +9 -0
  5. package/dist/commonjs/client.d.ts.map +1 -0
  6. package/dist/commonjs/client.js +307 -0
  7. package/dist/commonjs/client.js.map +1 -0
  8. package/dist/commonjs/contract.d.ts +59 -0
  9. package/dist/commonjs/contract.d.ts.map +1 -0
  10. package/dist/commonjs/contract.js +56 -0
  11. package/dist/commonjs/contract.js.map +1 -0
  12. package/dist/commonjs/errors.d.ts +58 -0
  13. package/dist/commonjs/errors.d.ts.map +1 -0
  14. package/dist/commonjs/errors.js +74 -0
  15. package/dist/commonjs/errors.js.map +1 -0
  16. package/dist/commonjs/eslint/index.d.ts +22 -0
  17. package/dist/commonjs/eslint/index.d.ts.map +1 -0
  18. package/dist/commonjs/eslint/index.js +33 -0
  19. package/dist/commonjs/eslint/index.js.map +1 -0
  20. package/dist/commonjs/events.d.ts +55 -0
  21. package/dist/commonjs/events.d.ts.map +1 -0
  22. package/dist/commonjs/events.js +82 -0
  23. package/dist/commonjs/events.js.map +1 -0
  24. package/dist/commonjs/headers.d.ts +53 -0
  25. package/dist/commonjs/headers.d.ts.map +1 -0
  26. package/dist/commonjs/headers.js +116 -0
  27. package/dist/commonjs/headers.js.map +1 -0
  28. package/dist/commonjs/index.d.ts +15 -0
  29. package/dist/commonjs/index.d.ts.map +1 -0
  30. package/dist/commonjs/index.js +61 -0
  31. package/dist/commonjs/index.js.map +1 -0
  32. package/dist/commonjs/kv.d.ts +13 -0
  33. package/dist/commonjs/kv.d.ts.map +1 -0
  34. package/dist/commonjs/kv.js +23 -0
  35. package/dist/commonjs/kv.js.map +1 -0
  36. package/dist/commonjs/package.json +4 -0
  37. package/dist/commonjs/server.d.ts +45 -0
  38. package/dist/commonjs/server.d.ts.map +1 -0
  39. package/dist/commonjs/server.js +433 -0
  40. package/dist/commonjs/server.js.map +1 -0
  41. package/dist/commonjs/types.d.ts +100 -0
  42. package/dist/commonjs/types.d.ts.map +1 -0
  43. package/dist/commonjs/types.js +3 -0
  44. package/dist/commonjs/types.js.map +1 -0
  45. package/dist/esm/client.d.ts +9 -0
  46. package/dist/esm/client.d.ts.map +1 -0
  47. package/dist/esm/client.js +304 -0
  48. package/dist/esm/client.js.map +1 -0
  49. package/dist/esm/contract.d.ts +59 -0
  50. package/dist/esm/contract.d.ts.map +1 -0
  51. package/dist/esm/contract.js +48 -0
  52. package/dist/esm/contract.js.map +1 -0
  53. package/dist/esm/errors.d.ts +58 -0
  54. package/dist/esm/errors.d.ts.map +1 -0
  55. package/dist/esm/errors.js +67 -0
  56. package/dist/esm/errors.js.map +1 -0
  57. package/dist/esm/eslint/index.d.ts +22 -0
  58. package/dist/esm/eslint/index.d.ts.map +1 -0
  59. package/dist/esm/eslint/index.js +30 -0
  60. package/dist/esm/eslint/index.js.map +1 -0
  61. package/dist/esm/events.d.ts +55 -0
  62. package/dist/esm/events.d.ts.map +1 -0
  63. package/dist/esm/events.js +77 -0
  64. package/dist/esm/events.js.map +1 -0
  65. package/dist/esm/headers.d.ts +53 -0
  66. package/dist/esm/headers.d.ts.map +1 -0
  67. package/dist/esm/headers.js +108 -0
  68. package/dist/esm/headers.js.map +1 -0
  69. package/dist/esm/index.d.ts +15 -0
  70. package/dist/esm/index.d.ts.map +1 -0
  71. package/dist/esm/index.js +25 -0
  72. package/dist/esm/index.js.map +1 -0
  73. package/dist/esm/kv.d.ts +13 -0
  74. package/dist/esm/kv.d.ts.map +1 -0
  75. package/dist/esm/kv.js +20 -0
  76. package/dist/esm/kv.js.map +1 -0
  77. package/dist/esm/package.json +4 -0
  78. package/dist/esm/server.d.ts +45 -0
  79. package/dist/esm/server.d.ts.map +1 -0
  80. package/dist/esm/server.js +426 -0
  81. package/dist/esm/server.js.map +1 -0
  82. package/dist/esm/types.d.ts +100 -0
  83. package/dist/esm/types.d.ts.map +1 -0
  84. package/dist/esm/types.js +2 -0
  85. package/dist/esm/types.js.map +1 -0
  86. package/package.json +103 -0
  87. package/src/client.ts +377 -0
  88. package/src/contract.ts +105 -0
  89. package/src/errors.ts +100 -0
  90. package/src/eslint/index.ts +30 -0
  91. package/src/events.ts +139 -0
  92. package/src/headers.ts +147 -0
  93. package/src/index.ts +76 -0
  94. package/src/kv.ts +33 -0
  95. package/src/server.ts +500 -0
  96. package/src/types.ts +119 -0
@@ -0,0 +1,13 @@
1
+ import { type z } from "zod";
2
+ /** A typed KV value contract (§2.5, §4.9). No envelope; a breaking shape change means a new bucket. */
3
+ export interface KvValueContract<T extends z.ZodType> {
4
+ bucket: string;
5
+ value: T;
6
+ /** Builds a validated, dot-joined key from parts; each part must be `[a-zA-Z0-9_-]+`. */
7
+ key(...parts: string[]): string;
8
+ }
9
+ export declare function defineKvValue<T extends z.ZodType>(def: {
10
+ bucket: string;
11
+ value: T;
12
+ }): KvValueContract<T>;
13
+ //# sourceMappingURL=kv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../../src/kv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,uGAAuG;AACvG,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,CAAC;IACT,yFAAyF;IACzF,GAAG,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CACjC;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,CAAC;CACV,GAAG,eAAe,CAAC,CAAC,CAAC,CAiBrB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineKvValue = defineKvValue;
4
+ const KV_KEY_PART_RE = /^[a-zA-Z0-9_-]+$/;
5
+ function defineKvValue(def) {
6
+ return {
7
+ bucket: def.bucket,
8
+ value: def.value,
9
+ key(...parts) {
10
+ for (const part of parts) {
11
+ if (!KV_KEY_PART_RE.test(part)) {
12
+ throw new Error(`defineKvValue(${def.bucket}): invalid key part "${part}"`);
13
+ }
14
+ }
15
+ const key = parts.join(".");
16
+ if (key.length === 0 || key.length > 255) {
17
+ throw new Error(`defineKvValue(${def.bucket}): key length out of bounds`);
18
+ }
19
+ return key;
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=kv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.js","sourceRoot":"","sources":["../../src/kv.ts"],"names":[],"mappings":";;AAYA,sCAoBC;AA9BD,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAU1C,SAAgB,aAAa,CAAsB,GAGlD;IACC,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,GAAG,CAAC,GAAG,KAAe;YACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,MAAM,wBAAwB,IAAI,GAAG,CAAC,CAAC;gBAC9E,CAAC;YACH,CAAC;YACD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,MAAM,6BAA6B,CAAC,CAAC;YAC5E,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "sideEffects": false
4
+ }
@@ -0,0 +1,45 @@
1
+ import { type ServiceHandler } from "@nats-io/services";
2
+ import { type ServiceContract } from "./contract.js";
3
+ import { type RpcAuthorize, type RpcContext, type RpcHandlers, type RpcResult, type RpcServerHandle, type RpcServerOptions } from "./types.js";
4
+ /** Build a successful `RpcResult`. */
5
+ export declare function ok<T>(result: T): {
6
+ ok: true;
7
+ result: T;
8
+ };
9
+ /** Build a domain-error `RpcResult`. */
10
+ export declare function fail<E extends string>(code: E, message: string, details?: unknown): {
11
+ ok: false;
12
+ error: {
13
+ code: E;
14
+ message: string;
15
+ details?: unknown;
16
+ };
17
+ };
18
+ /**
19
+ * Build the `ServiceHandler` for one contract method — dispatch order fixed
20
+ * by design §2.4 / wire spec §4.5.
21
+ *
22
+ * The library's own synchronous try/catch around `handler(err, msg)` (see
23
+ * `ServiceImpl.setupHandler`) cannot observe an `async` handler's rejection,
24
+ * so it never counts our service errors toward `num_errors` — we build every
25
+ * response (success, domain error, service error) explicitly instead of
26
+ * throwing, which is what lets a 400/403 carry a JSON `details` body. This is
27
+ * a documented deviation from `nats micro stats` incrementing on our service
28
+ * errors; see the package README.
29
+ *
30
+ * Exported for unit tests (mock `ServiceMsg` + assert the dispatch order) and
31
+ * for advanced embedding over a transport other than `@nats-io/services`'
32
+ * `ServiceHandler`. Not part of the documented public API surface.
33
+ */
34
+ export declare function buildEndpointHandler<C extends ServiceContract>(contract: C, methodName: string, options: RpcServerOptions<C>, drainSignal: AbortSignal): ServiceHandler;
35
+ /**
36
+ * Start the reconnect supervisor loop for one contract (design §3).
37
+ *
38
+ * `Svcm`-backed `Service` instances permanently stop on connection loss with
39
+ * no restart path, so this loop rebuilds the service every time it stops
40
+ * (healthy stop or connection loss) until `signal` aborts.
41
+ */
42
+ export declare function createRpcServer<C extends ServiceContract>(contract: C, options: RpcServerOptions<C>): RpcServerHandle;
43
+ export declare function newRequestId(): string;
44
+ export type { RpcHandlers, RpcAuthorize, RpcContext, RpcResult, RpcServerHandle, RpcServerOptions };
45
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAQ7F,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAIpB,sCAAsC;AACtC,wBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAExD;AAED,wCAAwC;AACxC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EACnC,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,GAChB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAEvE;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,eAAe,EAC5D,QAAQ,EAAE,CAAC,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,WAAW,EAAE,WAAW,GACvB,cAAc,CA0NhB;AAyED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC3B,eAAe,CAsHjB;AAID,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,433 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ok = ok;
4
+ exports.fail = fail;
5
+ exports.buildEndpointHandler = buildEndpointHandler;
6
+ exports.createRpcServer = createRpcServer;
7
+ exports.newRequestId = newRequestId;
8
+ const services_1 = require("@nats-io/services");
9
+ const nuid_1 = require("@nats-io/nuid");
10
+ const core_1 = require("@nats-kit/core");
11
+ const contract_js_1 = require("./contract.js");
12
+ const errors_js_1 = require("./errors.js");
13
+ const headers_js_1 = require("./headers.js");
14
+ const DEFAULT_REBUILD_DELAY_MS = 1000;
15
+ /** Build a successful `RpcResult`. */
16
+ function ok(result) {
17
+ return { ok: true, result };
18
+ }
19
+ /** Build a domain-error `RpcResult`. */
20
+ function fail(code, message, details) {
21
+ return { ok: false, error: { code, message, details } };
22
+ }
23
+ function encodeEnvelope(value) {
24
+ return new TextEncoder().encode(JSON.stringify(value));
25
+ }
26
+ function jsonResponseHeaders() {
27
+ const h = (0, core_1.headers)();
28
+ h.set(headers_js_1.HEADER_CONTENT_TYPE, headers_js_1.CONTENT_TYPE_JSON);
29
+ return h;
30
+ }
31
+ /**
32
+ * Build the `ServiceHandler` for one contract method — dispatch order fixed
33
+ * by design §2.4 / wire spec §4.5.
34
+ *
35
+ * The library's own synchronous try/catch around `handler(err, msg)` (see
36
+ * `ServiceImpl.setupHandler`) cannot observe an `async` handler's rejection,
37
+ * so it never counts our service errors toward `num_errors` — we build every
38
+ * response (success, domain error, service error) explicitly instead of
39
+ * throwing, which is what lets a 400/403 carry a JSON `details` body. This is
40
+ * a documented deviation from `nats micro stats` incrementing on our service
41
+ * errors; see the package README.
42
+ *
43
+ * Exported for unit tests (mock `ServiceMsg` + assert the dispatch order) and
44
+ * for advanced embedding over a transport other than `@nats-io/services`'
45
+ * `ServiceHandler`. Not part of the documented public API surface.
46
+ */
47
+ function buildEndpointHandler(contract, methodName, options, drainSignal) {
48
+ const rawMethodDef = contract.methods[methodName];
49
+ if (!rawMethodDef) {
50
+ throw new Error(`createRpcServer: contract has no method "${methodName}"`);
51
+ }
52
+ const methodDef = rawMethodDef;
53
+ // The generic `RpcHandlers<C>` mapping is only meaningful at the contract
54
+ // author's call site; this dispatcher is generic over an arbitrary method
55
+ // name, so it deals in `unknown` and lets zod re-establish the real types.
56
+ const handler = options.handlers[methodName];
57
+ const authorize = options.authorize;
58
+ const logger = options.logger;
59
+ const telemetry = options.telemetry ?? core_1.noopTelemetry;
60
+ const subject = (0, contract_js_1.methodSubject)(contract, methodName);
61
+ return (err, msg) => {
62
+ if (err)
63
+ return; // subscription-level error: the library is already closing the service.
64
+ void dispatch(msg).catch((dispatchErr) => {
65
+ logger?.error({ err: dispatchErr, subject }, "RPC dispatch crashed unexpectedly");
66
+ try {
67
+ msg.respondError(500, "internal error");
68
+ }
69
+ catch {
70
+ // best-effort — the connection may already be gone.
71
+ }
72
+ });
73
+ };
74
+ async function dispatch(msg) {
75
+ const start = Date.now();
76
+ let outcome = "ok";
77
+ let requestId = "unknown";
78
+ let responded = false;
79
+ const respond = (data) => {
80
+ responded = true;
81
+ msg.respond(data, { headers: jsonResponseHeaders() });
82
+ };
83
+ const respondError = (code, description, data) => {
84
+ responded = true;
85
+ if (data) {
86
+ msg.respondError(code, description, data, { headers: jsonResponseHeaders() });
87
+ }
88
+ else {
89
+ msg.respondError(code, description);
90
+ }
91
+ };
92
+ let deadlineTimer;
93
+ try {
94
+ if (drainSignal.aborted) {
95
+ respondError(503, "shutting down");
96
+ outcome = "service-error";
97
+ return;
98
+ }
99
+ // 1. header parse.
100
+ const parsed = (0, headers_js_1.parseRequestHeaders)(msg.headers);
101
+ if (!parsed.ok) {
102
+ respondError(400, parsed.message);
103
+ outcome = "service-error";
104
+ return;
105
+ }
106
+ const { value: h } = parsed;
107
+ requestId = h.requestId;
108
+ if (h.callerKind === "user" && !h.callerId) {
109
+ respondError(401, "user caller requires Nk-Caller-Id");
110
+ outcome = "service-error";
111
+ return;
112
+ }
113
+ // 2. deadline check — already past ⇒ 503, handler never invoked.
114
+ if (h.deadlineMs !== undefined && h.deadlineMs <= Date.now()) {
115
+ respondError(503, "deadline already passed");
116
+ outcome = "service-error";
117
+ return;
118
+ }
119
+ // 3. requiredRoles.
120
+ const requiredRoles = methodDef.requiredRoles ?? [];
121
+ const missingRole = requiredRoles.find((role) => !h.roles.includes(role));
122
+ if (missingRole) {
123
+ respondError(403, `missing role: ${missingRole}`);
124
+ outcome = "service-error";
125
+ return;
126
+ }
127
+ // Contract/implementation skew: the endpoint exists (there's a
128
+ // subscription) but no handler is bound for it — wire spec §4.5's 404.
129
+ // A genuinely unknown method never reaches here at all: no endpoint
130
+ // means no subscription, so the caller sees no-responders instead.
131
+ if (typeof handler !== "function") {
132
+ respondError(404, "unknown method");
133
+ outcome = "service-error";
134
+ return;
135
+ }
136
+ // Build the deadline/drain-combined abort signal for the handler.
137
+ const signals = [drainSignal];
138
+ if (h.deadlineMs !== undefined) {
139
+ const deadlineController = new AbortController();
140
+ deadlineTimer = setTimeout(() => deadlineController.abort(), Math.max(0, h.deadlineMs - Date.now()));
141
+ signals.push(deadlineController.signal);
142
+ }
143
+ const handlerSignal = AbortSignal.any(signals);
144
+ const ctx = {
145
+ caller: { kind: h.callerKind, id: h.callerId, roles: h.roles },
146
+ requestId: h.requestId,
147
+ signal: handlerSignal,
148
+ headers: msg.headers,
149
+ subject: msg.subject,
150
+ method: methodName,
151
+ deadlineMs: h.deadlineMs,
152
+ traceparent: h.traceparent,
153
+ };
154
+ // Body parsing gets its own try: `msg.json()` throws on non-UTF-8/
155
+ // non-JSON bodies, and that failure must never be mistaken for a
156
+ // handler crash (500) — it's a client mistake (400).
157
+ let body;
158
+ try {
159
+ body = msg.json();
160
+ }
161
+ catch (bodyErr) {
162
+ respondError(400, "malformed request body", encodeEnvelope({
163
+ details: { message: bodyErr instanceof Error ? bodyErr.message : String(bodyErr) },
164
+ }));
165
+ outcome = "service-error";
166
+ return;
167
+ }
168
+ // 4. input schema parse.
169
+ const parsedInput = methodDef.input.safeParse(body);
170
+ if (!parsedInput.success) {
171
+ respondError(400, "input validation failed", encodeEnvelope({ details: { issues: parsedInput.error.issues } }));
172
+ outcome = "service-error";
173
+ return;
174
+ }
175
+ // 5. authorize hook.
176
+ if (authorize) {
177
+ try {
178
+ await authorize(ctx, methodName);
179
+ }
180
+ catch (authErr) {
181
+ if ((0, errors_js_1.isRpcError)(authErr)) {
182
+ const status = errors_js_1.STATUS_BY_SERVICE_CODE[authErr.code] ?? 500;
183
+ respondError(status, authErr.message);
184
+ }
185
+ else {
186
+ respondError(500, "authorization hook threw");
187
+ }
188
+ outcome = "service-error";
189
+ return;
190
+ }
191
+ }
192
+ // 6. handler, 7. output schema parse — share a catch: any throw here
193
+ // (handler rejection, or an unexpected throw while validating output)
194
+ // is a server bug, never a client mistake, so it always maps to 500.
195
+ try {
196
+ const result = (await handler(parsedInput.data, ctx));
197
+ if (!result.ok) {
198
+ outcome = "domain-error";
199
+ respond(encodeEnvelope({ ok: false, error: result.error }));
200
+ return;
201
+ }
202
+ const parsedOutput = methodDef.output.safeParse(result.result);
203
+ if (!parsedOutput.success) {
204
+ respondError(500, "handler returned a value the output schema rejects");
205
+ outcome = "service-error";
206
+ return;
207
+ }
208
+ // Wire spec §4.3: a method with no output sends `"result": null`
209
+ // explicitly (never an omitted key) — `parsedOutput.data` is
210
+ // `undefined` for `z.void()`/`z.undefined()`, which `JSON.stringify`
211
+ // would otherwise drop from the object entirely.
212
+ respond(encodeEnvelope({ ok: true, result: parsedOutput.data ?? null }));
213
+ }
214
+ catch (handlerErr) {
215
+ outcome = "service-error";
216
+ // Never leak the real error message on the wire — it's server-internal
217
+ // detail, not something the caller should see or parse. Logged instead.
218
+ logger?.error({ err: handlerErr, requestId, subject }, "RPC handler threw");
219
+ // `responded` guards against a second response when the throw came
220
+ // from `respond()` itself (already sent) rather than the handler.
221
+ if (!responded)
222
+ respondError(500, "internal error");
223
+ }
224
+ }
225
+ catch (unexpectedErr) {
226
+ // Defensive net: anything that slipped past the wrapped sections
227
+ // above. `responded` guards against answering the same request twice.
228
+ outcome = "service-error";
229
+ if (!responded) {
230
+ try {
231
+ respondError(500, "internal error");
232
+ }
233
+ catch {
234
+ logger?.error({ err: unexpectedErr, subject }, "failed to respond with error");
235
+ }
236
+ }
237
+ }
238
+ finally {
239
+ if (deadlineTimer)
240
+ clearTimeout(deadlineTimer);
241
+ const durationMs = Date.now() - start;
242
+ logger?.debug?.({ subject, requestId, outcome, durationMs }, "RPC request handled");
243
+ telemetry.onRpcHandled?.(subject, outcome, durationMs);
244
+ }
245
+ }
246
+ }
247
+ /** Resolve on the next `onReconnect()` emission, on abort, or on completion (mirrors core's internal helper). */
248
+ function waitForReconnectOrAbort(nats, signal) {
249
+ if (signal.aborted)
250
+ return Promise.resolve("aborted");
251
+ return new Promise((resolve) => {
252
+ let settled = false;
253
+ // Holder object (not a reassigned `let`) so `settle` can unsubscribe even
254
+ // when the reconnect observable is a COMPLETED subject that signals
255
+ // synchronously during `.subscribe()` below — i.e. before the subscribe
256
+ // call itself has returned. Mirrors the core's internal helper.
257
+ const holder = {};
258
+ const settle = (outcome) => {
259
+ if (settled)
260
+ return;
261
+ settled = true;
262
+ signal.removeEventListener("abort", onAbort);
263
+ holder.sub?.unsubscribe();
264
+ resolve(outcome);
265
+ };
266
+ const onAbort = () => settle("aborted");
267
+ signal.addEventListener("abort", onAbort);
268
+ holder.sub = nats.onReconnect().subscribe({
269
+ next: () => settle("reconnect"),
270
+ complete: () => settle("completed"),
271
+ error: () => settle("completed"),
272
+ });
273
+ });
274
+ }
275
+ function abortableSleep(ms, signal) {
276
+ return new Promise((resolve) => {
277
+ if (signal.aborted) {
278
+ resolve();
279
+ return;
280
+ }
281
+ const onAbort = () => {
282
+ clearTimeout(timer);
283
+ resolve();
284
+ };
285
+ const timer = setTimeout(() => {
286
+ signal.removeEventListener("abort", onAbort);
287
+ resolve();
288
+ }, ms);
289
+ signal.addEventListener("abort", onAbort, { once: true });
290
+ });
291
+ }
292
+ function abortPromise(signal) {
293
+ if (signal.aborted)
294
+ return Promise.resolve();
295
+ return new Promise((resolve) => {
296
+ signal.addEventListener("abort", () => resolve(), { once: true });
297
+ });
298
+ }
299
+ /**
300
+ * True for the synchronous config errors `Svcm.add` throws (wrapped in a
301
+ * rejected promise) for an invalid `name`/`queue` or a non-SemVer `version`
302
+ * (design §3). These never become valid by retrying, so the supervisor must
303
+ * reject `handle.done` instead of looping — as opposed to a genuine
304
+ * connection error, which is retried after the next reconnect.
305
+ */
306
+ function isTerminalConfigError(err) {
307
+ if (!(err instanceof Error))
308
+ return false;
309
+ return (/ name required$/.test(err.message) ||
310
+ /^invalid .* name - /.test(err.message) ||
311
+ /is not a semver value$/.test(err.message));
312
+ }
313
+ /**
314
+ * Start the reconnect supervisor loop for one contract (design §3).
315
+ *
316
+ * `Svcm`-backed `Service` instances permanently stop on connection loss with
317
+ * no restart path, so this loop rebuilds the service every time it stops
318
+ * (healthy stop or connection loss) until `signal` aborts.
319
+ */
320
+ function createRpcServer(contract, options) {
321
+ const { nats, signal, logger } = options;
322
+ const telemetry = options.telemetry ?? core_1.noopTelemetry;
323
+ const rebuildDelayMs = options.rebuildDelayMs ?? DEFAULT_REBUILD_DELAY_MS;
324
+ const queue = options.queue;
325
+ // Combined abort: external `signal` OR an internal stop() call. Draining
326
+ // starts the instant either fires, per design §3's shutdown notes.
327
+ const internalController = new AbortController();
328
+ const drainSignal = AbortSignal.any([signal, internalController.signal]);
329
+ let currentServiceId;
330
+ let resolveDone;
331
+ let rejectDone;
332
+ const done = new Promise((resolve, reject) => {
333
+ resolveDone = resolve;
334
+ rejectDone = reject;
335
+ });
336
+ // `rejectDone(fatal)` (below) can fire before any consumer has awaited or
337
+ // `.catch()`-ed `done` (e.g. a terminal config error surfacing on the very
338
+ // first tick) — without this, that's an unhandled rejection. This dummy
339
+ // handler keeps the rejection observed without swallowing it: `done`
340
+ // itself still rejects for every real consumer that awaits it.
341
+ void done.catch(() => undefined);
342
+ // Registered once — every iteration reuses the same settled/pending
343
+ // promise instead of adding a fresh `drainSignal` abort listener per
344
+ // rebuild.
345
+ const drainAborted = abortPromise(drainSignal).then(() => null);
346
+ void (async () => {
347
+ try {
348
+ while (!drainSignal.aborted) {
349
+ let svc;
350
+ try {
351
+ // Racing against `drainAborted` (rather than a bare `await`) makes
352
+ // shutdown responsive even while `waitForReady()` is still
353
+ // pending — the eventual settlement is otherwise never observed,
354
+ // so it's `.catch()`-ed to avoid an unhandled rejection.
355
+ const readyPromise = nats.waitForReady();
356
+ await Promise.race([readyPromise, drainAborted]);
357
+ if (drainSignal.aborted) {
358
+ void readyPromise.catch(() => undefined);
359
+ break;
360
+ }
361
+ const nc = nats.getConnection();
362
+ svc = await new services_1.Svcm(nc).add({
363
+ name: (0, contract_js_1.serviceName)(contract),
364
+ version: contract.version,
365
+ description: contract.description,
366
+ metadata: contract.metadata,
367
+ queue,
368
+ });
369
+ currentServiceId = svc.info().id;
370
+ try {
371
+ const group = svc.addGroup((0, contract_js_1.groupSubject)(contract), queue);
372
+ for (const methodName of Object.keys(contract.methods)) {
373
+ group.addEndpoint(methodName, buildEndpointHandler(contract, methodName, options, drainSignal));
374
+ }
375
+ }
376
+ catch (groupErr) {
377
+ // The service was created but addGroup/addEndpoint failed
378
+ // (e.g. a bad method name) — stop it before retrying so we
379
+ // don't leak a half-wired service.
380
+ await svc.stop().catch(() => undefined);
381
+ throw groupErr;
382
+ }
383
+ if (drainSignal.aborted) {
384
+ await svc.stop();
385
+ break;
386
+ }
387
+ telemetry.onRpcServiceStarted?.(svc.info().name, svc.info().id);
388
+ logger?.log({ name: svc.info().name, id: svc.info().id }, "RPC service started");
389
+ const stopErr = await Promise.race([svc.stopped.then((e) => e), drainAborted]);
390
+ await svc.stop();
391
+ telemetry.onRpcServiceStopped?.(svc.info().name, svc.info().id, stopErr ?? undefined);
392
+ logger?.log({ name: svc.info().name, id: svc.info().id }, "RPC service stopped — will rebuild");
393
+ }
394
+ catch (error) {
395
+ if (drainSignal.aborted)
396
+ break;
397
+ // Config errors (bad name/queue, non-SemVer version) never become
398
+ // valid by retrying while the connection is healthy — reject
399
+ // `done` and stop the loop instead of waiting on a reconnect that
400
+ // will never fix anything.
401
+ if (isTerminalConfigError(error))
402
+ throw error;
403
+ logger?.error({ err: error }, "RPC service failed to start — will retry on reconnect");
404
+ const waitOutcome = await waitForReconnectOrAbort(nats, drainSignal);
405
+ if (waitOutcome === "completed")
406
+ break;
407
+ continue;
408
+ }
409
+ if (drainSignal.aborted)
410
+ break;
411
+ await abortableSleep(rebuildDelayMs, drainSignal);
412
+ }
413
+ resolveDone();
414
+ }
415
+ catch (fatal) {
416
+ rejectDone(fatal);
417
+ }
418
+ })();
419
+ return {
420
+ done,
421
+ serviceId: () => currentServiceId,
422
+ stop: async () => {
423
+ internalController.abort();
424
+ await done;
425
+ },
426
+ };
427
+ }
428
+ // Re-export request id helper for consumers who want to mint one manually
429
+ // (e.g. logging correlation before a call is made).
430
+ function newRequestId() {
431
+ return nuid_1.nuid.next();
432
+ }
433
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { type z } from "zod";
2
+ import { type MsgHdrs, type NatsConnectionLike, type NatsLogger, type NatsTelemetry } from "@nats-kit/core";
3
+ import { type ServiceContract } from "./contract.js";
4
+ /** Caller identity stamped on a request (§2.2, §4.2). */
5
+ export interface RpcCaller {
6
+ kind: "user" | "service";
7
+ /** Keycloak subject for `user`; service name for `service`. */
8
+ id?: string;
9
+ roles?: readonly string[];
10
+ }
11
+ export interface RpcCallOptions {
12
+ timeoutMs?: number;
13
+ /** Overrides the client-level caller for this call. */
14
+ caller?: RpcCaller;
15
+ /** Supply your own correlation id; otherwise a nuid is minted. */
16
+ requestId?: string;
17
+ /** Sets `Nk-Deadline`. Defaults to now + effective timeout. */
18
+ deadlineMs?: number;
19
+ /** Extra `Nk-*` headers. `Nats-*` and reserved names are rejected. */
20
+ headers?: Record<string, string>;
21
+ /** W3C traceparent to propagate. */
22
+ traceparent?: string;
23
+ /** Cancels the in-flight request locally. */
24
+ signal?: AbortSignal;
25
+ }
26
+ /** One typed method per contract method, resolving with `z.output` or rejecting with `RpcError`. */
27
+ export type RpcClient<C extends ServiceContract> = {
28
+ [K in keyof C["methods"]]: (input: z.input<C["methods"][K]["input"]>, opts?: RpcCallOptions) => Promise<z.output<C["methods"][K]["output"]>>;
29
+ };
30
+ export interface RpcClientOptions {
31
+ nats: NatsConnectionLike;
32
+ /** Default caller identity stamped on every call. */
33
+ caller?: RpcCaller;
34
+ /** Fallback when a method declares no `timeoutMs`. Default 10_000. */
35
+ defaultTimeoutMs?: number;
36
+ /**
37
+ * Validate responses against `output` before resolving. Default TRUE.
38
+ * Per-client opt-out only (no per-call override).
39
+ */
40
+ validateResponses?: boolean;
41
+ logger?: NatsLogger;
42
+ telemetry?: NatsTelemetry;
43
+ }
44
+ /** Per-request context handed to a server handler (§2.4). */
45
+ export interface RpcContext {
46
+ caller: Required<Pick<RpcCaller, "kind">> & RpcCaller & {
47
+ roles: readonly string[];
48
+ };
49
+ requestId: string;
50
+ /** Aborts at `Nk-Deadline`, or when the server drains. */
51
+ signal: AbortSignal;
52
+ headers: MsgHdrs;
53
+ subject: string;
54
+ method: string;
55
+ deadlineMs?: number;
56
+ traceparent?: string;
57
+ }
58
+ export type RpcResult<T, E extends string> = {
59
+ ok: true;
60
+ result: T;
61
+ } | {
62
+ ok: false;
63
+ error: {
64
+ code: E;
65
+ message: string;
66
+ details?: unknown;
67
+ };
68
+ };
69
+ export type RpcHandlers<C extends ServiceContract> = {
70
+ [K in keyof C["methods"]]: (input: z.output<C["methods"][K]["input"]>, ctx: RpcContext) => Promise<RpcResult<z.input<C["methods"][K]["output"]>, C["methods"][K]["errors"] extends readonly (infer E extends string)[] ? E : never>>;
71
+ };
72
+ /**
73
+ * Optional escape hatch for policy the contract's `requiredRoles` cannot
74
+ * express. Runs AFTER the library's `requiredRoles` check and before
75
+ * dispatch. Return normally to allow; throw an `RpcError` to deny.
76
+ */
77
+ export type RpcAuthorize<C extends ServiceContract> = (ctx: Omit<RpcContext, "signal">, method: keyof C["methods"] & string) => void | Promise<void>;
78
+ export interface RpcServerOptions<C extends ServiceContract> {
79
+ nats: NatsConnectionLike;
80
+ handlers: RpcHandlers<C>;
81
+ /** Optional second gate, run only after `requiredRoles` passed. */
82
+ authorize?: RpcAuthorize<C>;
83
+ /** Queue group. Defaults to micro's `"q"`; `""` disables load balancing. */
84
+ queue?: string;
85
+ /** Stops the supervisor and drains. */
86
+ signal: AbortSignal;
87
+ /** Delay before rebuilding after the service stops. Default 1000. */
88
+ rebuildDelayMs?: number;
89
+ logger?: NatsLogger;
90
+ telemetry?: NatsTelemetry;
91
+ }
92
+ export interface RpcServerHandle {
93
+ /** Resolves when the supervisor loop has exited. */
94
+ readonly done: Promise<void>;
95
+ /** Current micro service id (nuid). Changes on every rebuild. */
96
+ serviceId(): string | undefined;
97
+ /** Aborts the server's signal (if it owns one) and awaits `done`. */
98
+ stop(): Promise<void>;
99
+ }
100
+ //# sourceMappingURL=types.d.ts.map