@trevrpc/trevrpc-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/protoc-gen-trevrpc-js.js +17 -0
  4. package/examples/greeter/README.md +35 -0
  5. package/examples/greeter/client.js +172 -0
  6. package/examples/greeter/greeter.node.trevrpc.d.ts +53 -0
  7. package/examples/greeter/greeter.node.trevrpc.js +9 -0
  8. package/examples/greeter/greeter.proto +18 -0
  9. package/examples/greeter/greeter.trevrpc.d.ts +55 -0
  10. package/examples/greeter/greeter.trevrpc.js +103 -0
  11. package/examples/greeter/index.html +140 -0
  12. package/examples/greeter/static-server.js +168 -0
  13. package/package.json +89 -0
  14. package/src/advanced.d.ts +29 -0
  15. package/src/advanced.js +1 -0
  16. package/src/browser-channel.js +45 -0
  17. package/src/channel.js +375 -0
  18. package/src/client.js +1397 -0
  19. package/src/connect.js +6 -0
  20. package/src/connect.node.js +6 -0
  21. package/src/framing.js +618 -0
  22. package/src/generator.js +868 -0
  23. package/src/index.d.ts +577 -0
  24. package/src/index.js +3 -0
  25. package/src/index.node.d.ts +10 -0
  26. package/src/index.node.js +3 -0
  27. package/src/metadata.js +112 -0
  28. package/src/native-loader.js +94 -0
  29. package/src/node-advanced.d.ts +27 -0
  30. package/src/node-advanced.js +1 -0
  31. package/src/node-generated.d.ts +24 -0
  32. package/src/node-generated.js +126 -0
  33. package/src/node-index.d.ts +1 -0
  34. package/src/node-index.js +7 -0
  35. package/src/node.d.ts +298 -0
  36. package/src/node.js +1456 -0
  37. package/src/runtime.js +66 -0
  38. package/src/server-response.js +49 -0
  39. package/src/status.js +145 -0
  40. package/src/timer.js +29 -0
  41. package/src/webtransport.js +624 -0
  42. package/src/wire.js +403 -0
@@ -0,0 +1,112 @@
1
+ import { invalidArgument } from "./status.js";
2
+
3
+ export const MaxMetadataEntries = 64;
4
+ export const MaxMetadataKeyLen = 128;
5
+ export const MaxMetadataValueLen = 8 * 1024;
6
+ export const MaxMetadataTotalSize = 64 * 1024;
7
+ export const ReservedMetadataPrefix = "trevrpc-";
8
+
9
+ const textEncoder = new TextEncoder();
10
+
11
+ /** Normalizes a metadata key to lowercase ASCII. */
12
+ export function normalizeMetadataKey(key) {
13
+ return String(key).toLowerCase();
14
+ }
15
+
16
+ /** Normalizes metadata keys and converts metadata values to bytes. */
17
+ export function normalizeMetadata(metadata = {}) {
18
+ const normalized = Object.create(null);
19
+ const entries = metadata instanceof Map ? metadata.entries() : Object.entries(metadata);
20
+
21
+ for (const [key, value] of entries) {
22
+ normalized[normalizeMetadataKey(key)] = metadataValueToBytes(value);
23
+ }
24
+
25
+ validateMetadata(normalized);
26
+ return normalized;
27
+ }
28
+
29
+ /** Converts a metadata value into bytes. */
30
+ export function metadataValueToBytes(value) {
31
+ if (value instanceof Uint8Array) {
32
+ return value;
33
+ }
34
+
35
+ if (value instanceof ArrayBuffer) {
36
+ return new Uint8Array(value);
37
+ }
38
+
39
+ if (ArrayBuffer.isView(value)) {
40
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
41
+ }
42
+
43
+ if (typeof value === "string") {
44
+ return textEncoder.encode(value);
45
+ }
46
+
47
+ if (Array.isArray(value)) {
48
+ return new Uint8Array(value);
49
+ }
50
+
51
+ throw invalidArgument("metadata values must be bytes or strings");
52
+ }
53
+
54
+ /** Validates metadata key syntax, value sizes, and total metadata limits. */
55
+ export function validateMetadata(metadata = {}) {
56
+ const entries = Object.entries(metadata);
57
+ if (entries.length > MaxMetadataEntries) {
58
+ throw invalidArgument(
59
+ `metadata has ${entries.length} entries, maximum is ${MaxMetadataEntries}`,
60
+ );
61
+ }
62
+
63
+ let totalSize = 0;
64
+ for (const [key, value] of entries) {
65
+ validateMetadataKey(key);
66
+ const bytes = metadataValueToBytes(value);
67
+ if (bytes.byteLength > MaxMetadataValueLen) {
68
+ throw invalidArgument(
69
+ `metadata value ${JSON.stringify(key)} is ${bytes.byteLength} bytes, maximum is ${MaxMetadataValueLen}`,
70
+ );
71
+ }
72
+
73
+ totalSize += key.length + bytes.byteLength;
74
+ }
75
+
76
+ if (totalSize > MaxMetadataTotalSize) {
77
+ throw invalidArgument(`metadata is ${totalSize} bytes, maximum is ${MaxMetadataTotalSize}`);
78
+ }
79
+ }
80
+
81
+ function validateMetadataKey(key) {
82
+ if (key === "") {
83
+ throw invalidArgument("metadata key is empty");
84
+ }
85
+
86
+ if (key.length > MaxMetadataKeyLen) {
87
+ throw invalidArgument(
88
+ `metadata key ${JSON.stringify(key)} is ${key.length} bytes, maximum is ${MaxMetadataKeyLen}`,
89
+ );
90
+ }
91
+
92
+ if (key.startsWith(ReservedMetadataPrefix)) {
93
+ throw invalidArgument(
94
+ `metadata key ${JSON.stringify(key)} uses reserved prefix ${JSON.stringify(ReservedMetadataPrefix)}`,
95
+ );
96
+ }
97
+
98
+ for (let index = 0; index < key.length; index += 1) {
99
+ const code = key.charCodeAt(index);
100
+ const valid =
101
+ (code >= 0x61 && code <= 0x7a) ||
102
+ (code >= 0x30 && code <= 0x39) ||
103
+ code === 0x2e ||
104
+ code === 0x5f ||
105
+ code === 0x2d;
106
+ if (!valid) {
107
+ throw invalidArgument(
108
+ `metadata key ${JSON.stringify(key)} must use lowercase ASCII letters, digits, '.', '_' or '-'`,
109
+ );
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
9
+ const SupportedTarget = "linux/x64/glibc";
10
+ const LinuxX64GnuPackage = "@trevrpc/trevrpc-js-native-linux-x64-gnu";
11
+
12
+ /** Loads the target-specific native addon without building during installation. */
13
+ export function loadNativeAddon() {
14
+ const target = nativeTarget();
15
+ const packageName = nativePackageName(target);
16
+ let packageError;
17
+ let packageState = "unsupported";
18
+
19
+ if (packageName != null) {
20
+ try {
21
+ return require(packageName);
22
+ } catch (error) {
23
+ packageError = error;
24
+ packageState = isMissingPackage(error, packageName) ? "missing" : "failed to load";
25
+ }
26
+ }
27
+
28
+ const fallback = developmentFallbackPath();
29
+ let fallbackError;
30
+ if (fallback != null) {
31
+ try {
32
+ return require(fallback);
33
+ } catch (error) {
34
+ fallbackError = error;
35
+ }
36
+ }
37
+
38
+ const detected = targetName(target);
39
+ const expected = packageName ?? "no optional package (unsupported target)";
40
+ const fallbackState =
41
+ fallback == null
42
+ ? "no source-checkout fallback was present"
43
+ : fallbackError == null
44
+ ? "the source-checkout fallback was unavailable"
45
+ : "the source-checkout fallback failed to load";
46
+ throw new Error(
47
+ `TrevRPC native addon is unavailable for detected target ${detected}. ` +
48
+ `Supported targets: ${SupportedTarget}. Expected optional package: ${expected}@0.1.0; ` +
49
+ `package state: ${packageState}; ${fallbackState}. ` +
50
+ "Install the matching optional package or build build/native/trevrpc_native.node explicitly in a source checkout; npm installation never starts a source build.",
51
+ { cause: fallbackError ?? packageError },
52
+ );
53
+ }
54
+
55
+ /** Returns the normalized native npm target. */
56
+ export function nativeTarget() {
57
+ return Object.freeze({
58
+ platform: process.platform,
59
+ arch: process.arch,
60
+ libc: process.platform === "linux" ? linuxLibc() : undefined,
61
+ });
62
+ }
63
+
64
+ function nativePackageName(target) {
65
+ if (target.platform === "linux" && target.arch === "x64" && target.libc === "glibc") {
66
+ return LinuxX64GnuPackage;
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function developmentFallbackPath() {
72
+ if (!existsSync(join(packageRoot, "native", "CMakeLists.txt"))) {
73
+ return null;
74
+ }
75
+ const path = join(packageRoot, "build", "native", "trevrpc_native.node");
76
+ return existsSync(path) ? path : null;
77
+ }
78
+
79
+ function targetName(target) {
80
+ return `${target.platform}/${target.arch}${target.libc == null ? "" : `/${target.libc}`}`;
81
+ }
82
+
83
+ function linuxLibc() {
84
+ const report = process.report?.getReport?.();
85
+ return report?.header?.glibcVersionRuntime == null ? "musl" : "glibc";
86
+ }
87
+
88
+ function isMissingPackage(error, packageName) {
89
+ return (
90
+ error?.code === "MODULE_NOT_FOUND" &&
91
+ typeof error.message === "string" &&
92
+ error.message.includes(packageName)
93
+ );
94
+ }
@@ -0,0 +1,27 @@
1
+ import type { RpcResponseMessage, RpcStreamFrameMessage, Transport } from "./index.js";
2
+ import type { NodeChannelTarget, NodeConnectOptions } from "./node.js";
3
+
4
+ export interface RawNodeCloseInfo {
5
+ nativeCode: number;
6
+ }
7
+
8
+ /** Raw single-connection Node transport backed by trevrpc-c and MsQuic. */
9
+ export class RawNodeTransport implements Transport {
10
+ maxFrameSize?: number;
11
+ readonly closed: Promise<RawNodeCloseInfo>;
12
+
13
+ static connect(
14
+ target: string | URL | NodeChannelTarget,
15
+ options?: NodeConnectOptions,
16
+ ): Promise<RawNodeTransport>;
17
+ call(
18
+ request: Parameters<Transport["call"]>[0],
19
+ options?: Parameters<Transport["call"]>[1],
20
+ ): Promise<RpcResponseMessage>;
21
+ streamingCall(
22
+ request: Parameters<Transport["streamingCall"]>[0],
23
+ requestBody: AsyncIterable<Uint8Array>,
24
+ options?: Parameters<Transport["streamingCall"]>[2],
25
+ ): Promise<AsyncIterableIterator<RpcStreamFrameMessage>>;
26
+ close(): void;
27
+ }
@@ -0,0 +1 @@
1
+ export { RawNodeTransport } from "./node.js";
@@ -0,0 +1,24 @@
1
+ import type { Root, Type } from "protobufjs";
2
+
3
+ import type { RpcMethodKind, RpcServiceDescriptor } from "./index.js";
4
+ import type { NodeServer, NodeServerCall } from "./node.js";
5
+
6
+ export type { StreamingServerResponse, UnaryServerResponse } from "./index.js";
7
+ export type { NodeServer, NodeServerCall } from "./node.js";
8
+
9
+ /** Registers typed generated handlers on a Node server. */
10
+ export function registerTypedService(
11
+ server: NodeServer,
12
+ root: Root,
13
+ service: RpcServiceDescriptor,
14
+ handlers: Record<string, unknown>,
15
+ ): NodeServer;
16
+
17
+ /** Dispatches one typed generated Node handler. */
18
+ export function dispatchTypedNodeHandler(
19
+ call: NodeServerCall,
20
+ handler: (...args: unknown[]) => unknown,
21
+ kind: RpcMethodKind,
22
+ requestType: Type,
23
+ responseType: Type,
24
+ ): Promise<void>;
@@ -0,0 +1,126 @@
1
+ import { marshalMessage, unmarshalMessage } from "./framing.js";
2
+ import { isStreamingServerResponse, isUnaryServerResponse } from "./server-response.js";
3
+ import { Code, statusError } from "./status.js";
4
+ import { RpcStreamFrameKind } from "./wire.js";
5
+
6
+ /** Registers typed generated handlers on a Node server. */
7
+ export function registerTypedService(server, root, service, handlers) {
8
+ for (const [jsName, method] of Object.entries(service.methods)) {
9
+ const handler = handlers[jsName] ?? handlers[method.name];
10
+ if (handler == null) {
11
+ continue;
12
+ }
13
+ const requestType = root.lookupType(method.inputType);
14
+ const responseType = root.lookupType(method.outputType);
15
+ server.register(service.fullName, method.name, method.kind, async (call) => {
16
+ await dispatchTypedNodeHandler(call, handler, method.kind, requestType, responseType);
17
+ });
18
+ }
19
+ return server;
20
+ }
21
+
22
+ /** Dispatches one typed generated Node handler. */
23
+ export async function dispatchTypedNodeHandler(call, handler, kind, requestType, responseType) {
24
+ if (kind === "unary") {
25
+ const request = unmarshalMessage(requestType, call.request.body ?? new Uint8Array(0));
26
+ const response = await handler(request, call);
27
+ const envelope = isUnaryServerResponse(response) ? response : null;
28
+ await call.respond({
29
+ body: marshalMessage(responseType, envelope == null ? response : envelope.message),
30
+ metadata: envelope?.metadata ?? {},
31
+ });
32
+ return;
33
+ }
34
+
35
+ if (kind === "clientStreaming") {
36
+ const response = await handler(decodeTypedNodeRequests(call, requestType), call);
37
+ const envelope = isUnaryServerResponse(response) ? response : null;
38
+ await call.sendMessage(
39
+ marshalMessage(responseType, envelope == null ? response : envelope.message),
40
+ );
41
+ await call.finishStream(Code.Ok, "", envelope?.metadata ?? {});
42
+ return;
43
+ }
44
+
45
+ const request =
46
+ kind === "serverStreaming"
47
+ ? unmarshalMessage(requestType, call.request.body ?? new Uint8Array(0))
48
+ : decodeTypedNodeRequests(call, requestType);
49
+ const response = await handler(request, call);
50
+ const envelope = isStreamingServerResponse(response) ? response : null;
51
+ await writeTypedResponseMessages(
52
+ call,
53
+ envelope == null ? response : envelope.messages,
54
+ responseType,
55
+ );
56
+ const status = envelope?.status;
57
+ await call.finishStream(status?.code ?? Code.Ok, status?.message ?? "", status?.metadata ?? {});
58
+ }
59
+
60
+ async function writeTypedResponseMessages(call, messages, responseType) {
61
+ const iterator = responseIterator(messages ?? []);
62
+ try {
63
+ for (;;) {
64
+ const result = await nextTypedResponseBatch(iterator, call.writeBatchMaxMessages ?? 16);
65
+ if (result.done) {
66
+ return;
67
+ }
68
+ await call.sendMany(result.value.map((message) => marshalMessage(responseType, message)));
69
+ }
70
+ } catch (error) {
71
+ try {
72
+ await iterator.return?.();
73
+ } catch {
74
+ // Preserve the send or iterator error, matching for-await cleanup behavior.
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ function responseIterator(messages) {
81
+ if (typeof messages?.[Symbol.asyncIterator] === "function") {
82
+ return messages[Symbol.asyncIterator]();
83
+ }
84
+ if (typeof messages?.[Symbol.iterator] === "function") {
85
+ const iterator = messages[Symbol.iterator]();
86
+ return {
87
+ next: () => Promise.resolve(iterator.next()),
88
+ return: () =>
89
+ Promise.resolve(
90
+ typeof iterator.return === "function"
91
+ ? iterator.return()
92
+ : { done: true, value: undefined },
93
+ ),
94
+ };
95
+ }
96
+ throw new TypeError("streaming handler must return an iterable or async iterable");
97
+ }
98
+
99
+ async function nextTypedResponseBatch(iterator, maxMessages) {
100
+ if (typeof iterator.nextBatch === "function") {
101
+ return await iterator.nextBatch(maxMessages);
102
+ }
103
+ const result = await iterator.next();
104
+ return result.done ? result : { done: false, value: [result.value] };
105
+ }
106
+
107
+ async function* decodeTypedNodeRequests(call, requestType) {
108
+ for (;;) {
109
+ const frame = await call.recv();
110
+ if (frame == null) {
111
+ return;
112
+ }
113
+ if (frame.kind === RpcStreamFrameKind.Message) {
114
+ yield unmarshalMessage(requestType, frame.body ?? new Uint8Array(0));
115
+ continue;
116
+ }
117
+ if (frame.kind === RpcStreamFrameKind.Status) {
118
+ const code = frame.status ?? Code.Ok;
119
+ if (code !== Code.Ok) {
120
+ throw statusError(code, frame.message ?? "", frame.metadata ?? {});
121
+ }
122
+ return;
123
+ }
124
+ throw statusError(Code.InvalidArgument, "request stream contained an unknown frame kind");
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export * from "./node.js";
@@ -0,0 +1,7 @@
1
+ export {
2
+ Channel,
3
+ NodeServer,
4
+ NodeServerCall,
5
+ bearerAuthorizer,
6
+ metadataValueAuthorizer,
7
+ } from "./node.js";
package/src/node.d.ts ADDED
@@ -0,0 +1,298 @@
1
+ import type {
2
+ ChannelLifecycleEvent,
3
+ ChannelLifecycleOptions,
4
+ ChannelState,
5
+ RpcChannel,
6
+ MetadataInput,
7
+ RpcKindValue,
8
+ RpcMethodKind,
9
+ RpcResponseMessage,
10
+ RpcServiceDescriptor,
11
+ RpcStreamFrameMessage,
12
+ Transport,
13
+ } from "./index.js";
14
+
15
+ type TrevRpcEventListener = ((event: Event) => void) | { handleEvent(event: Event): void };
16
+ interface TrevRpcEventListenerOptions {
17
+ capture?: boolean;
18
+ }
19
+ interface TrevRpcAddEventListenerOptions extends TrevRpcEventListenerOptions {
20
+ once?: boolean;
21
+ passive?: boolean;
22
+ signal?: AbortSignal;
23
+ }
24
+
25
+ export interface NodeEndpoint {
26
+ host: string;
27
+ port: number;
28
+ }
29
+
30
+ export interface NodeConnectOptions {
31
+ caCertFile?: string;
32
+ skipCertificateValidation?: boolean;
33
+ maxStreamsPerSession?: number;
34
+ idleTimeoutMs?: number;
35
+ maxFrameSize?: number;
36
+ maxPendingSendBytes?: number;
37
+ maxPendingSendCount?: number;
38
+ signal?: AbortSignal;
39
+ }
40
+
41
+ export interface NodeChannelOptions extends NodeConnectOptions, ChannelLifecycleOptions {
42
+ /** Bounds initial readiness only. Later reconnects continue until close(). */
43
+ timeoutMs?: number;
44
+ }
45
+
46
+ export interface NodeChannelTarget extends NodeChannelOptions {
47
+ host: string;
48
+ port?: number;
49
+ }
50
+
51
+ export interface NodeRpcStartedEvent {
52
+ service: string;
53
+ method: string;
54
+ requestBodyLength: number;
55
+ kind: RpcKindValue;
56
+ }
57
+
58
+ export interface NodeRpcFinishedEvent extends NodeRpcStartedEvent {
59
+ responseBodyLength: number;
60
+ status: number;
61
+ elapsedMs?: number;
62
+ }
63
+
64
+ export interface NodeLogEvent {
65
+ level: "debug" | "info" | "warn" | "error" | string;
66
+ event: string;
67
+ message?: string;
68
+ service?: string;
69
+ method?: string;
70
+ status?: number;
71
+ }
72
+
73
+ export interface NodeServerMetrics {
74
+ rpcStarted?(event: NodeRpcStartedEvent): void;
75
+ rpcFinished?(event: NodeRpcFinishedEvent): void;
76
+ started?(event: NodeRpcStartedEvent): void;
77
+ finished?(event: NodeRpcFinishedEvent): void;
78
+ }
79
+
80
+ export interface NodeServerLogger {
81
+ log?(event: NodeLogEvent): void;
82
+ debug?(event: NodeLogEvent): void;
83
+ info?(event: NodeLogEvent): void;
84
+ warn?(event: NodeLogEvent): void;
85
+ error?(event: NodeLogEvent): void;
86
+ }
87
+
88
+ export type NodeServerAuthorizationResult =
89
+ | void
90
+ | boolean
91
+ | {
92
+ code?: number;
93
+ status?: number;
94
+ message?: string;
95
+ statusMessage?: string;
96
+ metadata?: MetadataInput;
97
+ };
98
+
99
+ export type NodeServerAuthorizer = (
100
+ call: NodeServerCall,
101
+ ) => NodeServerAuthorizationResult | Promise<NodeServerAuthorizationResult>;
102
+
103
+ export interface NodeHttp3AdmissionRequest {
104
+ path: string;
105
+ authority: string;
106
+ secure: boolean;
107
+ }
108
+
109
+ export type NodeHttp3Admission = (request: NodeHttp3AdmissionRequest) => boolean;
110
+
111
+ export interface NodeListenOptions extends NodeConnectOptions {
112
+ host?: string;
113
+ port?: number;
114
+ path?: string;
115
+ origin?: string;
116
+ certFile?: string;
117
+ keyFile?: string;
118
+ maxSessionsPerConnection?: number;
119
+ /** Enables ordinary HTTP/3 POST requests. Disabled by default. */
120
+ enableHttp3?: boolean;
121
+ /** HTTP/3 POST path. Defaults to /trevrpc. */
122
+ http3Path?: string;
123
+ /**
124
+ * Synchronous request admission hook run before RPC framing is read. The
125
+ * native wait is bounded by initialRequestTimeoutMs.
126
+ */
127
+ http3Admission?: NodeHttp3Admission;
128
+ authorizer?: NodeServerAuthorizer;
129
+ metrics?: NodeServerMetrics;
130
+ logger?: NodeServerLogger | ((event: NodeLogEvent) => void);
131
+ maxStreamMessages?: number;
132
+ /** Initial HTTP/RPC request timeout. Defaults to 10000 ms. */
133
+ initialRequestTimeoutMs?: number;
134
+ streamIdleTimeoutMs?: number;
135
+ }
136
+
137
+ /** Native Node channel with background connection reconnection. */
138
+ export class Channel extends EventTarget implements RpcChannel {
139
+ readonly endpoint: Readonly<NodeEndpoint>;
140
+ readonly options: Readonly<NodeChannelOptions>;
141
+ readonly ready: boolean;
142
+ readonly state: ChannelState;
143
+ readonly generation: number;
144
+
145
+ constructor(target: string | URL | NodeChannelTarget, options?: NodeChannelOptions);
146
+
147
+ /** Creates a native channel and waits for its first ready generation. */
148
+ static connect(
149
+ target: string | URL | NodeChannelTarget,
150
+ options?: NodeChannelOptions,
151
+ ): Promise<Channel>;
152
+ /** Waits for the current or next connection generation to become ready. */
153
+ waitUntilReady(): Promise<void>;
154
+ /** Sends a unary call on the current generation without replaying it. */
155
+ call(
156
+ request: Parameters<Transport["call"]>[0],
157
+ options?: Parameters<Transport["call"]>[1],
158
+ ): Promise<RpcResponseMessage>;
159
+ /** Starts a streaming call on the current generation without resuming it later. */
160
+ streamingCall(
161
+ request: Parameters<Transport["streamingCall"]>[0],
162
+ requestBody: AsyncIterable<Uint8Array>,
163
+ options?: Parameters<Transport["streamingCall"]>[2],
164
+ ): Promise<AsyncIterableIterator<RpcStreamFrameMessage>>;
165
+ /** Stops reconnecting and closes the current generation. */
166
+ close(): void;
167
+ addEventListener(
168
+ type: "statechange" | ChannelState,
169
+ callback: (event: ChannelLifecycleEvent) => void,
170
+ options?: boolean | TrevRpcAddEventListenerOptions,
171
+ ): void;
172
+ addEventListener(
173
+ type: string,
174
+ callback: TrevRpcEventListener | null,
175
+ options?: boolean | TrevRpcAddEventListenerOptions,
176
+ ): void;
177
+ removeEventListener(
178
+ type: "statechange" | ChannelState,
179
+ callback: (event: ChannelLifecycleEvent) => void,
180
+ options?: boolean | TrevRpcEventListenerOptions,
181
+ ): void;
182
+ removeEventListener(
183
+ type: string,
184
+ callback: TrevRpcEventListener | null,
185
+ options?: boolean | TrevRpcEventListenerOptions,
186
+ ): void;
187
+ }
188
+
189
+ export type NodeServerUnaryResult =
190
+ | RpcResponseMessage
191
+ | Uint8Array
192
+ | ArrayBuffer
193
+ | ArrayBufferView
194
+ | readonly number[];
195
+ export type NodeServerStreamingResult = AsyncIterable<
196
+ Uint8Array | ArrayBuffer | ArrayBufferView | readonly number[]
197
+ >;
198
+
199
+ export type NodeServerHandler = (
200
+ call: NodeServerCall,
201
+ ) =>
202
+ | void
203
+ | NodeServerUnaryResult
204
+ | NodeServerStreamingResult
205
+ | Promise<void | NodeServerUnaryResult | NodeServerStreamingResult>;
206
+
207
+ /** Raw server call passed to NodeServer handlers. */
208
+ export class NodeServerCall {
209
+ request: {
210
+ service: string;
211
+ method: string;
212
+ body: Uint8Array;
213
+ metadata: Record<string, Uint8Array>;
214
+ kind: RpcKindValue;
215
+ version: number;
216
+ timeoutNanos?: string;
217
+ };
218
+ completed: boolean;
219
+ deferred: boolean;
220
+ responseBodyLength: number;
221
+ finalStatus: number;
222
+ startedAt: number;
223
+ completedAt: number | null;
224
+ /** Aborts when the deadline, cancellation, or server shutdown settles first. */
225
+ readonly signal: AbortSignal;
226
+ /** Absolute peer deadline captured from the native call context, or null when absent. */
227
+ readonly deadline: Date | null;
228
+
229
+ /** Keeps the call open after the handler returns. */
230
+ defer(): this;
231
+ /** Sends a unary response and completes the call. */
232
+ respond(
233
+ response?: RpcResponseMessage | Uint8Array | ArrayBuffer | ArrayBufferView | readonly number[],
234
+ ): Promise<void>;
235
+ /** Sends one streaming response message. */
236
+ sendMessage(body: Uint8Array | ArrayBuffer | ArrayBufferView | readonly number[]): Promise<void>;
237
+ /** Sends multiple streaming response messages. */
238
+ sendMany(
239
+ bodies: Iterable<Uint8Array | ArrayBuffer | ArrayBufferView | readonly number[]>,
240
+ ): Promise<void>;
241
+ /** Receives one streaming request frame, or null after EOF. */
242
+ recv(): Promise<RpcStreamFrameMessage | null>;
243
+ /** Sends the terminal streaming status and completes the call. */
244
+ finishStream(status?: number, message?: string, metadata?: MetadataInput): Promise<void>;
245
+ /** Cancels and closes the call. */
246
+ close(reason?: unknown): void;
247
+ }
248
+
249
+ /** Native Node server backed by trevrpc-c and MsQuic. */
250
+ export class NodeServer {
251
+ port: number;
252
+ maxFrameSize?: number;
253
+ closed: Promise<void> | null;
254
+
255
+ /** Creates a native QUIC and WebTransport TrevRPC server backed by trevrpc-c. */
256
+ static listen(
257
+ urlOrOptions: string | URL | NodeListenOptions,
258
+ options?: NodeListenOptions,
259
+ ): Promise<NodeServer>;
260
+
261
+ /** Registers one raw RPC handler. */
262
+ register(
263
+ service: string,
264
+ method: string,
265
+ kind: RpcKindValue | RpcMethodKind,
266
+ handler: NodeServerHandler,
267
+ ): this;
268
+
269
+ /** Registers handlers for a generated service descriptor. */
270
+ registerService(service: RpcServiceDescriptor, handlers: Record<string, NodeServerHandler>): this;
271
+
272
+ /** Sets an async authorizer that runs before registered handlers. */
273
+ setAuthorizer(authorizer?: NodeServerAuthorizer | null): this;
274
+ /** Clears the server authorizer. */
275
+ clearAuthorizer(): this;
276
+ /** Sets metrics callbacks. */
277
+ setMetrics(metrics?: NodeServerMetrics | null): this;
278
+ /** Clears metrics callbacks. */
279
+ clearMetrics(): this;
280
+ /** Sets a logger function or logger object. */
281
+ setLogger(logger?: NodeServerLogger | ((event: NodeLogEvent) => void) | null): this;
282
+ /** Clears the logger. */
283
+ clearLogger(): this;
284
+
285
+ /** Starts accepting RPCs. The returned promise resolves after close. */
286
+ serve(): Promise<void>;
287
+ /** Requests graceful server shutdown and drains already admitted calls. */
288
+ close(): void;
289
+ }
290
+
291
+ /** Creates an authorizer requiring one metadata key/value pair. */
292
+ export function metadataValueAuthorizer(
293
+ key: string,
294
+ value: string | Uint8Array | ArrayBuffer | ArrayBufferView | readonly number[],
295
+ ): NodeServerAuthorizer;
296
+
297
+ /** Creates an authorizer requiring an Authorization: Bearer token metadata value. */
298
+ export function bearerAuthorizer(token: string): NodeServerAuthorizer;