@spikard/node 0.12.0 → 0.13.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/dist/index.d.ts CHANGED
@@ -1,67 +1,3 @@
1
- interface StreamingResponseInit {
2
- statusCode?: number;
3
- headers?: Record<string, string>;
4
- }
5
- declare const STREAM_HANDLE_PROP: "__spikard_stream_handle";
6
- type StreamChunk = JsonValue | string | Buffer | Uint8Array | ArrayBuffer | ArrayBufferView | null | undefined;
7
- type ChunkIterator = AsyncIterator<StreamChunk> & AsyncIterable<StreamChunk>;
8
- type StreamingHandle = {
9
- kind: "native";
10
- handle: number;
11
- init: StreamingResponseInit;
12
- } | {
13
- kind: "js";
14
- iterator: ChunkIterator;
15
- init: StreamingResponseInit;
16
- };
17
- declare class StreamingResponse {
18
- readonly [STREAM_HANDLE_PROP]: StreamingHandle;
19
- constructor(stream: AsyncIterable<StreamChunk> | AsyncIterator<StreamChunk>, init?: StreamingResponseInit);
20
- }
21
-
22
- type JsonPrimitive = string | number | boolean | null;
23
- type JsonValue = JsonPrimitive | JsonValue[] | {
24
- [Key in string]: JsonValue;
25
- };
26
- type JsonRecord = Record<string, JsonValue>;
27
- type MaybePromise<T> = T | Promise<T>;
28
- interface Base64EncodedBody {
29
- __spikard_base64__: string;
30
- }
31
- type HandlerBody = JsonValue | Base64EncodedBody | null;
32
- interface StructuredHandlerResponse {
33
- status?: number;
34
- statusCode?: number;
35
- headers?: Record<string, string>;
36
- body?: HandlerBody;
37
- }
38
- type HandlerResult = StructuredHandlerResponse | JsonValue | StreamingResponse | undefined;
39
- type HandlerFunction<TReturn extends HandlerResult = HandlerResult> = (request: Request) => MaybePromise<TReturn>;
40
- type NativeHandlerFunction<TReturn extends HandlerResult = HandlerResult> = (requestJson: string) => MaybePromise<TReturn | string>;
41
- type WebSocketHandler = (message: unknown) => MaybePromise<unknown>;
42
- interface WebSocketOptions {
43
- onConnect?: () => MaybePromise<void>;
44
- onDisconnect?: () => MaybePromise<void>;
45
- messageSchema?: unknown;
46
- responseSchema?: unknown;
47
- handlerName?: string;
48
- }
49
-
50
- interface Request {
51
- method: string;
52
- path: string;
53
- params: Record<string, string>;
54
- pathParams: Record<string, string>;
55
- query: Record<string, string>;
56
- queryParams: Record<string, string>;
57
- headers: Record<string, string>;
58
- cookies: Record<string, string>;
59
- body: Buffer | null;
60
- dependencies: Record<string, unknown> | undefined;
61
- json<T = JsonValue>(): T;
62
- form(): Record<string, string>;
63
- }
64
-
65
1
  interface CompressionConfig {
66
2
  gzip?: boolean;
67
3
  brotli?: boolean;
@@ -147,62 +83,6 @@ interface ServerConfig {
147
83
  jsonrpc?: JsonRpcConfig | null;
148
84
  }
149
85
 
150
- interface ServerOptions {
151
- host?: string;
152
- port?: number;
153
- }
154
- declare function runServer(app: SpikardApp, config?: ServerConfig | ServerOptions): void;
155
-
156
- type DependencyValue = unknown;
157
- type DependencyFactory = (dependencies: Record<string, DependencyValue>) => MaybePromise<DependencyValue>;
158
- interface DependencyOptions {
159
- dependsOn?: string[];
160
- singleton?: boolean;
161
- cacheable?: boolean;
162
- }
163
- interface DependencyDescriptor {
164
- isFactory: boolean;
165
- value?: DependencyValue | undefined;
166
- factory?: DependencyFactory | undefined;
167
- dependsOn: string[];
168
- singleton: boolean;
169
- cacheable: boolean;
170
- }
171
- type LifecycleHookPayload = Request | StructuredHandlerResponse;
172
- type LifecycleHookFunction = (payload: LifecycleHookPayload) => MaybePromise<LifecycleHookPayload>;
173
- interface LifecycleHooks {
174
- onRequest: LifecycleHookFunction[];
175
- preValidation: LifecycleHookFunction[];
176
- preHandler: LifecycleHookFunction[];
177
- onResponse: LifecycleHookFunction[];
178
- onError: LifecycleHookFunction[];
179
- }
180
- declare class Spikard implements SpikardApp {
181
- routes: RouteMetadata[];
182
- handlers: Record<string, HandlerFunction | NativeHandlerFunction>;
183
- websocketRoutes: RouteMetadata[];
184
- websocketHandlers: Record<string, Record<string, unknown>>;
185
- lifecycleHooks: LifecycleHooks;
186
- dependencies: Record<string, DependencyDescriptor>;
187
- addRoute(metadata: RouteMetadata, handler: HandlerFunction | NativeHandlerFunction): void;
188
- websocket(path: string, handler: WebSocketHandler, options?: WebSocketOptions): void;
189
- run(options?: ServerOptions): void;
190
- onRequest(hook: LifecycleHookFunction): LifecycleHookFunction;
191
- preValidation(hook: LifecycleHookFunction): LifecycleHookFunction;
192
- preHandler(hook: LifecycleHookFunction): LifecycleHookFunction;
193
- onResponse(hook: LifecycleHookFunction): LifecycleHookFunction;
194
- onError(hook: LifecycleHookFunction): LifecycleHookFunction;
195
- provide(key: string, valueOrFactory: DependencyValue | DependencyFactory, options?: DependencyOptions): this;
196
- getLifecycleHooks(): LifecycleHooks;
197
- }
198
-
199
- declare function run(work: () => void | Promise<void>): void;
200
-
201
- declare const background_run: typeof run;
202
- declare namespace background {
203
- export { background_run as run };
204
- }
205
-
206
86
  type GrpcMetadata = Record<string, string>;
207
87
  interface GrpcRequest {
208
88
  serviceName: string;
@@ -214,9 +94,37 @@ interface GrpcResponse {
214
94
  payload: Buffer;
215
95
  metadata?: GrpcMetadata;
216
96
  }
97
+ interface GrpcClientStreamRequest {
98
+ serviceName: string;
99
+ methodName: string;
100
+ metadata: GrpcMetadata;
101
+ messages: Buffer[];
102
+ }
103
+ interface GrpcServerStreamResponse {
104
+ messages: Buffer[];
105
+ }
106
+ interface GrpcBidiStreamRequest {
107
+ serviceName: string;
108
+ methodName: string;
109
+ metadata: GrpcMetadata;
110
+ messages: Buffer[];
111
+ }
112
+ interface GrpcBidiStreamResponse {
113
+ messages: Buffer[];
114
+ metadata?: GrpcMetadata;
115
+ }
217
116
  interface GrpcHandler {
218
117
  handleRequest(request: GrpcRequest): Promise<GrpcResponse>;
219
118
  }
119
+ interface GrpcServerStreamingHandler {
120
+ handleServerStream(request: GrpcRequest): Promise<GrpcServerStreamResponse>;
121
+ }
122
+ interface GrpcClientStreamingHandler {
123
+ handleClientStream(request: GrpcClientStreamRequest): Promise<GrpcResponse>;
124
+ }
125
+ interface GrpcBidirectionalStreamingHandler {
126
+ handleBidiStream(request: GrpcBidiStreamRequest): Promise<GrpcBidiStreamResponse>;
127
+ }
220
128
  declare enum GrpcStatusCode {
221
129
  OK = 0,
222
130
  CANCELLED = 1,
@@ -240,9 +148,29 @@ declare class GrpcError extends Error {
240
148
  readonly code: GrpcStatusCode;
241
149
  constructor(code: GrpcStatusCode, message: string);
242
150
  }
243
- interface GrpcServiceConfig {
151
+ type GrpcRpcMode = "unary" | "serverStreaming" | "clientStreaming" | "bidirectionalStreaming";
152
+ type GrpcMethodHandler = GrpcHandler | GrpcServerStreamingHandler | GrpcClientStreamingHandler | GrpcBidirectionalStreamingHandler;
153
+ interface GrpcMethodConfig {
244
154
  serviceName: string;
245
- handler: GrpcHandler;
155
+ methodName: string;
156
+ rpcMode: GrpcRpcMode;
157
+ handler: GrpcMethodHandler;
158
+ }
159
+ declare class GrpcService {
160
+ private readonly methods;
161
+ private methodKey;
162
+ private registerMethod;
163
+ registerUnary(serviceName: string, methodName: string, handler: GrpcHandler): this;
164
+ registerServerStreaming(serviceName: string, methodName: string, handler: GrpcServerStreamingHandler): this;
165
+ registerClientStreaming(serviceName: string, methodName: string, handler: GrpcClientStreamingHandler): this;
166
+ registerBidirectionalStreaming(serviceName: string, methodName: string, handler: GrpcBidirectionalStreamingHandler): this;
167
+ unregister(serviceName: string, methodName: string): void;
168
+ getMethod(serviceName: string, methodName: string): GrpcMethodConfig | undefined;
169
+ serviceNames(): string[];
170
+ methodNames(serviceName: string): string[];
171
+ hasMethod(serviceName: string, methodName: string): boolean;
172
+ entries(): GrpcMethodConfig[];
173
+ handleRequest(request: GrpcRequest): Promise<GrpcResponse>;
246
174
  }
247
175
  type UnaryHandlerResult<TResponse> = TResponse | {
248
176
  response: TResponse;
@@ -257,6 +185,128 @@ declare function createUnaryHandler<TRequest, TResponse>(methodName: string, han
257
185
  }): GrpcHandler;
258
186
  declare function createServiceHandler(methods: Record<string, GrpcHandler>): GrpcHandler;
259
187
 
188
+ interface StreamingResponseInit {
189
+ statusCode?: number;
190
+ headers?: Record<string, string>;
191
+ }
192
+ declare const STREAM_HANDLE_PROP: "__spikard_stream_handle";
193
+ type StreamChunk = JsonValue | string | Buffer | Uint8Array | ArrayBuffer | ArrayBufferView | null | undefined;
194
+ type ChunkIterator = AsyncIterator<StreamChunk> & AsyncIterable<StreamChunk>;
195
+ type StreamingHandle = {
196
+ kind: "native";
197
+ handle: number;
198
+ init: StreamingResponseInit;
199
+ } | {
200
+ kind: "js";
201
+ iterator: ChunkIterator;
202
+ init: StreamingResponseInit;
203
+ };
204
+ declare class StreamingResponse {
205
+ readonly [STREAM_HANDLE_PROP]: StreamingHandle;
206
+ constructor(stream: AsyncIterable<StreamChunk> | AsyncIterator<StreamChunk>, init?: StreamingResponseInit);
207
+ }
208
+
209
+ type JsonPrimitive = string | number | boolean | null;
210
+ type JsonValue = JsonPrimitive | JsonValue[] | {
211
+ [Key in string]: JsonValue;
212
+ };
213
+ type JsonRecord = Record<string, JsonValue>;
214
+ type MaybePromise<T> = T | Promise<T>;
215
+ interface Base64EncodedBody {
216
+ __spikard_base64__: string;
217
+ }
218
+ type HandlerBody = JsonValue | Base64EncodedBody | null;
219
+ interface StructuredHandlerResponse {
220
+ status?: number;
221
+ statusCode?: number;
222
+ headers?: Record<string, string>;
223
+ body?: HandlerBody;
224
+ }
225
+ type HandlerResult = StructuredHandlerResponse | JsonValue | StreamingResponse | undefined;
226
+ type HandlerFunction<TReturn extends HandlerResult = HandlerResult> = (request: Request) => MaybePromise<TReturn>;
227
+ type NativeHandlerFunction<TReturn extends HandlerResult = HandlerResult> = (requestJson: string) => MaybePromise<TReturn | string>;
228
+ type WebSocketHandler = (message: unknown) => MaybePromise<unknown>;
229
+ interface WebSocketOptions {
230
+ onConnect?: () => MaybePromise<void>;
231
+ onDisconnect?: () => MaybePromise<void>;
232
+ messageSchema?: unknown;
233
+ responseSchema?: unknown;
234
+ handlerName?: string;
235
+ }
236
+
237
+ interface Request {
238
+ method: string;
239
+ path: string;
240
+ params: Record<string, string>;
241
+ pathParams: Record<string, string>;
242
+ query: Record<string, string>;
243
+ queryParams: Record<string, string>;
244
+ headers: Record<string, string>;
245
+ cookies: Record<string, string>;
246
+ body: Buffer | null;
247
+ dependencies: Record<string, unknown> | undefined;
248
+ json<T = JsonValue>(): T;
249
+ form(): Record<string, string>;
250
+ }
251
+
252
+ type DependencyValue = unknown;
253
+ type DependencyFactory = (dependencies: Record<string, DependencyValue>) => MaybePromise<DependencyValue>;
254
+ interface DependencyOptions {
255
+ dependsOn?: string[];
256
+ singleton?: boolean;
257
+ cacheable?: boolean;
258
+ }
259
+ interface DependencyDescriptor {
260
+ isFactory: boolean;
261
+ value?: DependencyValue | undefined;
262
+ factory?: DependencyFactory | undefined;
263
+ dependsOn: string[];
264
+ singleton: boolean;
265
+ cacheable: boolean;
266
+ }
267
+ type LifecycleHookPayload = Request | StructuredHandlerResponse;
268
+ type LifecycleHookFunction = (payload: LifecycleHookPayload) => MaybePromise<LifecycleHookPayload>;
269
+ interface LifecycleHooks {
270
+ onRequest: LifecycleHookFunction[];
271
+ preValidation: LifecycleHookFunction[];
272
+ preHandler: LifecycleHookFunction[];
273
+ onResponse: LifecycleHookFunction[];
274
+ onError: LifecycleHookFunction[];
275
+ }
276
+ declare class Spikard implements SpikardApp {
277
+ routes: RouteMetadata[];
278
+ handlers: Record<string, HandlerFunction | NativeHandlerFunction>;
279
+ websocketRoutes: RouteMetadata[];
280
+ websocketHandlers: Record<string, Record<string, unknown>>;
281
+ grpcMethods: GrpcMethodRegistration[];
282
+ grpcHandlers: Record<string, Record<string, unknown>>;
283
+ lifecycleHooks: LifecycleHooks;
284
+ dependencies: Record<string, DependencyDescriptor>;
285
+ addRoute(metadata: RouteMetadata, handler: HandlerFunction | NativeHandlerFunction): void;
286
+ websocket(path: string, handler: WebSocketHandler, options?: WebSocketOptions): void;
287
+ addGrpcUnary(serviceName: string, methodName: string, handler: GrpcHandler): this;
288
+ addGrpcServerStreaming(serviceName: string, methodName: string, handler: GrpcServerStreamingHandler): this;
289
+ addGrpcClientStreaming(serviceName: string, methodName: string, handler: GrpcClientStreamingHandler): this;
290
+ addGrpcBidirectionalStreaming(serviceName: string, methodName: string, handler: GrpcBidirectionalStreamingHandler): this;
291
+ private registerGrpcMethod;
292
+ useGrpc(service: GrpcService): this;
293
+ run(config?: ServerConfig): void;
294
+ onRequest(hook: LifecycleHookFunction): LifecycleHookFunction;
295
+ preValidation(hook: LifecycleHookFunction): LifecycleHookFunction;
296
+ preHandler(hook: LifecycleHookFunction): LifecycleHookFunction;
297
+ onResponse(hook: LifecycleHookFunction): LifecycleHookFunction;
298
+ onError(hook: LifecycleHookFunction): LifecycleHookFunction;
299
+ provide(key: string, valueOrFactory: DependencyValue | DependencyFactory, options?: DependencyOptions): this;
300
+ getLifecycleHooks(): LifecycleHooks;
301
+ }
302
+
303
+ declare function run(work: () => void | Promise<void>): void;
304
+
305
+ declare const background_run: typeof run;
306
+ declare namespace background {
307
+ export { background_run as run };
308
+ }
309
+
260
310
  declare function wrapHandler(handler: HandlerFunction): NativeHandlerFunction;
261
311
  declare function wrapBodyHandler<TBody = unknown>(handler: (body: TBody, request: Request) => MaybePromise<HandlerResult>): NativeHandlerFunction;
262
312
 
@@ -280,6 +330,8 @@ declare function put(path: string, options?: Omit<RouteOptions, "methods">): (ha
280
330
  declare function del(path: string, options?: Omit<RouteOptions, "methods">): (handler: RouteHandler) => RouteHandler;
281
331
  declare function patch(path: string, options?: Omit<RouteOptions, "methods">): (handler: RouteHandler) => RouteHandler;
282
332
 
333
+ declare function runServer(app: SpikardApp, config?: ServerConfig): void;
334
+
283
335
  interface NativeTestResponse {
284
336
  statusCode: number;
285
337
  headers(): Record<string, string>;
@@ -402,14 +454,22 @@ interface RouteMetadata {
402
454
  is_async: boolean;
403
455
  cors?: CorsConfig | undefined;
404
456
  }
457
+ interface GrpcMethodRegistration {
458
+ serviceName: string;
459
+ methodName: string;
460
+ rpcMode: GrpcRpcMode;
461
+ handlerName: string;
462
+ }
405
463
  interface SpikardApp {
406
464
  routes: RouteMetadata[];
407
465
  handlers: Record<string, HandlerFunction | NativeHandlerFunction>;
408
466
  websocketRoutes?: RouteMetadata[];
409
467
  websocketHandlers?: Record<string, Record<string, unknown>>;
468
+ grpcMethods?: GrpcMethodRegistration[];
469
+ grpcHandlers?: Record<string, Record<string, unknown>>;
410
470
  config?: ServerConfig;
411
471
  lifecycleHooks?: Partial<LifecycleHooks>;
412
472
  dependencies?: Record<string, unknown>;
413
473
  }
414
474
 
415
- export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcRequest, type GrpcResponse, type GrpcServiceConfig, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonRpcConfig, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, type ServerOptions, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
475
+ export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, type GrpcBidiStreamRequest, type GrpcBidiStreamResponse, type GrpcBidirectionalStreamingHandler, type GrpcClientStreamRequest, type GrpcClientStreamingHandler, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcMethodConfig, type GrpcMethodHandler, type GrpcMethodRegistration, type GrpcRequest, type GrpcResponse, type GrpcRpcMode, type GrpcServerStreamResponse, type GrpcServerStreamingHandler, GrpcService, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonRpcConfig, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  GrpcError: () => GrpcError,
34
+ GrpcService: () => GrpcService,
34
35
  GrpcStatusCode: () => GrpcStatusCode,
35
36
  Spikard: () => Spikard,
36
37
  StreamingResponse: () => StreamingResponse,
@@ -51,7 +52,7 @@ __export(index_exports, {
51
52
  });
52
53
  module.exports = __toCommonJS(index_exports);
53
54
 
54
- // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
55
+ // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
55
56
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
56
57
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
57
58
 
@@ -599,6 +600,8 @@ var Spikard = class {
599
600
  handlers = {};
600
601
  websocketRoutes = [];
601
602
  websocketHandlers = {};
603
+ grpcMethods = [];
604
+ grpcHandlers = {};
602
605
  lifecycleHooks = {
603
606
  onRequest: [],
604
607
  preValidation: [],
@@ -642,13 +645,111 @@ var Spikard = class {
642
645
  this.websocketRoutes.push(route2);
643
646
  this.websocketHandlers[handlerName] = handlerWrapper;
644
647
  }
648
+ /**
649
+ * Register a unary gRPC method on the application.
650
+ *
651
+ * @param serviceName - Fully-qualified service name
652
+ * @param methodName - gRPC method name
653
+ * @param handler - gRPC handler implementation
654
+ * @returns The application for chaining
655
+ */
656
+ addGrpcUnary(serviceName, methodName, handler) {
657
+ if (typeof handler?.handleRequest !== "function") {
658
+ throw new TypeError("Unary handler must implement handleRequest(request)");
659
+ }
660
+ return this.registerGrpcMethod(serviceName, methodName, "unary", {
661
+ handleRequest: (request) => handler.handleRequest(request)
662
+ });
663
+ }
664
+ addGrpcServerStreaming(serviceName, methodName, handler) {
665
+ if (typeof handler?.handleServerStream !== "function") {
666
+ throw new TypeError("Server-streaming handler must implement handleServerStream(request)");
667
+ }
668
+ return this.registerGrpcMethod(serviceName, methodName, "serverStreaming", {
669
+ handleServerStream: (request) => handler.handleServerStream(request)
670
+ });
671
+ }
672
+ addGrpcClientStreaming(serviceName, methodName, handler) {
673
+ if (typeof handler?.handleClientStream !== "function") {
674
+ throw new TypeError("Client-streaming handler must implement handleClientStream(request)");
675
+ }
676
+ return this.registerGrpcMethod(serviceName, methodName, "clientStreaming", {
677
+ handleClientStream: (request) => handler.handleClientStream(request)
678
+ });
679
+ }
680
+ addGrpcBidirectionalStreaming(serviceName, methodName, handler) {
681
+ if (typeof handler?.handleBidiStream !== "function") {
682
+ throw new TypeError("Bidirectional-streaming handler must implement handleBidiStream(request)");
683
+ }
684
+ return this.registerGrpcMethod(serviceName, methodName, "bidirectionalStreaming", {
685
+ handleBidiStream: (request) => handler.handleBidiStream(request)
686
+ });
687
+ }
688
+ registerGrpcMethod(serviceName, methodName, rpcMode, handlerWrapper) {
689
+ if (!serviceName) {
690
+ throw new Error("Service name cannot be empty");
691
+ }
692
+ if (!methodName) {
693
+ throw new Error("Method name cannot be empty");
694
+ }
695
+ const previous = this.grpcMethods.find(
696
+ (entry) => entry.serviceName === serviceName && entry.methodName === methodName
697
+ );
698
+ if (previous) {
699
+ delete this.grpcHandlers[previous.handlerName];
700
+ }
701
+ const handlerName = `grpc_${this.grpcMethods.length}_${serviceName}_${methodName}`.replace(/[^a-zA-Z0-9_]/g, "_");
702
+ this.grpcHandlers[handlerName] = handlerWrapper;
703
+ this.grpcMethods = this.grpcMethods.filter(
704
+ (entry) => !(entry.serviceName === serviceName && entry.methodName === methodName)
705
+ );
706
+ this.grpcMethods.push({ serviceName, methodName, rpcMode, handlerName });
707
+ return this;
708
+ }
709
+ /**
710
+ * Mount all handlers from a gRPC service registry on the application.
711
+ *
712
+ * @param service - Registry containing one or more service methods
713
+ * @returns The application for chaining
714
+ */
715
+ useGrpc(service) {
716
+ for (const method of service.entries()) {
717
+ switch (method.rpcMode) {
718
+ case "unary":
719
+ this.addGrpcUnary(method.serviceName, method.methodName, method.handler);
720
+ break;
721
+ case "serverStreaming":
722
+ this.addGrpcServerStreaming(
723
+ method.serviceName,
724
+ method.methodName,
725
+ method.handler
726
+ );
727
+ break;
728
+ case "clientStreaming":
729
+ this.addGrpcClientStreaming(
730
+ method.serviceName,
731
+ method.methodName,
732
+ method.handler
733
+ );
734
+ break;
735
+ case "bidirectionalStreaming":
736
+ this.addGrpcBidirectionalStreaming(
737
+ method.serviceName,
738
+ method.methodName,
739
+ method.handler
740
+ );
741
+ break;
742
+ }
743
+ }
744
+ return this;
745
+ }
645
746
  /**
646
747
  * Run the server
647
748
  *
648
- * @param options - Server configuration
749
+ * @param config - Server configuration
649
750
  */
650
- run(options = {}) {
651
- runServer(this, options);
751
+ run(config = {}) {
752
+ runServer(this, config);
652
753
  }
653
754
  /**
654
755
  * Register an onRequest lifecycle hook
@@ -874,6 +975,135 @@ var GrpcError = class extends Error {
874
975
  this.name = "GrpcError";
875
976
  }
876
977
  };
978
+ var GrpcService = class {
979
+ methods = /* @__PURE__ */ new Map();
980
+ methodKey(serviceName, methodName) {
981
+ return `${serviceName}/${methodName}`;
982
+ }
983
+ registerMethod(config) {
984
+ if (!config.serviceName) {
985
+ throw new Error("Service name cannot be empty");
986
+ }
987
+ if (!config.methodName) {
988
+ throw new Error("Method name cannot be empty");
989
+ }
990
+ switch (config.rpcMode) {
991
+ case "unary":
992
+ if (typeof config.handler?.handleRequest !== "function") {
993
+ throw new TypeError("Unary handler must implement handleRequest(request)");
994
+ }
995
+ break;
996
+ case "serverStreaming":
997
+ if (typeof config.handler?.handleServerStream !== "function") {
998
+ throw new TypeError("Server-streaming handler must implement handleServerStream(request)");
999
+ }
1000
+ break;
1001
+ case "clientStreaming":
1002
+ if (typeof config.handler?.handleClientStream !== "function") {
1003
+ throw new TypeError("Client-streaming handler must implement handleClientStream(request)");
1004
+ }
1005
+ break;
1006
+ case "bidirectionalStreaming":
1007
+ if (typeof config.handler?.handleBidiStream !== "function") {
1008
+ throw new TypeError("Bidirectional-streaming handler must implement handleBidiStream(request)");
1009
+ }
1010
+ break;
1011
+ }
1012
+ this.methods.set(this.methodKey(config.serviceName, config.methodName), config);
1013
+ return this;
1014
+ }
1015
+ /**
1016
+ * Register a unary handler for a fully-qualified service method.
1017
+ *
1018
+ * @param serviceName - Service name such as `mypackage.UserService`
1019
+ * @param methodName - Method name such as `GetUser`
1020
+ * @param handler - Handler implementation for that method
1021
+ * @returns The registry for chaining
1022
+ */
1023
+ registerUnary(serviceName, methodName, handler) {
1024
+ return this.registerMethod({ serviceName, methodName, rpcMode: "unary", handler });
1025
+ }
1026
+ registerServerStreaming(serviceName, methodName, handler) {
1027
+ return this.registerMethod({ serviceName, methodName, rpcMode: "serverStreaming", handler });
1028
+ }
1029
+ registerClientStreaming(serviceName, methodName, handler) {
1030
+ return this.registerMethod({ serviceName, methodName, rpcMode: "clientStreaming", handler });
1031
+ }
1032
+ registerBidirectionalStreaming(serviceName, methodName, handler) {
1033
+ return this.registerMethod({ serviceName, methodName, rpcMode: "bidirectionalStreaming", handler });
1034
+ }
1035
+ /**
1036
+ * Remove a handler from the registry.
1037
+ *
1038
+ * @param serviceName - Fully-qualified service name
1039
+ * @param methodName - Method name
1040
+ */
1041
+ unregister(serviceName, methodName) {
1042
+ if (!this.methods.delete(this.methodKey(serviceName, methodName))) {
1043
+ throw new Error(`No handler registered for method: ${serviceName}/${methodName}`);
1044
+ }
1045
+ }
1046
+ /**
1047
+ * Get the registration for a service method.
1048
+ *
1049
+ * @param serviceName - Fully-qualified service name
1050
+ * @param methodName - Method name
1051
+ * @returns The registered method configuration, if present
1052
+ */
1053
+ getMethod(serviceName, methodName) {
1054
+ return this.methods.get(this.methodKey(serviceName, methodName));
1055
+ }
1056
+ /**
1057
+ * List all registered service names.
1058
+ *
1059
+ * @returns Fully-qualified service names
1060
+ */
1061
+ serviceNames() {
1062
+ return Array.from(new Set(Array.from(this.methods.values(), (entry) => entry.serviceName)));
1063
+ }
1064
+ methodNames(serviceName) {
1065
+ return Array.from(this.methods.values()).filter((entry) => entry.serviceName === serviceName).map((entry) => entry.methodName);
1066
+ }
1067
+ /**
1068
+ * Check whether a specific service method is registered.
1069
+ *
1070
+ * @param serviceName - Fully-qualified service name
1071
+ * @param methodName - Method name
1072
+ * @returns True when a handler is registered for the method
1073
+ */
1074
+ hasMethod(serviceName, methodName) {
1075
+ return this.methods.has(this.methodKey(serviceName, methodName));
1076
+ }
1077
+ /**
1078
+ * Return registered method entries.
1079
+ */
1080
+ entries() {
1081
+ return Array.from(this.methods.values());
1082
+ }
1083
+ /**
1084
+ * Route a unary request to the registered method handler.
1085
+ *
1086
+ * @param request - Incoming gRPC request
1087
+ * @returns Promise resolving to the handler response
1088
+ * @throws GrpcError when no service is registered
1089
+ */
1090
+ async handleRequest(request) {
1091
+ const method = this.getMethod(request.serviceName, request.methodName);
1092
+ if (!method) {
1093
+ throw new GrpcError(
1094
+ 12 /* UNIMPLEMENTED */,
1095
+ `No handler registered for method: ${request.serviceName}/${request.methodName}`
1096
+ );
1097
+ }
1098
+ if (method.rpcMode !== "unary") {
1099
+ throw new GrpcError(
1100
+ 12 /* UNIMPLEMENTED */,
1101
+ `Method ${request.serviceName}/${request.methodName} is registered as ${method.rpcMode}`
1102
+ );
1103
+ }
1104
+ return method.handler.handleRequest(request);
1105
+ }
1106
+ };
877
1107
  function createUnaryHandler(methodName, handler, requestType, responseType) {
878
1108
  return {
879
1109
  async handleRequest(request) {
@@ -1807,6 +2037,7 @@ var TestClient = class {
1807
2037
  // Annotate the CommonJS export names for ESM import in node:
1808
2038
  0 && (module.exports = {
1809
2039
  GrpcError,
2040
+ GrpcService,
1810
2041
  GrpcStatusCode,
1811
2042
  Spikard,
1812
2043
  StreamingResponse,