@richie-rpc/server 1.2.8 → 1.2.10

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/README.md CHANGED
@@ -374,7 +374,7 @@ const wsRouter = createWebSocketRouter(
374
374
  JSON.stringify({
375
375
  type: 'message',
376
376
  payload: { userId: 'user1', text: msg.payload.text },
377
- })
377
+ }),
378
378
  );
379
379
  }
380
380
  },
@@ -393,7 +393,7 @@ const wsRouter = createWebSocketRouter(
393
393
  {
394
394
  // Pass rawWebSocket for type inference - ws.raw will be typed as BunWS
395
395
  rawWebSocket: {} as BunWS,
396
- }
396
+ },
397
397
  );
398
398
  ```
399
399
 
@@ -448,7 +448,7 @@ const wsRouter = createWebSocketRouter(
448
448
  userId: z.string(),
449
449
  sessionId: z.string(),
450
450
  }),
451
- }
451
+ },
452
452
  );
453
453
  ```
454
454
 
@@ -55,14 +55,17 @@ __export(exports_server, {
55
55
  });
56
56
  module.exports = __toCommonJS(exports_server);
57
57
  var import_core = require("@richie-rpc/core");
58
+ var import_zod = require("zod");
58
59
  __reExport(exports_server, require("./websocket.cjs"), module.exports);
59
60
  class ValidationError extends Error {
60
61
  field;
61
- issues;
62
- constructor(field, issues, message) {
63
- super(message || `Validation failed for ${field}`);
62
+ zodError;
63
+ constructor(field, zodError) {
64
+ const pretty = import_zod.z.prettifyError(zodError);
65
+ super(`Validation failed for ${field}:
66
+ ${pretty}`);
64
67
  this.field = field;
65
- this.issues = issues;
68
+ this.zodError = zodError;
66
69
  this.name = "ValidationError";
67
70
  }
68
71
  }
@@ -83,7 +86,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
83
86
  if (endpoint.params) {
84
87
  const result = endpoint.params.safeParse(pathParams);
85
88
  if (!result.success) {
86
- throw new ValidationError("params", result.error.issues);
89
+ throw new ValidationError("params", result.error);
87
90
  }
88
91
  params = result.data;
89
92
  }
@@ -92,7 +95,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
92
95
  const queryData = import_core.parseQuery(url.searchParams);
93
96
  const result = endpoint.query.safeParse(queryData);
94
97
  if (!result.success) {
95
- throw new ValidationError("query", result.error.issues);
98
+ throw new ValidationError("query", result.error);
96
99
  }
97
100
  query = result.data;
98
101
  }
@@ -104,7 +107,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
104
107
  });
105
108
  const result = endpoint.headers.safeParse(headersObj);
106
109
  if (!result.success) {
107
- throw new ValidationError("headers", result.error.issues);
110
+ throw new ValidationError("headers", result.error);
108
111
  }
109
112
  headers = result.data;
110
113
  }
@@ -122,7 +125,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
122
125
  }
123
126
  const result = endpoint.body.safeParse(bodyData);
124
127
  if (!result.success) {
125
- throw new ValidationError("body", result.error.issues);
128
+ throw new ValidationError("body", result.error);
126
129
  }
127
130
  body = result.data;
128
131
  }
@@ -134,7 +137,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
134
137
  if (endpoint.params) {
135
138
  const result = endpoint.params.safeParse(pathParams);
136
139
  if (!result.success) {
137
- throw new ValidationError("params", result.error.issues);
140
+ throw new ValidationError("params", result.error);
138
141
  }
139
142
  params = result.data;
140
143
  }
@@ -143,7 +146,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
143
146
  const queryData = import_core.parseQuery(url.searchParams);
144
147
  const result = endpoint.query.safeParse(queryData);
145
148
  if (!result.success) {
146
- throw new ValidationError("query", result.error.issues);
149
+ throw new ValidationError("query", result.error);
147
150
  }
148
151
  query = result.data;
149
152
  }
@@ -155,7 +158,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
155
158
  });
156
159
  const result = endpoint.headers.safeParse(headersObj);
157
160
  if (!result.success) {
158
- throw new ValidationError("headers", result.error.issues);
161
+ throw new ValidationError("headers", result.error);
159
162
  }
160
163
  headers = result.data;
161
164
  }
@@ -173,7 +176,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
173
176
  }
174
177
  const result = endpoint.body.safeParse(bodyData);
175
178
  if (!result.success) {
176
- throw new ValidationError("body", result.error.issues);
179
+ throw new ValidationError("body", result.error);
177
180
  }
178
181
  body = result.data;
179
182
  }
@@ -185,7 +188,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
185
188
  if (endpoint.params) {
186
189
  const result = endpoint.params.safeParse(pathParams);
187
190
  if (!result.success) {
188
- throw new ValidationError("params", result.error.issues);
191
+ throw new ValidationError("params", result.error);
189
192
  }
190
193
  params = result.data;
191
194
  }
@@ -194,7 +197,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
194
197
  const queryData = import_core.parseQuery(url.searchParams);
195
198
  const result = endpoint.query.safeParse(queryData);
196
199
  if (!result.success) {
197
- throw new ValidationError("query", result.error.issues);
200
+ throw new ValidationError("query", result.error);
198
201
  }
199
202
  query = result.data;
200
203
  }
@@ -206,7 +209,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
206
209
  });
207
210
  const result = endpoint.headers.safeParse(headersObj);
208
211
  if (!result.success) {
209
- throw new ValidationError("headers", result.error.issues);
212
+ throw new ValidationError("headers", result.error);
210
213
  }
211
214
  headers = result.data;
212
215
  }
@@ -218,7 +221,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
218
221
  if (endpoint.params) {
219
222
  const result = endpoint.params.safeParse(pathParams);
220
223
  if (!result.success) {
221
- throw new ValidationError("params", result.error.issues);
224
+ throw new ValidationError("params", result.error);
222
225
  }
223
226
  params = result.data;
224
227
  }
@@ -227,7 +230,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
227
230
  const queryData = import_core.parseQuery(url.searchParams);
228
231
  const result = endpoint.query.safeParse(queryData);
229
232
  if (!result.success) {
230
- throw new ValidationError("query", result.error.issues);
233
+ throw new ValidationError("query", result.error);
231
234
  }
232
235
  query = result.data;
233
236
  }
@@ -239,7 +242,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
239
242
  });
240
243
  const result = endpoint.headers.safeParse(headersObj);
241
244
  if (!result.success) {
242
- throw new ValidationError("headers", result.error.issues);
245
+ throw new ValidationError("headers", result.error);
243
246
  }
244
247
  headers = result.data;
245
248
  }
@@ -251,7 +254,7 @@ function createResponse(endpoint, handlerResponse) {
251
254
  if (responseSchema) {
252
255
  const result = responseSchema.safeParse(body);
253
256
  if (!result.success) {
254
- throw new ValidationError(`response[${String(status)}]`, result.error.issues);
257
+ throw new ValidationError(`response[${String(status)}]`, result.error);
255
258
  }
256
259
  }
257
260
  const responseHeaders = new Headers(customHeaders);
@@ -402,6 +405,13 @@ class Router {
402
405
  constructor(contract, handlers, options) {
403
406
  this.contract = contract;
404
407
  this.handlers = handlers;
408
+ for (const [name, endpoint] of Object.entries(contract)) {
409
+ try {
410
+ import_core.validatePathPattern(endpoint.path);
411
+ } catch (error) {
412
+ throw new Error(`Invalid path in endpoint "${name}": ${error.message}`);
413
+ }
414
+ }
405
415
  const bp = options?.basePath || "";
406
416
  if (bp) {
407
417
  this.basePath = bp.startsWith("/") ? bp : `/${bp}`;
@@ -475,4 +485,4 @@ function createRouter(contract, handlers, options) {
475
485
  }
476
486
  })
477
487
 
478
- //# debugId=BC7D8AAEE9E9006464756E2164756E21
488
+ //# debugId=38E2BFF0734E74E464756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Contract,\n DownloadEndpointDefinition,\n EndpointDefinition,\n ExtractBody,\n ExtractChunk,\n ExtractFinalResponse,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n ExtractSSEEventData,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport { formDataToObject, matchPath, parseQuery, Status } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n// Re-export Status for convenience\nexport { Status };\n\n// Handler input types (for standard endpoints)\nexport type HandlerInput<T extends StandardEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n};\n\n// Handler response type (for standard endpoints)\nexport type HandlerResponse<T extends StandardEndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n body: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n headers?: Record<string, string>;\n };\n}[keyof T['responses']];\n\n// Handler function type (for standard endpoints)\nexport type Handler<T extends StandardEndpointDefinition, C = unknown> = (\n input: HandlerInput<T, C>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// ============================================\n// Streaming Endpoint Types\n// ============================================\n\n/**\n * Emitter for streaming responses - push-based API\n */\nexport interface StreamEmitter<T extends StreamingEndpointDefinition> {\n /** Send a chunk to the client */\n send(chunk: ExtractChunk<T>): void;\n /** Close the stream with optional final response */\n close(final?: ExtractFinalResponse<T>): void;\n /** Check if stream is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for streaming endpoints\n */\nexport type StreamingHandlerInput<T extends StreamingEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n stream: StreamEmitter<T>;\n};\n\n/**\n * Handler function type for streaming endpoints\n */\nexport type StreamingHandler<T extends StreamingEndpointDefinition, C = unknown> = (\n input: StreamingHandlerInput<T, C>,\n) => void | Promise<void>;\n\n// ============================================\n// SSE Endpoint Types\n// ============================================\n\n/**\n * Emitter for SSE responses\n */\nexport interface SSEEmitter<T extends SSEEndpointDefinition> {\n /** Send an event to the client */\n send<K extends keyof T['events']>(\n event: K,\n data: ExtractSSEEventData<T, K>,\n options?: { id?: string },\n ): void;\n /** Close the connection */\n close(): void;\n /** Check if connection is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for SSE endpoints\n */\nexport type SSEHandlerInput<T extends SSEEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n emitter: SSEEmitter<T>;\n /** AbortSignal for detecting client disconnect */\n signal: AbortSignal;\n};\n\n/**\n * Handler function type for SSE endpoints\n * Returns an optional cleanup function\n */\nexport type SSEHandler<T extends SSEEndpointDefinition, C = unknown> = (\n input: SSEHandlerInput<T, C>,\n) => void | (() => void) | Promise<void | (() => void)>;\n\n// ============================================\n// Download Endpoint Types\n// ============================================\n\n/**\n * Handler input for download endpoints\n */\nexport type DownloadHandlerInput<T extends DownloadEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n};\n\n/**\n * Handler response for download endpoints\n * Success (200) returns File, errors return typed response\n */\nexport type DownloadHandlerResponse<T extends DownloadEndpointDefinition> =\n | { status: 200; body: File; headers?: Record<string, string> }\n | (T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? {\n [S in keyof T['errorResponses']]: {\n status: S;\n body: T['errorResponses'][S] extends z.ZodTypeAny\n ? z.infer<T['errorResponses'][S]>\n : never;\n headers?: Record<string, string>;\n };\n }[keyof T['errorResponses']]\n : never);\n\n/**\n * Handler function type for download endpoints\n */\nexport type DownloadHandler<T extends DownloadEndpointDefinition, C = unknown> = (\n input: DownloadHandlerInput<T, C>,\n) => Promise<DownloadHandlerResponse<T>> | DownloadHandlerResponse<T>;\n\n// ============================================\n// Contract Handlers (supports all endpoint types)\n// ============================================\n\n/**\n * Contract handlers mapping - conditionally applies handler type based on endpoint type\n */\nexport type ContractHandlers<T extends Contract, C = unknown> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? Handler<T[K], C>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHandler<T[K], C>\n : T[K] extends SSEEndpointDefinition\n ? SSEHandler<T[K], C>\n : T[K] extends DownloadEndpointDefinition\n ? DownloadHandler<T[K], C>\n : never;\n};\n\n// Error classes\nexport class ValidationError extends Error {\n constructor(\n public field: string,\n public issues: z.ZodIssue[],\n message?: string,\n ) {\n super(message || `Validation failed for ${field}`);\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends Error {\n constructor(\n public path: string,\n public method: string,\n ) {\n super(`Route not found: ${method} ${path}`);\n this.name = 'RouteNotFoundError';\n }\n}\n\n/**\n * Parse and validate request data\n */\nasync function parseRequest<T extends StandardEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<HandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error.issues);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as HandlerInput<T, C>;\n}\n\n/**\n * Parse and validate request data for streaming endpoints\n */\nasync function parseStreamingRequest<T extends StreamingEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<StreamingHandlerInput<T, C>, 'stream'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error.issues);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as Omit<\n StreamingHandlerInput<T, C>,\n 'stream'\n >;\n}\n\n/**\n * Parse and validate request data for SSE endpoints\n */\nasync function parseSSERequest<T extends SSEEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<SSEHandlerInput<T, C>, 'emitter' | 'signal'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // SSE endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as Omit<\n SSEHandlerInput<T, C>,\n 'emitter' | 'signal'\n >;\n}\n\n/**\n * Parse and validate request data for download endpoints\n */\nasync function parseDownloadRequest<T extends DownloadEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<DownloadHandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Download endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as DownloadHandlerInput<T, C>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends StandardEndpointDefinition>(\n endpoint: T,\n handlerResponse: HandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n\n // Validate response body\n const responseSchema = endpoint.responses[status as keyof typeof endpoint.responses];\n if (responseSchema) {\n const result = responseSchema.safeParse(body);\n if (!result.success) {\n throw new ValidationError(`response[${String(status)}]`, result.error.issues);\n }\n }\n\n // Create response headers\n const responseHeaders = new Headers(customHeaders);\n\n // Handle 204 No Content - must have no body\n if (status === 204) {\n return new Response(null, {\n status: 204,\n headers: responseHeaders,\n });\n }\n\n // For all other responses, return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create response for download endpoints\n */\nfunction createDownloadResponse<T extends DownloadEndpointDefinition>(\n _endpoint: T,\n handlerResponse: DownloadHandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n const responseHeaders = new Headers(customHeaders);\n\n // Success: return File as binary\n if (status === 200 && body instanceof File) {\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', body.type || 'application/octet-stream');\n }\n responseHeaders.set('content-length', String(body.size));\n if (!responseHeaders.has('content-disposition')) {\n const filename = encodeURIComponent(body.name);\n responseHeaders.set(\n 'content-disposition',\n `attachment; filename=\"${filename}\"; filename*=UTF-8''${filename}`,\n );\n }\n return new Response(body, { status: 200, headers: responseHeaders });\n }\n\n // Error: return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create a streaming NDJSON response\n */\nfunction createStreamingResponse<T extends StreamingEndpointDefinition>(\n handler: (stream: StreamEmitter<T>) => void | Promise<void>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let closed = false;\n\n const stream: StreamEmitter<T> = {\n send(chunk) {\n if (!closed) {\n writer.write(encoder.encode(`${JSON.stringify(chunk)}\\n`)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close(final) {\n if (!closed) {\n closed = true;\n const closeWriter = () => {\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n };\n if (final !== undefined) {\n writer\n .write(encoder.encode(`${JSON.stringify({ __final__: true, data: final })}\\n`))\n .catch(() => {\n // Stream was closed by client disconnect - ignore\n })\n .finally(closeWriter);\n } else {\n closeWriter();\n }\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler (don't await - let it run in background)\n Promise.resolve(handler(stream)).catch((err) => {\n console.error('Streaming handler error:', err);\n stream.close();\n });\n\n return new Response(readable, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Create an SSE response\n */\nfunction createSSEResponse<T extends SSEEndpointDefinition>(\n handler: (\n emitter: SSEEmitter<T>,\n signal: AbortSignal,\n ) => void | (() => void) | Promise<void | (() => void)>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n const controller = new AbortController();\n let closed = false;\n let cleanup: (() => void) | undefined;\n\n const emitter: SSEEmitter<T> = {\n send(event, data, options) {\n if (!closed) {\n let message = '';\n if (options?.id) {\n message += `id: ${options.id}\\n`;\n }\n message += `event: ${String(event)}\\n`;\n message += `data: ${JSON.stringify(data)}\\n\\n`;\n writer.write(encoder.encode(message)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close() {\n if (!closed) {\n closed = true;\n if (cleanup) cleanup();\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler and get cleanup function\n Promise.resolve(handler(emitter, controller.signal))\n .then((cleanupFn) => {\n if (typeof cleanupFn === 'function') {\n cleanup = cleanupFn;\n }\n })\n .catch((err) => {\n console.error('SSE handler error:', err);\n emitter.close();\n });\n\n // Tee the stream - one for the response, one for disconnect detection\n const [responseStream, detectStream] = readable.tee();\n\n // Handle client disconnect by detecting when the stream is cancelled\n detectStream.pipeTo(new WritableStream()).catch(() => {\n controller.abort();\n emitter.close();\n });\n\n return new Response(responseStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Router configuration options\n */\nexport interface RouterOptions<C = unknown> {\n basePath?: string;\n context?: (request: Request, routeName?: string, endpoint?: EndpointDefinition) => C | Promise<C>;\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract, C = unknown> {\n private basePath: string;\n private contextFactory?: (\n request: Request,\n routeName: string,\n endpoint: EndpointDefinition,\n ) => C | Promise<C>;\n\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n ) {\n // Normalize basePath: ensure it starts with / and doesn't end with /\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.contextFactory = options?.context;\n }\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): {\n name: keyof T;\n endpoint: EndpointDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n if (endpoint.method === method) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return { name, endpoint, params };\n }\n }\n }\n return null;\n }\n\n /**\n * Handle a request\n */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const method = request.method;\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(method, path);\n if (!match) {\n throw new RouteNotFoundError(path, method);\n }\n\n const { name, endpoint, params } = match;\n const handler = this.handlers[name];\n\n // Create context if factory is provided\n const context = this.contextFactory\n ? await this.contextFactory(request, String(name), endpoint)\n : (undefined as C);\n\n // Dispatch based on endpoint type\n if (endpoint.type === 'streaming') {\n // Parse request for streaming endpoint\n const input = await parseStreamingRequest(request, endpoint, params, context);\n return createStreamingResponse((stream) => {\n return (handler as StreamingHandler<StreamingEndpointDefinition, C>)({\n ...input,\n stream,\n });\n });\n }\n\n if (endpoint.type === 'sse') {\n // Parse request for SSE endpoint\n const input = await parseSSERequest(request, endpoint, params, context);\n return createSSEResponse((emitter, signal) => {\n return (handler as SSEHandler<SSEEndpointDefinition, C>)({\n ...input,\n emitter,\n signal,\n });\n });\n }\n\n if (endpoint.type === 'download') {\n // Parse request for download endpoint\n const input = await parseDownloadRequest(request, endpoint, params, context);\n const downloadHandler = handler as unknown as DownloadHandler<DownloadEndpointDefinition, C>;\n const response = await downloadHandler(\n input as DownloadHandlerInput<DownloadEndpointDefinition, C>,\n );\n return createDownloadResponse(\n endpoint as DownloadEndpointDefinition,\n response as DownloadHandlerResponse<DownloadEndpointDefinition>,\n );\n }\n\n // Standard endpoint\n const input = await parseRequest(request, endpoint, params, context);\n const standardHandler = handler as unknown as Handler<StandardEndpointDefinition, C>;\n const handlerResponse = await standardHandler(\n input as HandlerInput<StandardEndpointDefinition, C>,\n );\n return createResponse(\n endpoint as StandardEndpointDefinition,\n handlerResponse as HandlerResponse<StandardEndpointDefinition>,\n );\n }\n\n /**\n * Get fetch handler compatible with Bun.serve\n */\n get fetch() {\n return (request: Request) => this.handle(request);\n }\n}\n\n/**\n * Create a router from a contract and handlers\n */\nexport function createRouter<T extends Contract, C = unknown>(\n contract: T,\n handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n): Router<T, C> {\n return new Router(contract, handlers, options);\n}\n\nexport * from './websocket.cjs';\n"
5
+ "import type {\n Contract,\n DownloadEndpointDefinition,\n EndpointDefinition,\n ExtractBody,\n ExtractChunk,\n ExtractFinalResponse,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n ExtractSSEEventData,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport {\n formDataToObject,\n matchPath,\n parseQuery,\n Status,\n validatePathPattern,\n} from '@richie-rpc/core';\nimport { z } from 'zod';\n\n// Re-export Status for convenience\nexport { Status };\n\n// Handler input types (for standard endpoints)\nexport type HandlerInput<T extends StandardEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n};\n\n// Handler response type (for standard endpoints)\nexport type HandlerResponse<T extends StandardEndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n body: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n headers?: Record<string, string>;\n };\n}[keyof T['responses']];\n\n// Handler function type (for standard endpoints)\nexport type Handler<T extends StandardEndpointDefinition, C = unknown> = (\n input: HandlerInput<T, C>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// ============================================\n// Streaming Endpoint Types\n// ============================================\n\n/**\n * Emitter for streaming responses - push-based API\n */\nexport interface StreamEmitter<T extends StreamingEndpointDefinition> {\n /** Send a chunk to the client */\n send(chunk: ExtractChunk<T>): void;\n /** Close the stream with optional final response */\n close(final?: ExtractFinalResponse<T>): void;\n /** Check if stream is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for streaming endpoints\n */\nexport type StreamingHandlerInput<T extends StreamingEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n stream: StreamEmitter<T>;\n};\n\n/**\n * Handler function type for streaming endpoints\n */\nexport type StreamingHandler<T extends StreamingEndpointDefinition, C = unknown> = (\n input: StreamingHandlerInput<T, C>,\n) => void | Promise<void>;\n\n// ============================================\n// SSE Endpoint Types\n// ============================================\n\n/**\n * Emitter for SSE responses\n */\nexport interface SSEEmitter<T extends SSEEndpointDefinition> {\n /** Send an event to the client */\n send<K extends keyof T['events']>(\n event: K,\n data: ExtractSSEEventData<T, K>,\n options?: { id?: string },\n ): void;\n /** Close the connection */\n close(): void;\n /** Check if connection is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for SSE endpoints\n */\nexport type SSEHandlerInput<T extends SSEEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n emitter: SSEEmitter<T>;\n /** AbortSignal for detecting client disconnect */\n signal: AbortSignal;\n};\n\n/**\n * Handler function type for SSE endpoints\n * Returns an optional cleanup function\n */\nexport type SSEHandler<T extends SSEEndpointDefinition, C = unknown> = (\n input: SSEHandlerInput<T, C>,\n) => void | (() => void) | Promise<void | (() => void)>;\n\n// ============================================\n// Download Endpoint Types\n// ============================================\n\n/**\n * Handler input for download endpoints\n */\nexport type DownloadHandlerInput<T extends DownloadEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n};\n\n/**\n * Handler response for download endpoints\n * Success (200) returns File, errors return typed response\n */\nexport type DownloadHandlerResponse<T extends DownloadEndpointDefinition> =\n | { status: 200; body: File; headers?: Record<string, string> }\n | (T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? {\n [S in keyof T['errorResponses']]: {\n status: S;\n body: T['errorResponses'][S] extends z.ZodTypeAny\n ? z.infer<T['errorResponses'][S]>\n : never;\n headers?: Record<string, string>;\n };\n }[keyof T['errorResponses']]\n : never);\n\n/**\n * Handler function type for download endpoints\n */\nexport type DownloadHandler<T extends DownloadEndpointDefinition, C = unknown> = (\n input: DownloadHandlerInput<T, C>,\n) => Promise<DownloadHandlerResponse<T>> | DownloadHandlerResponse<T>;\n\n// ============================================\n// Contract Handlers (supports all endpoint types)\n// ============================================\n\n/**\n * Contract handlers mapping - conditionally applies handler type based on endpoint type\n */\nexport type ContractHandlers<T extends Contract, C = unknown> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? Handler<T[K], C>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHandler<T[K], C>\n : T[K] extends SSEEndpointDefinition\n ? SSEHandler<T[K], C>\n : T[K] extends DownloadEndpointDefinition\n ? DownloadHandler<T[K], C>\n : never;\n};\n\n// Error classes\nexport class ValidationError extends Error {\n constructor(\n public field: string,\n public zodError: z.ZodError<unknown>,\n ) {\n const pretty = z.prettifyError(zodError);\n super(`Validation failed for ${field}:\\n${pretty}`);\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends Error {\n constructor(\n public path: string,\n public method: string,\n ) {\n super(`Route not found: ${method} ${path}`);\n this.name = 'RouteNotFoundError';\n }\n}\n\n/**\n * Parse and validate request data\n */\nasync function parseRequest<T extends StandardEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<HandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as HandlerInput<T, C>;\n}\n\n/**\n * Parse and validate request data for streaming endpoints\n */\nasync function parseStreamingRequest<T extends StreamingEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<StreamingHandlerInput<T, C>, 'stream'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as Omit<\n StreamingHandlerInput<T, C>,\n 'stream'\n >;\n}\n\n/**\n * Parse and validate request data for SSE endpoints\n */\nasync function parseSSERequest<T extends SSEEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<SSEHandlerInput<T, C>, 'emitter' | 'signal'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // SSE endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as Omit<\n SSEHandlerInput<T, C>,\n 'emitter' | 'signal'\n >;\n}\n\n/**\n * Parse and validate request data for download endpoints\n */\nasync function parseDownloadRequest<T extends DownloadEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<DownloadHandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Download endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as DownloadHandlerInput<T, C>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends StandardEndpointDefinition>(\n endpoint: T,\n handlerResponse: HandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n\n // Validate response body\n const responseSchema = endpoint.responses[status as keyof typeof endpoint.responses];\n if (responseSchema) {\n const result = responseSchema.safeParse(body);\n if (!result.success) {\n throw new ValidationError(`response[${String(status)}]`, result.error);\n }\n }\n\n // Create response headers\n const responseHeaders = new Headers(customHeaders);\n\n // Handle 204 No Content - must have no body\n if (status === 204) {\n return new Response(null, {\n status: 204,\n headers: responseHeaders,\n });\n }\n\n // For all other responses, return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create response for download endpoints\n */\nfunction createDownloadResponse<T extends DownloadEndpointDefinition>(\n _endpoint: T,\n handlerResponse: DownloadHandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n const responseHeaders = new Headers(customHeaders);\n\n // Success: return File as binary\n if (status === 200 && body instanceof File) {\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', body.type || 'application/octet-stream');\n }\n responseHeaders.set('content-length', String(body.size));\n if (!responseHeaders.has('content-disposition')) {\n const filename = encodeURIComponent(body.name);\n responseHeaders.set(\n 'content-disposition',\n `attachment; filename=\"${filename}\"; filename*=UTF-8''${filename}`,\n );\n }\n return new Response(body, { status: 200, headers: responseHeaders });\n }\n\n // Error: return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create a streaming NDJSON response\n */\nfunction createStreamingResponse<T extends StreamingEndpointDefinition>(\n handler: (stream: StreamEmitter<T>) => void | Promise<void>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let closed = false;\n\n const stream: StreamEmitter<T> = {\n send(chunk) {\n if (!closed) {\n writer.write(encoder.encode(`${JSON.stringify(chunk)}\\n`)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close(final) {\n if (!closed) {\n closed = true;\n const closeWriter = () => {\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n };\n if (final !== undefined) {\n writer\n .write(encoder.encode(`${JSON.stringify({ __final__: true, data: final })}\\n`))\n .catch(() => {\n // Stream was closed by client disconnect - ignore\n })\n .finally(closeWriter);\n } else {\n closeWriter();\n }\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler (don't await - let it run in background)\n Promise.resolve(handler(stream)).catch((err) => {\n console.error('Streaming handler error:', err);\n stream.close();\n });\n\n return new Response(readable, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Create an SSE response\n */\nfunction createSSEResponse<T extends SSEEndpointDefinition>(\n handler: (\n emitter: SSEEmitter<T>,\n signal: AbortSignal,\n ) => void | (() => void) | Promise<void | (() => void)>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n const controller = new AbortController();\n let closed = false;\n let cleanup: (() => void) | undefined;\n\n const emitter: SSEEmitter<T> = {\n send(event, data, options) {\n if (!closed) {\n let message = '';\n if (options?.id) {\n message += `id: ${options.id}\\n`;\n }\n message += `event: ${String(event)}\\n`;\n message += `data: ${JSON.stringify(data)}\\n\\n`;\n writer.write(encoder.encode(message)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close() {\n if (!closed) {\n closed = true;\n if (cleanup) cleanup();\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler and get cleanup function\n Promise.resolve(handler(emitter, controller.signal))\n .then((cleanupFn) => {\n if (typeof cleanupFn === 'function') {\n cleanup = cleanupFn;\n }\n })\n .catch((err) => {\n console.error('SSE handler error:', err);\n emitter.close();\n });\n\n // Tee the stream - one for the response, one for disconnect detection\n const [responseStream, detectStream] = readable.tee();\n\n // Handle client disconnect by detecting when the stream is cancelled\n detectStream.pipeTo(new WritableStream()).catch(() => {\n controller.abort();\n emitter.close();\n });\n\n return new Response(responseStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Router configuration options\n */\nexport interface RouterOptions<C = unknown> {\n basePath?: string;\n context?: (request: Request, routeName?: string, endpoint?: EndpointDefinition) => C | Promise<C>;\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract, C = unknown> {\n private basePath: string;\n private contextFactory?: (\n request: Request,\n routeName: string,\n endpoint: EndpointDefinition,\n ) => C | Promise<C>;\n\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n ) {\n // Validate all path patterns\n for (const [name, endpoint] of Object.entries(contract)) {\n try {\n validatePathPattern(endpoint.path);\n } catch (error) {\n throw new Error(`Invalid path in endpoint \"${name}\": ${(error as Error).message}`);\n }\n }\n\n // Normalize basePath: ensure it starts with / and doesn't end with /\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.contextFactory = options?.context;\n }\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): {\n name: keyof T;\n endpoint: EndpointDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n if (endpoint.method === method) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return { name, endpoint, params };\n }\n }\n }\n return null;\n }\n\n /**\n * Handle a request\n */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const method = request.method;\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(method, path);\n if (!match) {\n throw new RouteNotFoundError(path, method);\n }\n\n const { name, endpoint, params } = match;\n const handler = this.handlers[name];\n\n // Create context if factory is provided\n const context = this.contextFactory\n ? await this.contextFactory(request, String(name), endpoint)\n : (undefined as C);\n\n // Dispatch based on endpoint type\n if (endpoint.type === 'streaming') {\n // Parse request for streaming endpoint\n const input = await parseStreamingRequest(request, endpoint, params, context);\n return createStreamingResponse((stream) => {\n return (handler as StreamingHandler<StreamingEndpointDefinition, C>)({\n ...input,\n stream,\n });\n });\n }\n\n if (endpoint.type === 'sse') {\n // Parse request for SSE endpoint\n const input = await parseSSERequest(request, endpoint, params, context);\n return createSSEResponse((emitter, signal) => {\n return (handler as SSEHandler<SSEEndpointDefinition, C>)({\n ...input,\n emitter,\n signal,\n });\n });\n }\n\n if (endpoint.type === 'download') {\n // Parse request for download endpoint\n const input = await parseDownloadRequest(request, endpoint, params, context);\n const downloadHandler = handler as unknown as DownloadHandler<DownloadEndpointDefinition, C>;\n const response = await downloadHandler(\n input as DownloadHandlerInput<DownloadEndpointDefinition, C>,\n );\n return createDownloadResponse(\n endpoint as DownloadEndpointDefinition,\n response as DownloadHandlerResponse<DownloadEndpointDefinition>,\n );\n }\n\n // Standard endpoint\n const input = await parseRequest(request, endpoint, params, context);\n const standardHandler = handler as unknown as Handler<StandardEndpointDefinition, C>;\n const handlerResponse = await standardHandler(\n input as HandlerInput<StandardEndpointDefinition, C>,\n );\n return createResponse(\n endpoint as StandardEndpointDefinition,\n handlerResponse as HandlerResponse<StandardEndpointDefinition>,\n );\n }\n\n /**\n * Get fetch handler compatible with Bun.serve\n */\n get fetch() {\n return (request: Request) => this.handle(request);\n }\n}\n\n/**\n * Create a router from a contract and handlers\n */\nexport function createRouter<T extends Contract, C = unknown>(\n contract: T,\n handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n): Router<T, C> {\n return new Router(contract, handlers, options);\n}\n\nexport * from './websocket.cjs';\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "BC7D8AAEE9E9006464756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "38E2BFF0734E74E464756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "type": "commonjs"
5
5
  }
@@ -36,14 +36,17 @@ __export(exports_websocket, {
36
36
  });
37
37
  module.exports = __toCommonJS(exports_websocket);
38
38
  var import_core = require("@richie-rpc/core");
39
+ var import_zod = require("zod");
39
40
 
40
41
  class WebSocketValidationError extends Error {
41
42
  messageType;
42
- issues;
43
- constructor(messageType, issues) {
44
- super(`Validation failed for WebSocket message type: ${messageType}`);
43
+ zodError;
44
+ constructor(messageType, zodError) {
45
+ const pretty = import_zod.z.prettifyError(zodError);
46
+ super(`Validation failed for WebSocket message type: ${messageType}:
47
+ ${pretty}`);
45
48
  this.messageType = messageType;
46
- this.issues = issues;
49
+ this.zodError = zodError;
47
50
  this.name = "WebSocketValidationError";
48
51
  }
49
52
  }
@@ -153,7 +156,7 @@ class WebSocketRouter {
153
156
  if (this.dataSchema) {
154
157
  const result = this.dataSchema.safeParse(data);
155
158
  if (!result.success) {
156
- throw new WebSocketValidationError("data", result.error.issues);
159
+ throw new WebSocketValidationError("data", result.error);
157
160
  }
158
161
  return result.data;
159
162
  }
@@ -165,17 +168,17 @@ class WebSocketRouter {
165
168
  const { type, payload } = parsed;
166
169
  const messageDef = endpoint.clientMessages[type];
167
170
  if (!messageDef) {
168
- throw new WebSocketValidationError(type, [
171
+ throw new WebSocketValidationError(type, new import_zod.z.ZodError([
169
172
  {
170
173
  code: "custom",
171
174
  path: ["type"],
172
175
  message: `Unknown message type: ${type}`
173
176
  }
174
- ]);
177
+ ]));
175
178
  }
176
179
  const result = messageDef.payload.safeParse(payload);
177
180
  if (!result.success) {
178
- throw new WebSocketValidationError(type, result.error.issues);
181
+ throw new WebSocketValidationError(type, result.error);
179
182
  }
180
183
  return { type, payload: result.data };
181
184
  }
@@ -225,7 +228,7 @@ class WebSocketRouter {
225
228
  typedWs.send("error", {
226
229
  code: "VALIDATION_ERROR",
227
230
  message: err.message,
228
- issues: err.issues
231
+ issues: err.zodError.issues
229
232
  });
230
233
  }
231
234
  } else {
@@ -273,4 +276,4 @@ function createWebSocketRouter(contract, handlers, options) {
273
276
  }
274
277
  })
275
278
 
276
- //# debugId=E702494F39B5D7E664756E2164756E21
279
+ //# debugId=38868F936199C68D64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../websocket.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n ExtractClientMessage,\n ExtractWSHeaders,\n ExtractWSParams,\n ExtractWSQuery,\n WebSocketContract,\n WebSocketContractDefinition,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n/**\n * Validation error for WebSocket messages\n */\nexport class WebSocketValidationError extends Error {\n constructor(public messageType: string, public issues: z.ZodIssue[]) {\n super(`Validation failed for WebSocket message type: ${messageType}`);\n this.name = 'WebSocketValidationError';\n }\n}\n\n/**\n * Typed WebSocket wrapper for sending messages\n */\nexport interface TypedServerWebSocket<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket\n> {\n /** The underlying Bun WebSocket */\n readonly raw: WS;\n /** Send a typed message to the client */\n send<K extends keyof T['serverMessages']>(\n type: K,\n payload: z.infer<T['serverMessages'][K]['payload']>\n ): void;\n /** Close the connection */\n close(code?: number, reason?: string): void;\n}\n\nexport type GenericWebSocket = {\n send: (message: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/**\n * Arguments passed to handler functions\n */\nexport interface HandlerArgs<T extends WebSocketContractDefinition, WS extends GenericWebSocket, D = unknown> {\n ws: TypedServerWebSocket<T, WS>;\n params: ExtractWSParams<T>;\n query: ExtractWSQuery<T>;\n headers: ExtractWSHeaders<T>;\n data: D;\n}\n\n/**\n * Handler functions for a WebSocket endpoint\n */\nexport interface WebSocketEndpointHandlers<T extends WebSocketContractDefinition, WS extends GenericWebSocket, D = unknown> {\n /** Called when connection opens */\n open?(args: HandlerArgs<T, WS, D>): void | Promise<void>;\n /** Called for each validated message */\n message(args: HandlerArgs<T, WS, D> & { message: ExtractClientMessage<T> }): void | Promise<void>;\n /** Called when connection closes */\n close?(args: HandlerArgs<T, WS, D>): void;\n /** Called on backpressure drain */\n drain?(args: HandlerArgs<T, WS, D>): void;\n /** Called when message validation fails */\n validationError?(args: {\n ws: TypedServerWebSocket<T, WS>;\n error: WebSocketValidationError;\n data: D;\n }): void;\n}\n\n/**\n * Contract handlers mapping for WebSocket endpoints\n */\nexport type WebSocketContractHandlers<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown> = {\n [K in keyof T]: WebSocketEndpointHandlers<T[K], WS, D>;\n};\n\n/**\n * Upgrade data returned by matchAndPrepareUpgrade\n */\nexport interface UpgradeData {\n endpointName: string;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n query: Record<string, string | string[]>;\n headers: Record<string, string>;\n}\n\n/**\n * Options for WebSocket router\n */\nexport interface WebSocketRouterOptions<WS extends GenericWebSocket, D = unknown> {\n basePath?: string;\n dataSchema?: z.ZodSchema<D>;\n rawWebSocket?: WS;\n}\n\ntype WithData<T, D> = T &\n (D extends object | string | number | boolean\n ? {\n data: D;\n }\n : {\n data?: never;\n });\n\n/**\n * WebSocket handler interface with context parameter\n */\nexport interface WebSocketHandler<WS extends GenericWebSocket, D = unknown> {\n open(\n args: WithData<\n {\n ws: WS;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void | Promise<void>;\n message(\n args: WithData<\n {\n ws: WS;\n rawMessage: string | Buffer<ArrayBuffer>;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void | Promise<void>;\n close(\n args: WithData<\n {\n ws: WS;\n code: number;\n reason: string;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void;\n drain(args: WithData<{ ws: WS; upgradeData: UpgradeData }, D>): void;\n}\n\n/**\n * Create a typed WebSocket wrapper\n */\nfunction createTypedWebSocket<T extends WebSocketContractDefinition, WS extends GenericWebSocket>(\n ws: WS\n): TypedServerWebSocket<T, WS> {\n return {\n get raw() {\n return ws;\n },\n send(type, payload) {\n ws.send(JSON.stringify({ type, payload }));\n },\n close(code, reason) {\n ws.close(code, reason);\n },\n };\n}\n\n/**\n * WebSocket router for managing WebSocket contract endpoints\n */\nexport class WebSocketRouter<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown> {\n private basePath: string;\n private dataSchema?: z.ZodSchema<D>;\n\n constructor(\n private contract: T,\n private handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>\n ) {\n // Normalize basePath\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.dataSchema = options?.dataSchema;\n }\n\n /**\n * Find matching endpoint for a path\n */\n private findEndpoint(path: string): {\n name: keyof T;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return {\n name,\n endpoint: endpoint as WebSocketContractDefinition,\n params,\n };\n }\n }\n return null;\n }\n\n /**\n * Parse and validate upgrade request parameters\n */\n private parseUpgradeParams(\n request: Request,\n endpoint: WebSocketContractDefinition,\n pathParams: Record<string, string>\n ): { params: any; query: any; headers: any } {\n const url = new URL(request.url);\n\n // Parse and validate path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new Error(`Invalid path params: ${result.error.message}`);\n }\n params = result.data;\n }\n\n // Parse and validate query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new Error(`Invalid query params: ${result.error.message}`);\n }\n query = result.data;\n }\n\n // Parse and validate headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new Error(`Invalid headers: ${result.error.message}`);\n }\n headers = result.data;\n }\n\n return { params, query, headers };\n }\n\n /**\n * Match a request and prepare upgrade data\n * Returns null if no match, or UpgradeData for server.upgrade()\n */\n async matchAndPrepareUpgrade(request: Request): Promise<UpgradeData | null> {\n const url = new URL(request.url);\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(path);\n if (!match) {\n return null;\n }\n\n const { name, endpoint, params: rawParams } = match;\n\n try {\n const { params, query, headers } = this.parseUpgradeParams(request, endpoint, rawParams);\n\n return {\n endpointName: String(name),\n endpoint,\n params,\n query,\n headers,\n };\n } catch (err) {\n // Validation failed during upgrade\n console.error('WebSocket upgrade validation failed:', err);\n return null;\n }\n }\n\n /**\n * Validate data against dataSchema if provided\n */\n private validateData(data: unknown): D {\n if (this.dataSchema) {\n const result = this.dataSchema.safeParse(data);\n if (!result.success) {\n throw new WebSocketValidationError('data', result.error.issues);\n }\n return result.data;\n }\n return data as D;\n }\n\n /**\n * Validate an incoming client message\n */\n private validateMessage(\n endpoint: WebSocketContractDefinition,\n rawMessage: string | ArrayBuffer\n ): ExtractClientMessage<typeof endpoint> {\n // Parse message\n const messageStr =\n typeof rawMessage === 'string' ? rawMessage : new TextDecoder().decode(rawMessage);\n const parsed = JSON.parse(messageStr) as { type: string; payload: unknown };\n\n const { type, payload } = parsed;\n\n // Find the schema for this message type\n const messageDef = endpoint.clientMessages[type];\n if (!messageDef) {\n throw new WebSocketValidationError(type, [\n {\n code: 'custom',\n path: ['type'],\n message: `Unknown message type: ${type}`,\n },\n ]);\n }\n\n // Validate payload\n const result = messageDef.payload.safeParse(payload);\n if (!result.success) {\n throw new WebSocketValidationError(type, result.error.issues);\n }\n\n return { type, payload: result.data } as ExtractClientMessage<typeof endpoint>;\n }\n\n /**\n * Get WebSocket handler that accepts context as parameter\n */\n get websocketHandler(): WebSocketHandler<WS, D> {\n return {\n open: async ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n // Validate data if schema provided\n const validatedData = this.validateData(data);\n\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.open) {\n await endpointHandlers.open({\n ws: typedWs,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n message: async ({ ws, rawMessage, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n try {\n // Validate the message\n const validatedMessage = this.validateMessage(\n upgradeData.endpoint,\n typeof rawMessage === 'string' ? rawMessage : rawMessage.buffer\n );\n\n // Call handler with validated message\n await endpointHandlers.message({\n ws: typedWs as any,\n message: validatedMessage as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n } catch (err) {\n if (err instanceof WebSocketValidationError) {\n // Call validation error handler if provided\n if (endpointHandlers.validationError) {\n endpointHandlers.validationError({\n ws: typedWs as any,\n error: err,\n data: validatedData,\n });\n } else {\n // Default: send error message back\n typedWs.send(\n 'error' as any,\n {\n code: 'VALIDATION_ERROR',\n message: err.message,\n issues: err.issues,\n } as any\n );\n }\n } else {\n console.error('WebSocket message handler error:', err);\n }\n }\n },\n\n close: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.close) {\n endpointHandlers.close({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n drain: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.drain) {\n endpointHandlers.drain({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n };\n }\n}\n\n/**\n * Create a WebSocket router from a contract and handlers\n */\nexport function createWebSocketRouter<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown>(\n contract: T,\n handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>\n): WebSocketRouter<T, WS, D> {\n return new WebSocketRouter(contract, handlers, options);\n}\n"
5
+ "import type {\n ExtractClientMessage,\n ExtractWSHeaders,\n ExtractWSParams,\n ExtractWSQuery,\n WebSocketContract,\n WebSocketContractDefinition,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport { z } from 'zod';\n\n/**\n * Validation error for WebSocket messages\n */\nexport class WebSocketValidationError extends Error {\n constructor(\n public messageType: string,\n public zodError: z.ZodError<unknown>,\n ) {\n const pretty = z.prettifyError(zodError);\n super(`Validation failed for WebSocket message type: ${messageType}:\\n${pretty}`);\n this.name = 'WebSocketValidationError';\n }\n}\n\n/**\n * Typed WebSocket wrapper for sending messages\n */\nexport interface TypedServerWebSocket<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n> {\n /** The underlying Bun WebSocket */\n readonly raw: WS;\n /** Send a typed message to the client */\n send<K extends keyof T['serverMessages']>(\n type: K,\n payload: z.infer<T['serverMessages'][K]['payload']>,\n ): void;\n /** Close the connection */\n close(code?: number, reason?: string): void;\n}\n\nexport type GenericWebSocket = {\n send: (message: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/**\n * Arguments passed to handler functions\n */\nexport interface HandlerArgs<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n ws: TypedServerWebSocket<T, WS>;\n params: ExtractWSParams<T>;\n query: ExtractWSQuery<T>;\n headers: ExtractWSHeaders<T>;\n data: D;\n}\n\n/**\n * Handler functions for a WebSocket endpoint\n */\nexport interface WebSocketEndpointHandlers<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n /** Called when connection opens */\n open?(args: HandlerArgs<T, WS, D>): void | Promise<void>;\n /** Called for each validated message */\n message(args: HandlerArgs<T, WS, D> & { message: ExtractClientMessage<T> }): void | Promise<void>;\n /** Called when connection closes */\n close?(args: HandlerArgs<T, WS, D>): void;\n /** Called on backpressure drain */\n drain?(args: HandlerArgs<T, WS, D>): void;\n /** Called when message validation fails */\n validationError?(args: {\n ws: TypedServerWebSocket<T, WS>;\n error: WebSocketValidationError;\n data: D;\n }): void;\n}\n\n/**\n * Contract handlers mapping for WebSocket endpoints\n */\nexport type WebSocketContractHandlers<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n> = {\n [K in keyof T]: WebSocketEndpointHandlers<T[K], WS, D>;\n};\n\n/**\n * Upgrade data returned by matchAndPrepareUpgrade\n */\nexport interface UpgradeData {\n endpointName: string;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n query: Record<string, string | string[]>;\n headers: Record<string, string>;\n}\n\n/**\n * Options for WebSocket router\n */\nexport interface WebSocketRouterOptions<WS extends GenericWebSocket, D = unknown> {\n basePath?: string;\n dataSchema?: z.ZodSchema<D>;\n rawWebSocket?: WS;\n}\n\ntype WithData<T, D> = T &\n (D extends object | string | number | boolean\n ? {\n data: D;\n }\n : {\n data?: never;\n });\n\n/**\n * WebSocket handler interface with context parameter\n */\nexport interface WebSocketHandler<WS extends GenericWebSocket, D = unknown> {\n open(\n args: WithData<\n {\n ws: WS;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void | Promise<void>;\n message(\n args: WithData<\n {\n ws: WS;\n rawMessage: string | Buffer<ArrayBuffer>;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void | Promise<void>;\n close(\n args: WithData<\n {\n ws: WS;\n code: number;\n reason: string;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void;\n drain(args: WithData<{ ws: WS; upgradeData: UpgradeData }, D>): void;\n}\n\n/**\n * Create a typed WebSocket wrapper\n */\nfunction createTypedWebSocket<T extends WebSocketContractDefinition, WS extends GenericWebSocket>(\n ws: WS,\n): TypedServerWebSocket<T, WS> {\n return {\n get raw() {\n return ws;\n },\n send(type, payload) {\n ws.send(JSON.stringify({ type, payload }));\n },\n close(code, reason) {\n ws.close(code, reason);\n },\n };\n}\n\n/**\n * WebSocket router for managing WebSocket contract endpoints\n */\nexport class WebSocketRouter<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n private basePath: string;\n private dataSchema?: z.ZodSchema<D>;\n\n constructor(\n private contract: T,\n private handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>,\n ) {\n // Normalize basePath\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.dataSchema = options?.dataSchema;\n }\n\n /**\n * Find matching endpoint for a path\n */\n private findEndpoint(path: string): {\n name: keyof T;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return {\n name,\n endpoint: endpoint as WebSocketContractDefinition,\n params,\n };\n }\n }\n return null;\n }\n\n /**\n * Parse and validate upgrade request parameters\n */\n private parseUpgradeParams(\n request: Request,\n endpoint: WebSocketContractDefinition,\n pathParams: Record<string, string>,\n ): { params: any; query: any; headers: any } {\n const url = new URL(request.url);\n\n // Parse and validate path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new Error(`Invalid path params: ${result.error.message}`);\n }\n params = result.data;\n }\n\n // Parse and validate query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new Error(`Invalid query params: ${result.error.message}`);\n }\n query = result.data;\n }\n\n // Parse and validate headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new Error(`Invalid headers: ${result.error.message}`);\n }\n headers = result.data;\n }\n\n return { params, query, headers };\n }\n\n /**\n * Match a request and prepare upgrade data\n * Returns null if no match, or UpgradeData for server.upgrade()\n */\n async matchAndPrepareUpgrade(request: Request): Promise<UpgradeData | null> {\n const url = new URL(request.url);\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(path);\n if (!match) {\n return null;\n }\n\n const { name, endpoint, params: rawParams } = match;\n\n try {\n const { params, query, headers } = this.parseUpgradeParams(request, endpoint, rawParams);\n\n return {\n endpointName: String(name),\n endpoint,\n params,\n query,\n headers,\n };\n } catch (err) {\n // Validation failed during upgrade\n console.error('WebSocket upgrade validation failed:', err);\n return null;\n }\n }\n\n /**\n * Validate data against dataSchema if provided\n */\n private validateData(data: unknown): D {\n if (this.dataSchema) {\n const result = this.dataSchema.safeParse(data);\n if (!result.success) {\n throw new WebSocketValidationError('data', result.error);\n }\n return result.data;\n }\n return data as D;\n }\n\n /**\n * Validate an incoming client message\n */\n private validateMessage(\n endpoint: WebSocketContractDefinition,\n rawMessage: string | ArrayBuffer,\n ): ExtractClientMessage<typeof endpoint> {\n // Parse message\n const messageStr =\n typeof rawMessage === 'string' ? rawMessage : new TextDecoder().decode(rawMessage);\n const parsed = JSON.parse(messageStr) as { type: string; payload: unknown };\n\n const { type, payload } = parsed;\n\n // Find the schema for this message type\n const messageDef = endpoint.clientMessages[type];\n if (!messageDef) {\n throw new WebSocketValidationError(\n type,\n new z.ZodError([\n {\n code: 'custom',\n path: ['type'],\n message: `Unknown message type: ${type}`,\n },\n ]),\n );\n }\n\n // Validate payload\n const result = messageDef.payload.safeParse(payload);\n if (!result.success) {\n throw new WebSocketValidationError(type, result.error);\n }\n\n return { type, payload: result.data } as ExtractClientMessage<typeof endpoint>;\n }\n\n /**\n * Get WebSocket handler that accepts context as parameter\n */\n get websocketHandler(): WebSocketHandler<WS, D> {\n return {\n open: async ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n // Validate data if schema provided\n const validatedData = this.validateData(data);\n\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.open) {\n await endpointHandlers.open({\n ws: typedWs,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n message: async ({ ws, rawMessage, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n try {\n // Validate the message\n const validatedMessage = this.validateMessage(\n upgradeData.endpoint,\n typeof rawMessage === 'string' ? rawMessage : rawMessage.buffer,\n );\n\n // Call handler with validated message\n await endpointHandlers.message({\n ws: typedWs as any,\n message: validatedMessage as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n } catch (err) {\n if (err instanceof WebSocketValidationError) {\n // Call validation error handler if provided\n if (endpointHandlers.validationError) {\n endpointHandlers.validationError({\n ws: typedWs as any,\n error: err,\n data: validatedData,\n });\n } else {\n // Default: send error message back\n typedWs.send(\n 'error' as any,\n {\n code: 'VALIDATION_ERROR',\n message: err.message,\n issues: err.zodError.issues,\n } as any,\n );\n }\n } else {\n console.error('WebSocket message handler error:', err);\n }\n }\n },\n\n close: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.close) {\n endpointHandlers.close({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n drain: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.drain) {\n endpointHandlers.drain({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n };\n }\n}\n\n/**\n * Create a WebSocket router from a contract and handlers\n */\nexport function createWebSocketRouter<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n>(\n contract: T,\n handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>,\n): WebSocketRouter<T, WS, D> {\n return new WebSocketRouter(contract, handlers, options);\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "E702494F39B5D7E664756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "38868F936199C68D64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,15 +1,24 @@
1
1
  // @bun
2
2
  // packages/server/index.ts
3
- import { formDataToObject, matchPath, parseQuery, Status } from "@richie-rpc/core";
3
+ import {
4
+ formDataToObject,
5
+ matchPath,
6
+ parseQuery,
7
+ Status,
8
+ validatePathPattern
9
+ } from "@richie-rpc/core";
10
+ import { z } from "zod";
4
11
 
5
12
  export * from "./websocket.mjs";
6
13
  class ValidationError extends Error {
7
14
  field;
8
- issues;
9
- constructor(field, issues, message) {
10
- super(message || `Validation failed for ${field}`);
15
+ zodError;
16
+ constructor(field, zodError) {
17
+ const pretty = z.prettifyError(zodError);
18
+ super(`Validation failed for ${field}:
19
+ ${pretty}`);
11
20
  this.field = field;
12
- this.issues = issues;
21
+ this.zodError = zodError;
13
22
  this.name = "ValidationError";
14
23
  }
15
24
  }
@@ -30,7 +39,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
30
39
  if (endpoint.params) {
31
40
  const result = endpoint.params.safeParse(pathParams);
32
41
  if (!result.success) {
33
- throw new ValidationError("params", result.error.issues);
42
+ throw new ValidationError("params", result.error);
34
43
  }
35
44
  params = result.data;
36
45
  }
@@ -39,7 +48,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
39
48
  const queryData = parseQuery(url.searchParams);
40
49
  const result = endpoint.query.safeParse(queryData);
41
50
  if (!result.success) {
42
- throw new ValidationError("query", result.error.issues);
51
+ throw new ValidationError("query", result.error);
43
52
  }
44
53
  query = result.data;
45
54
  }
@@ -51,7 +60,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
51
60
  });
52
61
  const result = endpoint.headers.safeParse(headersObj);
53
62
  if (!result.success) {
54
- throw new ValidationError("headers", result.error.issues);
63
+ throw new ValidationError("headers", result.error);
55
64
  }
56
65
  headers = result.data;
57
66
  }
@@ -69,7 +78,7 @@ async function parseRequest(request, endpoint, pathParams, context) {
69
78
  }
70
79
  const result = endpoint.body.safeParse(bodyData);
71
80
  if (!result.success) {
72
- throw new ValidationError("body", result.error.issues);
81
+ throw new ValidationError("body", result.error);
73
82
  }
74
83
  body = result.data;
75
84
  }
@@ -81,7 +90,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
81
90
  if (endpoint.params) {
82
91
  const result = endpoint.params.safeParse(pathParams);
83
92
  if (!result.success) {
84
- throw new ValidationError("params", result.error.issues);
93
+ throw new ValidationError("params", result.error);
85
94
  }
86
95
  params = result.data;
87
96
  }
@@ -90,7 +99,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
90
99
  const queryData = parseQuery(url.searchParams);
91
100
  const result = endpoint.query.safeParse(queryData);
92
101
  if (!result.success) {
93
- throw new ValidationError("query", result.error.issues);
102
+ throw new ValidationError("query", result.error);
94
103
  }
95
104
  query = result.data;
96
105
  }
@@ -102,7 +111,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
102
111
  });
103
112
  const result = endpoint.headers.safeParse(headersObj);
104
113
  if (!result.success) {
105
- throw new ValidationError("headers", result.error.issues);
114
+ throw new ValidationError("headers", result.error);
106
115
  }
107
116
  headers = result.data;
108
117
  }
@@ -120,7 +129,7 @@ async function parseStreamingRequest(request, endpoint, pathParams, context) {
120
129
  }
121
130
  const result = endpoint.body.safeParse(bodyData);
122
131
  if (!result.success) {
123
- throw new ValidationError("body", result.error.issues);
132
+ throw new ValidationError("body", result.error);
124
133
  }
125
134
  body = result.data;
126
135
  }
@@ -132,7 +141,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
132
141
  if (endpoint.params) {
133
142
  const result = endpoint.params.safeParse(pathParams);
134
143
  if (!result.success) {
135
- throw new ValidationError("params", result.error.issues);
144
+ throw new ValidationError("params", result.error);
136
145
  }
137
146
  params = result.data;
138
147
  }
@@ -141,7 +150,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
141
150
  const queryData = parseQuery(url.searchParams);
142
151
  const result = endpoint.query.safeParse(queryData);
143
152
  if (!result.success) {
144
- throw new ValidationError("query", result.error.issues);
153
+ throw new ValidationError("query", result.error);
145
154
  }
146
155
  query = result.data;
147
156
  }
@@ -153,7 +162,7 @@ async function parseSSERequest(request, endpoint, pathParams, context) {
153
162
  });
154
163
  const result = endpoint.headers.safeParse(headersObj);
155
164
  if (!result.success) {
156
- throw new ValidationError("headers", result.error.issues);
165
+ throw new ValidationError("headers", result.error);
157
166
  }
158
167
  headers = result.data;
159
168
  }
@@ -165,7 +174,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
165
174
  if (endpoint.params) {
166
175
  const result = endpoint.params.safeParse(pathParams);
167
176
  if (!result.success) {
168
- throw new ValidationError("params", result.error.issues);
177
+ throw new ValidationError("params", result.error);
169
178
  }
170
179
  params = result.data;
171
180
  }
@@ -174,7 +183,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
174
183
  const queryData = parseQuery(url.searchParams);
175
184
  const result = endpoint.query.safeParse(queryData);
176
185
  if (!result.success) {
177
- throw new ValidationError("query", result.error.issues);
186
+ throw new ValidationError("query", result.error);
178
187
  }
179
188
  query = result.data;
180
189
  }
@@ -186,7 +195,7 @@ async function parseDownloadRequest(request, endpoint, pathParams, context) {
186
195
  });
187
196
  const result = endpoint.headers.safeParse(headersObj);
188
197
  if (!result.success) {
189
- throw new ValidationError("headers", result.error.issues);
198
+ throw new ValidationError("headers", result.error);
190
199
  }
191
200
  headers = result.data;
192
201
  }
@@ -198,7 +207,7 @@ function createResponse(endpoint, handlerResponse) {
198
207
  if (responseSchema) {
199
208
  const result = responseSchema.safeParse(body);
200
209
  if (!result.success) {
201
- throw new ValidationError(`response[${String(status)}]`, result.error.issues);
210
+ throw new ValidationError(`response[${String(status)}]`, result.error);
202
211
  }
203
212
  }
204
213
  const responseHeaders = new Headers(customHeaders);
@@ -349,6 +358,13 @@ class Router {
349
358
  constructor(contract, handlers, options) {
350
359
  this.contract = contract;
351
360
  this.handlers = handlers;
361
+ for (const [name, endpoint] of Object.entries(contract)) {
362
+ try {
363
+ validatePathPattern(endpoint.path);
364
+ } catch (error) {
365
+ throw new Error(`Invalid path in endpoint "${name}": ${error.message}`);
366
+ }
367
+ }
352
368
  const bp = options?.basePath || "";
353
369
  if (bp) {
354
370
  this.basePath = bp.startsWith("/") ? bp : `/${bp}`;
@@ -428,4 +444,4 @@ export {
428
444
  RouteNotFoundError
429
445
  };
430
446
 
431
- //# debugId=1586BF9125F5237564756E2164756E21
447
+ //# debugId=B55A823B6B45AE3B64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Contract,\n DownloadEndpointDefinition,\n EndpointDefinition,\n ExtractBody,\n ExtractChunk,\n ExtractFinalResponse,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n ExtractSSEEventData,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport { formDataToObject, matchPath, parseQuery, Status } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n// Re-export Status for convenience\nexport { Status };\n\n// Handler input types (for standard endpoints)\nexport type HandlerInput<T extends StandardEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n};\n\n// Handler response type (for standard endpoints)\nexport type HandlerResponse<T extends StandardEndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n body: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n headers?: Record<string, string>;\n };\n}[keyof T['responses']];\n\n// Handler function type (for standard endpoints)\nexport type Handler<T extends StandardEndpointDefinition, C = unknown> = (\n input: HandlerInput<T, C>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// ============================================\n// Streaming Endpoint Types\n// ============================================\n\n/**\n * Emitter for streaming responses - push-based API\n */\nexport interface StreamEmitter<T extends StreamingEndpointDefinition> {\n /** Send a chunk to the client */\n send(chunk: ExtractChunk<T>): void;\n /** Close the stream with optional final response */\n close(final?: ExtractFinalResponse<T>): void;\n /** Check if stream is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for streaming endpoints\n */\nexport type StreamingHandlerInput<T extends StreamingEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n stream: StreamEmitter<T>;\n};\n\n/**\n * Handler function type for streaming endpoints\n */\nexport type StreamingHandler<T extends StreamingEndpointDefinition, C = unknown> = (\n input: StreamingHandlerInput<T, C>,\n) => void | Promise<void>;\n\n// ============================================\n// SSE Endpoint Types\n// ============================================\n\n/**\n * Emitter for SSE responses\n */\nexport interface SSEEmitter<T extends SSEEndpointDefinition> {\n /** Send an event to the client */\n send<K extends keyof T['events']>(\n event: K,\n data: ExtractSSEEventData<T, K>,\n options?: { id?: string },\n ): void;\n /** Close the connection */\n close(): void;\n /** Check if connection is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for SSE endpoints\n */\nexport type SSEHandlerInput<T extends SSEEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n emitter: SSEEmitter<T>;\n /** AbortSignal for detecting client disconnect */\n signal: AbortSignal;\n};\n\n/**\n * Handler function type for SSE endpoints\n * Returns an optional cleanup function\n */\nexport type SSEHandler<T extends SSEEndpointDefinition, C = unknown> = (\n input: SSEHandlerInput<T, C>,\n) => void | (() => void) | Promise<void | (() => void)>;\n\n// ============================================\n// Download Endpoint Types\n// ============================================\n\n/**\n * Handler input for download endpoints\n */\nexport type DownloadHandlerInput<T extends DownloadEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n};\n\n/**\n * Handler response for download endpoints\n * Success (200) returns File, errors return typed response\n */\nexport type DownloadHandlerResponse<T extends DownloadEndpointDefinition> =\n | { status: 200; body: File; headers?: Record<string, string> }\n | (T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? {\n [S in keyof T['errorResponses']]: {\n status: S;\n body: T['errorResponses'][S] extends z.ZodTypeAny\n ? z.infer<T['errorResponses'][S]>\n : never;\n headers?: Record<string, string>;\n };\n }[keyof T['errorResponses']]\n : never);\n\n/**\n * Handler function type for download endpoints\n */\nexport type DownloadHandler<T extends DownloadEndpointDefinition, C = unknown> = (\n input: DownloadHandlerInput<T, C>,\n) => Promise<DownloadHandlerResponse<T>> | DownloadHandlerResponse<T>;\n\n// ============================================\n// Contract Handlers (supports all endpoint types)\n// ============================================\n\n/**\n * Contract handlers mapping - conditionally applies handler type based on endpoint type\n */\nexport type ContractHandlers<T extends Contract, C = unknown> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? Handler<T[K], C>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHandler<T[K], C>\n : T[K] extends SSEEndpointDefinition\n ? SSEHandler<T[K], C>\n : T[K] extends DownloadEndpointDefinition\n ? DownloadHandler<T[K], C>\n : never;\n};\n\n// Error classes\nexport class ValidationError extends Error {\n constructor(\n public field: string,\n public issues: z.ZodIssue[],\n message?: string,\n ) {\n super(message || `Validation failed for ${field}`);\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends Error {\n constructor(\n public path: string,\n public method: string,\n ) {\n super(`Route not found: ${method} ${path}`);\n this.name = 'RouteNotFoundError';\n }\n}\n\n/**\n * Parse and validate request data\n */\nasync function parseRequest<T extends StandardEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<HandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error.issues);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as HandlerInput<T, C>;\n}\n\n/**\n * Parse and validate request data for streaming endpoints\n */\nasync function parseStreamingRequest<T extends StreamingEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<StreamingHandlerInput<T, C>, 'stream'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error.issues);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as Omit<\n StreamingHandlerInput<T, C>,\n 'stream'\n >;\n}\n\n/**\n * Parse and validate request data for SSE endpoints\n */\nasync function parseSSERequest<T extends SSEEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<SSEHandlerInput<T, C>, 'emitter' | 'signal'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // SSE endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as Omit<\n SSEHandlerInput<T, C>,\n 'emitter' | 'signal'\n >;\n}\n\n/**\n * Parse and validate request data for download endpoints\n */\nasync function parseDownloadRequest<T extends DownloadEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<DownloadHandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error.issues);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error.issues);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error.issues);\n }\n headers = result.data;\n }\n\n // Download endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as DownloadHandlerInput<T, C>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends StandardEndpointDefinition>(\n endpoint: T,\n handlerResponse: HandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n\n // Validate response body\n const responseSchema = endpoint.responses[status as keyof typeof endpoint.responses];\n if (responseSchema) {\n const result = responseSchema.safeParse(body);\n if (!result.success) {\n throw new ValidationError(`response[${String(status)}]`, result.error.issues);\n }\n }\n\n // Create response headers\n const responseHeaders = new Headers(customHeaders);\n\n // Handle 204 No Content - must have no body\n if (status === 204) {\n return new Response(null, {\n status: 204,\n headers: responseHeaders,\n });\n }\n\n // For all other responses, return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create response for download endpoints\n */\nfunction createDownloadResponse<T extends DownloadEndpointDefinition>(\n _endpoint: T,\n handlerResponse: DownloadHandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n const responseHeaders = new Headers(customHeaders);\n\n // Success: return File as binary\n if (status === 200 && body instanceof File) {\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', body.type || 'application/octet-stream');\n }\n responseHeaders.set('content-length', String(body.size));\n if (!responseHeaders.has('content-disposition')) {\n const filename = encodeURIComponent(body.name);\n responseHeaders.set(\n 'content-disposition',\n `attachment; filename=\"${filename}\"; filename*=UTF-8''${filename}`,\n );\n }\n return new Response(body, { status: 200, headers: responseHeaders });\n }\n\n // Error: return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create a streaming NDJSON response\n */\nfunction createStreamingResponse<T extends StreamingEndpointDefinition>(\n handler: (stream: StreamEmitter<T>) => void | Promise<void>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let closed = false;\n\n const stream: StreamEmitter<T> = {\n send(chunk) {\n if (!closed) {\n writer.write(encoder.encode(`${JSON.stringify(chunk)}\\n`)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close(final) {\n if (!closed) {\n closed = true;\n const closeWriter = () => {\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n };\n if (final !== undefined) {\n writer\n .write(encoder.encode(`${JSON.stringify({ __final__: true, data: final })}\\n`))\n .catch(() => {\n // Stream was closed by client disconnect - ignore\n })\n .finally(closeWriter);\n } else {\n closeWriter();\n }\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler (don't await - let it run in background)\n Promise.resolve(handler(stream)).catch((err) => {\n console.error('Streaming handler error:', err);\n stream.close();\n });\n\n return new Response(readable, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Create an SSE response\n */\nfunction createSSEResponse<T extends SSEEndpointDefinition>(\n handler: (\n emitter: SSEEmitter<T>,\n signal: AbortSignal,\n ) => void | (() => void) | Promise<void | (() => void)>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n const controller = new AbortController();\n let closed = false;\n let cleanup: (() => void) | undefined;\n\n const emitter: SSEEmitter<T> = {\n send(event, data, options) {\n if (!closed) {\n let message = '';\n if (options?.id) {\n message += `id: ${options.id}\\n`;\n }\n message += `event: ${String(event)}\\n`;\n message += `data: ${JSON.stringify(data)}\\n\\n`;\n writer.write(encoder.encode(message)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close() {\n if (!closed) {\n closed = true;\n if (cleanup) cleanup();\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler and get cleanup function\n Promise.resolve(handler(emitter, controller.signal))\n .then((cleanupFn) => {\n if (typeof cleanupFn === 'function') {\n cleanup = cleanupFn;\n }\n })\n .catch((err) => {\n console.error('SSE handler error:', err);\n emitter.close();\n });\n\n // Tee the stream - one for the response, one for disconnect detection\n const [responseStream, detectStream] = readable.tee();\n\n // Handle client disconnect by detecting when the stream is cancelled\n detectStream.pipeTo(new WritableStream()).catch(() => {\n controller.abort();\n emitter.close();\n });\n\n return new Response(responseStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Router configuration options\n */\nexport interface RouterOptions<C = unknown> {\n basePath?: string;\n context?: (request: Request, routeName?: string, endpoint?: EndpointDefinition) => C | Promise<C>;\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract, C = unknown> {\n private basePath: string;\n private contextFactory?: (\n request: Request,\n routeName: string,\n endpoint: EndpointDefinition,\n ) => C | Promise<C>;\n\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n ) {\n // Normalize basePath: ensure it starts with / and doesn't end with /\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.contextFactory = options?.context;\n }\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): {\n name: keyof T;\n endpoint: EndpointDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n if (endpoint.method === method) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return { name, endpoint, params };\n }\n }\n }\n return null;\n }\n\n /**\n * Handle a request\n */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const method = request.method;\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(method, path);\n if (!match) {\n throw new RouteNotFoundError(path, method);\n }\n\n const { name, endpoint, params } = match;\n const handler = this.handlers[name];\n\n // Create context if factory is provided\n const context = this.contextFactory\n ? await this.contextFactory(request, String(name), endpoint)\n : (undefined as C);\n\n // Dispatch based on endpoint type\n if (endpoint.type === 'streaming') {\n // Parse request for streaming endpoint\n const input = await parseStreamingRequest(request, endpoint, params, context);\n return createStreamingResponse((stream) => {\n return (handler as StreamingHandler<StreamingEndpointDefinition, C>)({\n ...input,\n stream,\n });\n });\n }\n\n if (endpoint.type === 'sse') {\n // Parse request for SSE endpoint\n const input = await parseSSERequest(request, endpoint, params, context);\n return createSSEResponse((emitter, signal) => {\n return (handler as SSEHandler<SSEEndpointDefinition, C>)({\n ...input,\n emitter,\n signal,\n });\n });\n }\n\n if (endpoint.type === 'download') {\n // Parse request for download endpoint\n const input = await parseDownloadRequest(request, endpoint, params, context);\n const downloadHandler = handler as unknown as DownloadHandler<DownloadEndpointDefinition, C>;\n const response = await downloadHandler(\n input as DownloadHandlerInput<DownloadEndpointDefinition, C>,\n );\n return createDownloadResponse(\n endpoint as DownloadEndpointDefinition,\n response as DownloadHandlerResponse<DownloadEndpointDefinition>,\n );\n }\n\n // Standard endpoint\n const input = await parseRequest(request, endpoint, params, context);\n const standardHandler = handler as unknown as Handler<StandardEndpointDefinition, C>;\n const handlerResponse = await standardHandler(\n input as HandlerInput<StandardEndpointDefinition, C>,\n );\n return createResponse(\n endpoint as StandardEndpointDefinition,\n handlerResponse as HandlerResponse<StandardEndpointDefinition>,\n );\n }\n\n /**\n * Get fetch handler compatible with Bun.serve\n */\n get fetch() {\n return (request: Request) => this.handle(request);\n }\n}\n\n/**\n * Create a router from a contract and handlers\n */\nexport function createRouter<T extends Contract, C = unknown>(\n contract: T,\n handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n): Router<T, C> {\n return new Router(contract, handlers, options);\n}\n\nexport * from './websocket.mjs';\n"
5
+ "import type {\n Contract,\n DownloadEndpointDefinition,\n EndpointDefinition,\n ExtractBody,\n ExtractChunk,\n ExtractFinalResponse,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n ExtractSSEEventData,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport {\n formDataToObject,\n matchPath,\n parseQuery,\n Status,\n validatePathPattern,\n} from '@richie-rpc/core';\nimport { z } from 'zod';\n\n// Re-export Status for convenience\nexport { Status };\n\n// Handler input types (for standard endpoints)\nexport type HandlerInput<T extends StandardEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n};\n\n// Handler response type (for standard endpoints)\nexport type HandlerResponse<T extends StandardEndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n body: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n headers?: Record<string, string>;\n };\n}[keyof T['responses']];\n\n// Handler function type (for standard endpoints)\nexport type Handler<T extends StandardEndpointDefinition, C = unknown> = (\n input: HandlerInput<T, C>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// ============================================\n// Streaming Endpoint Types\n// ============================================\n\n/**\n * Emitter for streaming responses - push-based API\n */\nexport interface StreamEmitter<T extends StreamingEndpointDefinition> {\n /** Send a chunk to the client */\n send(chunk: ExtractChunk<T>): void;\n /** Close the stream with optional final response */\n close(final?: ExtractFinalResponse<T>): void;\n /** Check if stream is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for streaming endpoints\n */\nexport type StreamingHandlerInput<T extends StreamingEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n context: C;\n stream: StreamEmitter<T>;\n};\n\n/**\n * Handler function type for streaming endpoints\n */\nexport type StreamingHandler<T extends StreamingEndpointDefinition, C = unknown> = (\n input: StreamingHandlerInput<T, C>,\n) => void | Promise<void>;\n\n// ============================================\n// SSE Endpoint Types\n// ============================================\n\n/**\n * Emitter for SSE responses\n */\nexport interface SSEEmitter<T extends SSEEndpointDefinition> {\n /** Send an event to the client */\n send<K extends keyof T['events']>(\n event: K,\n data: ExtractSSEEventData<T, K>,\n options?: { id?: string },\n ): void;\n /** Close the connection */\n close(): void;\n /** Check if connection is still open */\n readonly isOpen: boolean;\n}\n\n/**\n * Handler input for SSE endpoints\n */\nexport type SSEHandlerInput<T extends SSEEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n emitter: SSEEmitter<T>;\n /** AbortSignal for detecting client disconnect */\n signal: AbortSignal;\n};\n\n/**\n * Handler function type for SSE endpoints\n * Returns an optional cleanup function\n */\nexport type SSEHandler<T extends SSEEndpointDefinition, C = unknown> = (\n input: SSEHandlerInput<T, C>,\n) => void | (() => void) | Promise<void | (() => void)>;\n\n// ============================================\n// Download Endpoint Types\n// ============================================\n\n/**\n * Handler input for download endpoints\n */\nexport type DownloadHandlerInput<T extends DownloadEndpointDefinition, C = unknown> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n request: Request;\n context: C;\n};\n\n/**\n * Handler response for download endpoints\n * Success (200) returns File, errors return typed response\n */\nexport type DownloadHandlerResponse<T extends DownloadEndpointDefinition> =\n | { status: 200; body: File; headers?: Record<string, string> }\n | (T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? {\n [S in keyof T['errorResponses']]: {\n status: S;\n body: T['errorResponses'][S] extends z.ZodTypeAny\n ? z.infer<T['errorResponses'][S]>\n : never;\n headers?: Record<string, string>;\n };\n }[keyof T['errorResponses']]\n : never);\n\n/**\n * Handler function type for download endpoints\n */\nexport type DownloadHandler<T extends DownloadEndpointDefinition, C = unknown> = (\n input: DownloadHandlerInput<T, C>,\n) => Promise<DownloadHandlerResponse<T>> | DownloadHandlerResponse<T>;\n\n// ============================================\n// Contract Handlers (supports all endpoint types)\n// ============================================\n\n/**\n * Contract handlers mapping - conditionally applies handler type based on endpoint type\n */\nexport type ContractHandlers<T extends Contract, C = unknown> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? Handler<T[K], C>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHandler<T[K], C>\n : T[K] extends SSEEndpointDefinition\n ? SSEHandler<T[K], C>\n : T[K] extends DownloadEndpointDefinition\n ? DownloadHandler<T[K], C>\n : never;\n};\n\n// Error classes\nexport class ValidationError extends Error {\n constructor(\n public field: string,\n public zodError: z.ZodError<unknown>,\n ) {\n const pretty = z.prettifyError(zodError);\n super(`Validation failed for ${field}:\\n${pretty}`);\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends Error {\n constructor(\n public path: string,\n public method: string,\n ) {\n super(`Route not found: ${method} ${path}`);\n this.name = 'RouteNotFoundError';\n }\n}\n\n/**\n * Parse and validate request data\n */\nasync function parseRequest<T extends StandardEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<HandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as HandlerInput<T, C>;\n}\n\n/**\n * Parse and validate request data for streaming endpoints\n */\nasync function parseStreamingRequest<T extends StreamingEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<StreamingHandlerInput<T, C>, 'stream'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Parse body\n let body: any;\n if (endpoint.body) {\n const contentType = request.headers.get('content-type') || '';\n let bodyData: any;\n\n if (contentType.includes('application/json')) {\n bodyData = await request.json();\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = formDataToObject(formData as FormData);\n } else {\n bodyData = await request.text();\n }\n\n const result = endpoint.body.safeParse(bodyData);\n if (!result.success) {\n throw new ValidationError('body', result.error);\n }\n body = result.data;\n }\n\n return { params, query, headers, body, request, context } as Omit<\n StreamingHandlerInput<T, C>,\n 'stream'\n >;\n}\n\n/**\n * Parse and validate request data for SSE endpoints\n */\nasync function parseSSERequest<T extends SSEEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<Omit<SSEHandlerInput<T, C>, 'emitter' | 'signal'>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // SSE endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as Omit<\n SSEHandlerInput<T, C>,\n 'emitter' | 'signal'\n >;\n}\n\n/**\n * Parse and validate request data for download endpoints\n */\nasync function parseDownloadRequest<T extends DownloadEndpointDefinition, C = unknown>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n context: C,\n): Promise<DownloadHandlerInput<T, C>> {\n const url = new URL(request.url);\n\n // Parse path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new ValidationError('params', result.error);\n }\n params = result.data;\n }\n\n // Parse query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new ValidationError('query', result.error);\n }\n query = result.data;\n }\n\n // Parse headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new ValidationError('headers', result.error);\n }\n headers = result.data;\n }\n\n // Download endpoints don't have a body (GET only)\n return { params, query, headers, request, context } as DownloadHandlerInput<T, C>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends StandardEndpointDefinition>(\n endpoint: T,\n handlerResponse: HandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n\n // Validate response body\n const responseSchema = endpoint.responses[status as keyof typeof endpoint.responses];\n if (responseSchema) {\n const result = responseSchema.safeParse(body);\n if (!result.success) {\n throw new ValidationError(`response[${String(status)}]`, result.error);\n }\n }\n\n // Create response headers\n const responseHeaders = new Headers(customHeaders);\n\n // Handle 204 No Content - must have no body\n if (status === 204) {\n return new Response(null, {\n status: 204,\n headers: responseHeaders,\n });\n }\n\n // For all other responses, return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create response for download endpoints\n */\nfunction createDownloadResponse<T extends DownloadEndpointDefinition>(\n _endpoint: T,\n handlerResponse: DownloadHandlerResponse<T>,\n): Response {\n const { status, body, headers: customHeaders } = handlerResponse;\n const responseHeaders = new Headers(customHeaders);\n\n // Success: return File as binary\n if (status === 200 && body instanceof File) {\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', body.type || 'application/octet-stream');\n }\n responseHeaders.set('content-length', String(body.size));\n if (!responseHeaders.has('content-disposition')) {\n const filename = encodeURIComponent(body.name);\n responseHeaders.set(\n 'content-disposition',\n `attachment; filename=\"${filename}\"; filename*=UTF-8''${filename}`,\n );\n }\n return new Response(body, { status: 200, headers: responseHeaders });\n }\n\n // Error: return JSON\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json');\n }\n return new Response(JSON.stringify(body), {\n status: status as number,\n headers: responseHeaders,\n });\n}\n\n/**\n * Create a streaming NDJSON response\n */\nfunction createStreamingResponse<T extends StreamingEndpointDefinition>(\n handler: (stream: StreamEmitter<T>) => void | Promise<void>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let closed = false;\n\n const stream: StreamEmitter<T> = {\n send(chunk) {\n if (!closed) {\n writer.write(encoder.encode(`${JSON.stringify(chunk)}\\n`)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close(final) {\n if (!closed) {\n closed = true;\n const closeWriter = () => {\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n };\n if (final !== undefined) {\n writer\n .write(encoder.encode(`${JSON.stringify({ __final__: true, data: final })}\\n`))\n .catch(() => {\n // Stream was closed by client disconnect - ignore\n })\n .finally(closeWriter);\n } else {\n closeWriter();\n }\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler (don't await - let it run in background)\n Promise.resolve(handler(stream)).catch((err) => {\n console.error('Streaming handler error:', err);\n stream.close();\n });\n\n return new Response(readable, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Create an SSE response\n */\nfunction createSSEResponse<T extends SSEEndpointDefinition>(\n handler: (\n emitter: SSEEmitter<T>,\n signal: AbortSignal,\n ) => void | (() => void) | Promise<void | (() => void)>,\n): Response {\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n const controller = new AbortController();\n let closed = false;\n let cleanup: (() => void) | undefined;\n\n const emitter: SSEEmitter<T> = {\n send(event, data, options) {\n if (!closed) {\n let message = '';\n if (options?.id) {\n message += `id: ${options.id}\\n`;\n }\n message += `event: ${String(event)}\\n`;\n message += `data: ${JSON.stringify(data)}\\n\\n`;\n writer.write(encoder.encode(message)).catch(() => {\n // Stream was closed by client disconnect - ignore\n closed = true;\n });\n }\n },\n close() {\n if (!closed) {\n closed = true;\n if (cleanup) cleanup();\n writer.close().catch(() => {\n // Stream was already closed or errored - ignore\n });\n }\n },\n get isOpen() {\n return !closed;\n },\n };\n\n // Execute handler and get cleanup function\n Promise.resolve(handler(emitter, controller.signal))\n .then((cleanupFn) => {\n if (typeof cleanupFn === 'function') {\n cleanup = cleanupFn;\n }\n })\n .catch((err) => {\n console.error('SSE handler error:', err);\n emitter.close();\n });\n\n // Tee the stream - one for the response, one for disconnect detection\n const [responseStream, detectStream] = readable.tee();\n\n // Handle client disconnect by detecting when the stream is cancelled\n detectStream.pipeTo(new WritableStream()).catch(() => {\n controller.abort();\n emitter.close();\n });\n\n return new Response(responseStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n\n/**\n * Router configuration options\n */\nexport interface RouterOptions<C = unknown> {\n basePath?: string;\n context?: (request: Request, routeName?: string, endpoint?: EndpointDefinition) => C | Promise<C>;\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract, C = unknown> {\n private basePath: string;\n private contextFactory?: (\n request: Request,\n routeName: string,\n endpoint: EndpointDefinition,\n ) => C | Promise<C>;\n\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n ) {\n // Validate all path patterns\n for (const [name, endpoint] of Object.entries(contract)) {\n try {\n validatePathPattern(endpoint.path);\n } catch (error) {\n throw new Error(`Invalid path in endpoint \"${name}\": ${(error as Error).message}`);\n }\n }\n\n // Normalize basePath: ensure it starts with / and doesn't end with /\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.contextFactory = options?.context;\n }\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): {\n name: keyof T;\n endpoint: EndpointDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n if (endpoint.method === method) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return { name, endpoint, params };\n }\n }\n }\n return null;\n }\n\n /**\n * Handle a request\n */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const method = request.method;\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(method, path);\n if (!match) {\n throw new RouteNotFoundError(path, method);\n }\n\n const { name, endpoint, params } = match;\n const handler = this.handlers[name];\n\n // Create context if factory is provided\n const context = this.contextFactory\n ? await this.contextFactory(request, String(name), endpoint)\n : (undefined as C);\n\n // Dispatch based on endpoint type\n if (endpoint.type === 'streaming') {\n // Parse request for streaming endpoint\n const input = await parseStreamingRequest(request, endpoint, params, context);\n return createStreamingResponse((stream) => {\n return (handler as StreamingHandler<StreamingEndpointDefinition, C>)({\n ...input,\n stream,\n });\n });\n }\n\n if (endpoint.type === 'sse') {\n // Parse request for SSE endpoint\n const input = await parseSSERequest(request, endpoint, params, context);\n return createSSEResponse((emitter, signal) => {\n return (handler as SSEHandler<SSEEndpointDefinition, C>)({\n ...input,\n emitter,\n signal,\n });\n });\n }\n\n if (endpoint.type === 'download') {\n // Parse request for download endpoint\n const input = await parseDownloadRequest(request, endpoint, params, context);\n const downloadHandler = handler as unknown as DownloadHandler<DownloadEndpointDefinition, C>;\n const response = await downloadHandler(\n input as DownloadHandlerInput<DownloadEndpointDefinition, C>,\n );\n return createDownloadResponse(\n endpoint as DownloadEndpointDefinition,\n response as DownloadHandlerResponse<DownloadEndpointDefinition>,\n );\n }\n\n // Standard endpoint\n const input = await parseRequest(request, endpoint, params, context);\n const standardHandler = handler as unknown as Handler<StandardEndpointDefinition, C>;\n const handlerResponse = await standardHandler(\n input as HandlerInput<StandardEndpointDefinition, C>,\n );\n return createResponse(\n endpoint as StandardEndpointDefinition,\n handlerResponse as HandlerResponse<StandardEndpointDefinition>,\n );\n }\n\n /**\n * Get fetch handler compatible with Bun.serve\n */\n get fetch() {\n return (request: Request) => this.handle(request);\n }\n}\n\n/**\n * Create a router from a contract and handlers\n */\nexport function createRouter<T extends Contract, C = unknown>(\n contract: T,\n handlers: ContractHandlers<T, C>,\n options?: RouterOptions<C>,\n): Router<T, C> {\n return new Router(contract, handlers, options);\n}\n\nexport * from './websocket.mjs';\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "1586BF9125F5237564756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "B55A823B6B45AE3B64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "type": "module"
5
5
  }
@@ -1,14 +1,17 @@
1
1
  // @bun
2
2
  // packages/server/websocket.ts
3
3
  import { matchPath, parseQuery } from "@richie-rpc/core";
4
+ import { z } from "zod";
4
5
 
5
6
  class WebSocketValidationError extends Error {
6
7
  messageType;
7
- issues;
8
- constructor(messageType, issues) {
9
- super(`Validation failed for WebSocket message type: ${messageType}`);
8
+ zodError;
9
+ constructor(messageType, zodError) {
10
+ const pretty = z.prettifyError(zodError);
11
+ super(`Validation failed for WebSocket message type: ${messageType}:
12
+ ${pretty}`);
10
13
  this.messageType = messageType;
11
- this.issues = issues;
14
+ this.zodError = zodError;
12
15
  this.name = "WebSocketValidationError";
13
16
  }
14
17
  }
@@ -118,7 +121,7 @@ class WebSocketRouter {
118
121
  if (this.dataSchema) {
119
122
  const result = this.dataSchema.safeParse(data);
120
123
  if (!result.success) {
121
- throw new WebSocketValidationError("data", result.error.issues);
124
+ throw new WebSocketValidationError("data", result.error);
122
125
  }
123
126
  return result.data;
124
127
  }
@@ -130,17 +133,17 @@ class WebSocketRouter {
130
133
  const { type, payload } = parsed;
131
134
  const messageDef = endpoint.clientMessages[type];
132
135
  if (!messageDef) {
133
- throw new WebSocketValidationError(type, [
136
+ throw new WebSocketValidationError(type, new z.ZodError([
134
137
  {
135
138
  code: "custom",
136
139
  path: ["type"],
137
140
  message: `Unknown message type: ${type}`
138
141
  }
139
- ]);
142
+ ]));
140
143
  }
141
144
  const result = messageDef.payload.safeParse(payload);
142
145
  if (!result.success) {
143
- throw new WebSocketValidationError(type, result.error.issues);
146
+ throw new WebSocketValidationError(type, result.error);
144
147
  }
145
148
  return { type, payload: result.data };
146
149
  }
@@ -190,7 +193,7 @@ class WebSocketRouter {
190
193
  typedWs.send("error", {
191
194
  code: "VALIDATION_ERROR",
192
195
  message: err.message,
193
- issues: err.issues
196
+ issues: err.zodError.issues
194
197
  });
195
198
  }
196
199
  } else {
@@ -242,4 +245,4 @@ export {
242
245
  WebSocketRouter
243
246
  };
244
247
 
245
- //# debugId=881841E9E86A748F64756E2164756E21
248
+ //# debugId=B2472008810082BE64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../websocket.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n ExtractClientMessage,\n ExtractWSHeaders,\n ExtractWSParams,\n ExtractWSQuery,\n WebSocketContract,\n WebSocketContractDefinition,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n/**\n * Validation error for WebSocket messages\n */\nexport class WebSocketValidationError extends Error {\n constructor(public messageType: string, public issues: z.ZodIssue[]) {\n super(`Validation failed for WebSocket message type: ${messageType}`);\n this.name = 'WebSocketValidationError';\n }\n}\n\n/**\n * Typed WebSocket wrapper for sending messages\n */\nexport interface TypedServerWebSocket<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket\n> {\n /** The underlying Bun WebSocket */\n readonly raw: WS;\n /** Send a typed message to the client */\n send<K extends keyof T['serverMessages']>(\n type: K,\n payload: z.infer<T['serverMessages'][K]['payload']>\n ): void;\n /** Close the connection */\n close(code?: number, reason?: string): void;\n}\n\nexport type GenericWebSocket = {\n send: (message: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/**\n * Arguments passed to handler functions\n */\nexport interface HandlerArgs<T extends WebSocketContractDefinition, WS extends GenericWebSocket, D = unknown> {\n ws: TypedServerWebSocket<T, WS>;\n params: ExtractWSParams<T>;\n query: ExtractWSQuery<T>;\n headers: ExtractWSHeaders<T>;\n data: D;\n}\n\n/**\n * Handler functions for a WebSocket endpoint\n */\nexport interface WebSocketEndpointHandlers<T extends WebSocketContractDefinition, WS extends GenericWebSocket, D = unknown> {\n /** Called when connection opens */\n open?(args: HandlerArgs<T, WS, D>): void | Promise<void>;\n /** Called for each validated message */\n message(args: HandlerArgs<T, WS, D> & { message: ExtractClientMessage<T> }): void | Promise<void>;\n /** Called when connection closes */\n close?(args: HandlerArgs<T, WS, D>): void;\n /** Called on backpressure drain */\n drain?(args: HandlerArgs<T, WS, D>): void;\n /** Called when message validation fails */\n validationError?(args: {\n ws: TypedServerWebSocket<T, WS>;\n error: WebSocketValidationError;\n data: D;\n }): void;\n}\n\n/**\n * Contract handlers mapping for WebSocket endpoints\n */\nexport type WebSocketContractHandlers<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown> = {\n [K in keyof T]: WebSocketEndpointHandlers<T[K], WS, D>;\n};\n\n/**\n * Upgrade data returned by matchAndPrepareUpgrade\n */\nexport interface UpgradeData {\n endpointName: string;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n query: Record<string, string | string[]>;\n headers: Record<string, string>;\n}\n\n/**\n * Options for WebSocket router\n */\nexport interface WebSocketRouterOptions<WS extends GenericWebSocket, D = unknown> {\n basePath?: string;\n dataSchema?: z.ZodSchema<D>;\n rawWebSocket?: WS;\n}\n\ntype WithData<T, D> = T &\n (D extends object | string | number | boolean\n ? {\n data: D;\n }\n : {\n data?: never;\n });\n\n/**\n * WebSocket handler interface with context parameter\n */\nexport interface WebSocketHandler<WS extends GenericWebSocket, D = unknown> {\n open(\n args: WithData<\n {\n ws: WS;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void | Promise<void>;\n message(\n args: WithData<\n {\n ws: WS;\n rawMessage: string | Buffer<ArrayBuffer>;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void | Promise<void>;\n close(\n args: WithData<\n {\n ws: WS;\n code: number;\n reason: string;\n upgradeData: UpgradeData;\n },\n D\n >\n ): void;\n drain(args: WithData<{ ws: WS; upgradeData: UpgradeData }, D>): void;\n}\n\n/**\n * Create a typed WebSocket wrapper\n */\nfunction createTypedWebSocket<T extends WebSocketContractDefinition, WS extends GenericWebSocket>(\n ws: WS\n): TypedServerWebSocket<T, WS> {\n return {\n get raw() {\n return ws;\n },\n send(type, payload) {\n ws.send(JSON.stringify({ type, payload }));\n },\n close(code, reason) {\n ws.close(code, reason);\n },\n };\n}\n\n/**\n * WebSocket router for managing WebSocket contract endpoints\n */\nexport class WebSocketRouter<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown> {\n private basePath: string;\n private dataSchema?: z.ZodSchema<D>;\n\n constructor(\n private contract: T,\n private handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>\n ) {\n // Normalize basePath\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.dataSchema = options?.dataSchema;\n }\n\n /**\n * Find matching endpoint for a path\n */\n private findEndpoint(path: string): {\n name: keyof T;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return {\n name,\n endpoint: endpoint as WebSocketContractDefinition,\n params,\n };\n }\n }\n return null;\n }\n\n /**\n * Parse and validate upgrade request parameters\n */\n private parseUpgradeParams(\n request: Request,\n endpoint: WebSocketContractDefinition,\n pathParams: Record<string, string>\n ): { params: any; query: any; headers: any } {\n const url = new URL(request.url);\n\n // Parse and validate path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new Error(`Invalid path params: ${result.error.message}`);\n }\n params = result.data;\n }\n\n // Parse and validate query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new Error(`Invalid query params: ${result.error.message}`);\n }\n query = result.data;\n }\n\n // Parse and validate headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new Error(`Invalid headers: ${result.error.message}`);\n }\n headers = result.data;\n }\n\n return { params, query, headers };\n }\n\n /**\n * Match a request and prepare upgrade data\n * Returns null if no match, or UpgradeData for server.upgrade()\n */\n async matchAndPrepareUpgrade(request: Request): Promise<UpgradeData | null> {\n const url = new URL(request.url);\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(path);\n if (!match) {\n return null;\n }\n\n const { name, endpoint, params: rawParams } = match;\n\n try {\n const { params, query, headers } = this.parseUpgradeParams(request, endpoint, rawParams);\n\n return {\n endpointName: String(name),\n endpoint,\n params,\n query,\n headers,\n };\n } catch (err) {\n // Validation failed during upgrade\n console.error('WebSocket upgrade validation failed:', err);\n return null;\n }\n }\n\n /**\n * Validate data against dataSchema if provided\n */\n private validateData(data: unknown): D {\n if (this.dataSchema) {\n const result = this.dataSchema.safeParse(data);\n if (!result.success) {\n throw new WebSocketValidationError('data', result.error.issues);\n }\n return result.data;\n }\n return data as D;\n }\n\n /**\n * Validate an incoming client message\n */\n private validateMessage(\n endpoint: WebSocketContractDefinition,\n rawMessage: string | ArrayBuffer\n ): ExtractClientMessage<typeof endpoint> {\n // Parse message\n const messageStr =\n typeof rawMessage === 'string' ? rawMessage : new TextDecoder().decode(rawMessage);\n const parsed = JSON.parse(messageStr) as { type: string; payload: unknown };\n\n const { type, payload } = parsed;\n\n // Find the schema for this message type\n const messageDef = endpoint.clientMessages[type];\n if (!messageDef) {\n throw new WebSocketValidationError(type, [\n {\n code: 'custom',\n path: ['type'],\n message: `Unknown message type: ${type}`,\n },\n ]);\n }\n\n // Validate payload\n const result = messageDef.payload.safeParse(payload);\n if (!result.success) {\n throw new WebSocketValidationError(type, result.error.issues);\n }\n\n return { type, payload: result.data } as ExtractClientMessage<typeof endpoint>;\n }\n\n /**\n * Get WebSocket handler that accepts context as parameter\n */\n get websocketHandler(): WebSocketHandler<WS, D> {\n return {\n open: async ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n // Validate data if schema provided\n const validatedData = this.validateData(data);\n\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.open) {\n await endpointHandlers.open({\n ws: typedWs,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n message: async ({ ws, rawMessage, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n try {\n // Validate the message\n const validatedMessage = this.validateMessage(\n upgradeData.endpoint,\n typeof rawMessage === 'string' ? rawMessage : rawMessage.buffer\n );\n\n // Call handler with validated message\n await endpointHandlers.message({\n ws: typedWs as any,\n message: validatedMessage as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n } catch (err) {\n if (err instanceof WebSocketValidationError) {\n // Call validation error handler if provided\n if (endpointHandlers.validationError) {\n endpointHandlers.validationError({\n ws: typedWs as any,\n error: err,\n data: validatedData,\n });\n } else {\n // Default: send error message back\n typedWs.send(\n 'error' as any,\n {\n code: 'VALIDATION_ERROR',\n message: err.message,\n issues: err.issues,\n } as any\n );\n }\n } else {\n console.error('WebSocket message handler error:', err);\n }\n }\n },\n\n close: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.close) {\n endpointHandlers.close({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n drain: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.drain) {\n endpointHandlers.drain({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n };\n }\n}\n\n/**\n * Create a WebSocket router from a contract and handlers\n */\nexport function createWebSocketRouter<T extends WebSocketContract, WS extends GenericWebSocket, D = unknown>(\n contract: T,\n handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>\n): WebSocketRouter<T, WS, D> {\n return new WebSocketRouter(contract, handlers, options);\n}\n"
5
+ "import type {\n ExtractClientMessage,\n ExtractWSHeaders,\n ExtractWSParams,\n ExtractWSQuery,\n WebSocketContract,\n WebSocketContractDefinition,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport { z } from 'zod';\n\n/**\n * Validation error for WebSocket messages\n */\nexport class WebSocketValidationError extends Error {\n constructor(\n public messageType: string,\n public zodError: z.ZodError<unknown>,\n ) {\n const pretty = z.prettifyError(zodError);\n super(`Validation failed for WebSocket message type: ${messageType}:\\n${pretty}`);\n this.name = 'WebSocketValidationError';\n }\n}\n\n/**\n * Typed WebSocket wrapper for sending messages\n */\nexport interface TypedServerWebSocket<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n> {\n /** The underlying Bun WebSocket */\n readonly raw: WS;\n /** Send a typed message to the client */\n send<K extends keyof T['serverMessages']>(\n type: K,\n payload: z.infer<T['serverMessages'][K]['payload']>,\n ): void;\n /** Close the connection */\n close(code?: number, reason?: string): void;\n}\n\nexport type GenericWebSocket = {\n send: (message: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/**\n * Arguments passed to handler functions\n */\nexport interface HandlerArgs<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n ws: TypedServerWebSocket<T, WS>;\n params: ExtractWSParams<T>;\n query: ExtractWSQuery<T>;\n headers: ExtractWSHeaders<T>;\n data: D;\n}\n\n/**\n * Handler functions for a WebSocket endpoint\n */\nexport interface WebSocketEndpointHandlers<\n T extends WebSocketContractDefinition,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n /** Called when connection opens */\n open?(args: HandlerArgs<T, WS, D>): void | Promise<void>;\n /** Called for each validated message */\n message(args: HandlerArgs<T, WS, D> & { message: ExtractClientMessage<T> }): void | Promise<void>;\n /** Called when connection closes */\n close?(args: HandlerArgs<T, WS, D>): void;\n /** Called on backpressure drain */\n drain?(args: HandlerArgs<T, WS, D>): void;\n /** Called when message validation fails */\n validationError?(args: {\n ws: TypedServerWebSocket<T, WS>;\n error: WebSocketValidationError;\n data: D;\n }): void;\n}\n\n/**\n * Contract handlers mapping for WebSocket endpoints\n */\nexport type WebSocketContractHandlers<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n> = {\n [K in keyof T]: WebSocketEndpointHandlers<T[K], WS, D>;\n};\n\n/**\n * Upgrade data returned by matchAndPrepareUpgrade\n */\nexport interface UpgradeData {\n endpointName: string;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n query: Record<string, string | string[]>;\n headers: Record<string, string>;\n}\n\n/**\n * Options for WebSocket router\n */\nexport interface WebSocketRouterOptions<WS extends GenericWebSocket, D = unknown> {\n basePath?: string;\n dataSchema?: z.ZodSchema<D>;\n rawWebSocket?: WS;\n}\n\ntype WithData<T, D> = T &\n (D extends object | string | number | boolean\n ? {\n data: D;\n }\n : {\n data?: never;\n });\n\n/**\n * WebSocket handler interface with context parameter\n */\nexport interface WebSocketHandler<WS extends GenericWebSocket, D = unknown> {\n open(\n args: WithData<\n {\n ws: WS;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void | Promise<void>;\n message(\n args: WithData<\n {\n ws: WS;\n rawMessage: string | Buffer<ArrayBuffer>;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void | Promise<void>;\n close(\n args: WithData<\n {\n ws: WS;\n code: number;\n reason: string;\n upgradeData: UpgradeData;\n },\n D\n >,\n ): void;\n drain(args: WithData<{ ws: WS; upgradeData: UpgradeData }, D>): void;\n}\n\n/**\n * Create a typed WebSocket wrapper\n */\nfunction createTypedWebSocket<T extends WebSocketContractDefinition, WS extends GenericWebSocket>(\n ws: WS,\n): TypedServerWebSocket<T, WS> {\n return {\n get raw() {\n return ws;\n },\n send(type, payload) {\n ws.send(JSON.stringify({ type, payload }));\n },\n close(code, reason) {\n ws.close(code, reason);\n },\n };\n}\n\n/**\n * WebSocket router for managing WebSocket contract endpoints\n */\nexport class WebSocketRouter<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n> {\n private basePath: string;\n private dataSchema?: z.ZodSchema<D>;\n\n constructor(\n private contract: T,\n private handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>,\n ) {\n // Normalize basePath\n const bp = options?.basePath || '';\n if (bp) {\n this.basePath = bp.startsWith('/') ? bp : `/${bp}`;\n this.basePath = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;\n } else {\n this.basePath = '';\n }\n this.dataSchema = options?.dataSchema;\n }\n\n /**\n * Find matching endpoint for a path\n */\n private findEndpoint(path: string): {\n name: keyof T;\n endpoint: WebSocketContractDefinition;\n params: Record<string, string>;\n } | null {\n for (const [name, endpoint] of Object.entries(this.contract)) {\n const params = matchPath(endpoint.path, path);\n if (params !== null) {\n return {\n name,\n endpoint: endpoint as WebSocketContractDefinition,\n params,\n };\n }\n }\n return null;\n }\n\n /**\n * Parse and validate upgrade request parameters\n */\n private parseUpgradeParams(\n request: Request,\n endpoint: WebSocketContractDefinition,\n pathParams: Record<string, string>,\n ): { params: any; query: any; headers: any } {\n const url = new URL(request.url);\n\n // Parse and validate path params\n let params: any = pathParams;\n if (endpoint.params) {\n const result = endpoint.params.safeParse(pathParams);\n if (!result.success) {\n throw new Error(`Invalid path params: ${result.error.message}`);\n }\n params = result.data;\n }\n\n // Parse and validate query params\n let query: any = {};\n if (endpoint.query) {\n const queryData = parseQuery(url.searchParams);\n const result = endpoint.query.safeParse(queryData);\n if (!result.success) {\n throw new Error(`Invalid query params: ${result.error.message}`);\n }\n query = result.data;\n }\n\n // Parse and validate headers\n let headers: any = {};\n if (endpoint.headers) {\n const headersObj: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n const result = endpoint.headers.safeParse(headersObj);\n if (!result.success) {\n throw new Error(`Invalid headers: ${result.error.message}`);\n }\n headers = result.data;\n }\n\n return { params, query, headers };\n }\n\n /**\n * Match a request and prepare upgrade data\n * Returns null if no match, or UpgradeData for server.upgrade()\n */\n async matchAndPrepareUpgrade(request: Request): Promise<UpgradeData | null> {\n const url = new URL(request.url);\n let path = url.pathname;\n\n // Strip basePath if configured\n if (this.basePath && path.startsWith(this.basePath)) {\n path = path.slice(this.basePath.length) || '/';\n }\n\n const match = this.findEndpoint(path);\n if (!match) {\n return null;\n }\n\n const { name, endpoint, params: rawParams } = match;\n\n try {\n const { params, query, headers } = this.parseUpgradeParams(request, endpoint, rawParams);\n\n return {\n endpointName: String(name),\n endpoint,\n params,\n query,\n headers,\n };\n } catch (err) {\n // Validation failed during upgrade\n console.error('WebSocket upgrade validation failed:', err);\n return null;\n }\n }\n\n /**\n * Validate data against dataSchema if provided\n */\n private validateData(data: unknown): D {\n if (this.dataSchema) {\n const result = this.dataSchema.safeParse(data);\n if (!result.success) {\n throw new WebSocketValidationError('data', result.error);\n }\n return result.data;\n }\n return data as D;\n }\n\n /**\n * Validate an incoming client message\n */\n private validateMessage(\n endpoint: WebSocketContractDefinition,\n rawMessage: string | ArrayBuffer,\n ): ExtractClientMessage<typeof endpoint> {\n // Parse message\n const messageStr =\n typeof rawMessage === 'string' ? rawMessage : new TextDecoder().decode(rawMessage);\n const parsed = JSON.parse(messageStr) as { type: string; payload: unknown };\n\n const { type, payload } = parsed;\n\n // Find the schema for this message type\n const messageDef = endpoint.clientMessages[type];\n if (!messageDef) {\n throw new WebSocketValidationError(\n type,\n new z.ZodError([\n {\n code: 'custom',\n path: ['type'],\n message: `Unknown message type: ${type}`,\n },\n ]),\n );\n }\n\n // Validate payload\n const result = messageDef.payload.safeParse(payload);\n if (!result.success) {\n throw new WebSocketValidationError(type, result.error);\n }\n\n return { type, payload: result.data } as ExtractClientMessage<typeof endpoint>;\n }\n\n /**\n * Get WebSocket handler that accepts context as parameter\n */\n get websocketHandler(): WebSocketHandler<WS, D> {\n return {\n open: async ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n // Validate data if schema provided\n const validatedData = this.validateData(data);\n\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.open) {\n await endpointHandlers.open({\n ws: typedWs,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n message: async ({ ws, rawMessage, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n try {\n // Validate the message\n const validatedMessage = this.validateMessage(\n upgradeData.endpoint,\n typeof rawMessage === 'string' ? rawMessage : rawMessage.buffer,\n );\n\n // Call handler with validated message\n await endpointHandlers.message({\n ws: typedWs as any,\n message: validatedMessage as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n } catch (err) {\n if (err instanceof WebSocketValidationError) {\n // Call validation error handler if provided\n if (endpointHandlers.validationError) {\n endpointHandlers.validationError({\n ws: typedWs as any,\n error: err,\n data: validatedData,\n });\n } else {\n // Default: send error message back\n typedWs.send(\n 'error' as any,\n {\n code: 'VALIDATION_ERROR',\n message: err.message,\n issues: err.zodError.issues,\n } as any,\n );\n }\n } else {\n console.error('WebSocket message handler error:', err);\n }\n }\n },\n\n close: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.close) {\n endpointHandlers.close({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n\n drain: ({ ws, upgradeData, data }) => {\n const endpointHandlers = this.handlers[upgradeData.endpointName as keyof T];\n if (!endpointHandlers) return;\n\n const validatedData = this.validateData(data);\n const typedWs = createTypedWebSocket<WebSocketContractDefinition, WS>(ws);\n\n if (endpointHandlers.drain) {\n endpointHandlers.drain({\n ws: typedWs as any,\n params: upgradeData.params as any,\n query: upgradeData.query as any,\n headers: upgradeData.headers as any,\n data: validatedData,\n });\n }\n },\n };\n }\n}\n\n/**\n * Create a WebSocket router from a contract and handlers\n */\nexport function createWebSocketRouter<\n T extends WebSocketContract,\n WS extends GenericWebSocket,\n D = unknown,\n>(\n contract: T,\n handlers: WebSocketContractHandlers<T, WS, D>,\n options?: WebSocketRouterOptions<WS, D>,\n): WebSocketRouter<T, WS, D> {\n return new WebSocketRouter(contract, handlers, options);\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "881841E9E86A748F64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "B2472008810082BE64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,6 +1,6 @@
1
1
  import type { Contract, DownloadEndpointDefinition, EndpointDefinition, ExtractBody, ExtractChunk, ExtractFinalResponse, ExtractHeaders, ExtractParams, ExtractQuery, ExtractSSEEventData, SSEEndpointDefinition, StandardEndpointDefinition, StreamingEndpointDefinition } from '@richie-rpc/core';
2
2
  import { Status } from '@richie-rpc/core';
3
- import type { z } from 'zod';
3
+ import { z } from 'zod';
4
4
  export { Status };
5
5
  export type HandlerInput<T extends StandardEndpointDefinition, C = unknown> = {
6
6
  params: ExtractParams<T>;
@@ -113,8 +113,8 @@ export type ContractHandlers<T extends Contract, C = unknown> = {
113
113
  };
114
114
  export declare class ValidationError extends Error {
115
115
  field: string;
116
- issues: z.ZodIssue[];
117
- constructor(field: string, issues: z.ZodIssue[], message?: string);
116
+ zodError: z.ZodError<unknown>;
117
+ constructor(field: string, zodError: z.ZodError<unknown>);
118
118
  }
119
119
  export declare class RouteNotFoundError extends Error {
120
120
  path: string;
@@ -1,12 +1,12 @@
1
1
  import type { ExtractClientMessage, ExtractWSHeaders, ExtractWSParams, ExtractWSQuery, WebSocketContract, WebSocketContractDefinition } from '@richie-rpc/core';
2
- import type { z } from 'zod';
2
+ import { z } from 'zod';
3
3
  /**
4
4
  * Validation error for WebSocket messages
5
5
  */
6
6
  export declare class WebSocketValidationError extends Error {
7
7
  messageType: string;
8
- issues: z.ZodIssue[];
9
- constructor(messageType: string, issues: z.ZodIssue[]);
8
+ zodError: z.ZodError<unknown>;
9
+ constructor(messageType: string, zodError: z.ZodError<unknown>);
10
10
  }
11
11
  /**
12
12
  * Typed WebSocket wrapper for sending messages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "exports": {
6
6
  ".": {
@@ -10,7 +10,7 @@
10
10
  }
11
11
  },
12
12
  "peerDependencies": {
13
- "@richie-rpc/core": "^1.2.4",
13
+ "@richie-rpc/core": "^1.2.5",
14
14
  "typescript": "^5",
15
15
  "zod": "^4.1.12"
16
16
  },