@richie-rpc/server 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ bun add @richie-rpc/server @richie-rpc/core zod@^4
13
13
  ### Creating a Router
14
14
 
15
15
  ```typescript
16
- import { createRouter } from '@richie-rpc/server';
16
+ import { createRouter, Status } from '@richie-rpc/server';
17
17
  import { contract } from './contract';
18
18
 
19
19
  const router = createRouter(contract, {
@@ -22,16 +22,16 @@ const router = createRouter(contract, {
22
22
  const user = await db.getUser(params.id);
23
23
 
24
24
  if (!user) {
25
- return { status: 404, body: { error: 'User not found' } };
25
+ return { status: Status.NotFound, body: { error: 'User not found' } };
26
26
  }
27
27
 
28
- return { status: 200, body: user };
28
+ return { status: Status.OK, body: user };
29
29
  },
30
30
 
31
31
  createUser: async ({ body }) => {
32
32
  // body is fully typed and already validated
33
33
  const user = await db.createUser(body);
34
- return { status: 201, body: user };
34
+ return { status: Status.Created, body: user };
35
35
  }
36
36
  });
37
37
  ```
@@ -67,6 +67,7 @@ Bun.serve({
67
67
  - ✅ Automatic request validation (params, query, headers, body)
68
68
  - ✅ Automatic response validation
69
69
  - ✅ Type-safe handler inputs
70
+ - ✅ Type-safe status codes with `Status` const object
70
71
  - ✅ Path parameter matching
71
72
  - ✅ Query parameter parsing
72
73
  - ✅ JSON body parsing
@@ -101,6 +102,63 @@ Each handler must return a response object with:
101
102
  }
102
103
  ```
103
104
 
105
+ ### Using Status Codes
106
+
107
+ Use the `Status` const object for type-safe status codes:
108
+
109
+ ```typescript
110
+ import { Status } from '@richie-rpc/server';
111
+
112
+ return { status: Status.OK, body: user }; // 200
113
+ return { status: Status.Created, body: newUser }; // 201
114
+ return { status: Status.NoContent, body: {} }; // 204
115
+ return { status: Status.BadRequest, body: error }; // 400
116
+ return { status: Status.NotFound, body: error }; // 404
117
+ ```
118
+
119
+ Available status codes in `Status` object:
120
+ - **Success**: `OK` (200), `Created` (201), `Accepted` (202), `NoContent` (204)
121
+ - **Redirection**: `MovedPermanently` (301), `Found` (302), `NotModified` (304)
122
+ - **Client Errors**: `BadRequest` (400), `Unauthorized` (401), `Forbidden` (403), `NotFound` (404), `MethodNotAllowed` (405), `Conflict` (409), `UnprocessableEntity` (422), `TooManyRequests` (429)
123
+ - **Server Errors**: `InternalServerError` (500), `NotImplemented` (501), `BadGateway` (502), `ServiceUnavailable` (503), `GatewayTimeout` (504)
124
+
125
+ **Using custom status codes:**
126
+
127
+ For status codes not in the `Status` object:
128
+
129
+ ```typescript
130
+ // 1. Define in contract (no 'as const' needed)
131
+ responses: {
132
+ 418: z.object({ message: z.string() })
133
+ }
134
+
135
+ // 2. Return in handler (with 'as const')
136
+ return { status: 418 as const, body: { message: "I'm a teapot" } };
137
+ ```
138
+
139
+ Full example:
140
+
141
+ ```typescript
142
+ const contract = defineContract({
143
+ teapot: {
144
+ method: 'GET',
145
+ path: '/teapot',
146
+ responses: {
147
+ 418: z.object({ message: z.string(), isTeapot: z.boolean() })
148
+ }
149
+ }
150
+ });
151
+
152
+ const router = createRouter(contract, {
153
+ teapot: async () => {
154
+ return {
155
+ status: 418 as const,
156
+ body: { message: "I'm a teapot", isTeapot: true }
157
+ };
158
+ }
159
+ });
160
+ ```
161
+
104
162
  ## Error Handling
105
163
 
106
164
  The router automatically handles:
@@ -32,12 +32,12 @@ var exports_server = {};
32
32
  __export(exports_server, {
33
33
  createRouter: () => createRouter,
34
34
  ValidationError: () => ValidationError,
35
+ Status: () => import_core.Status,
35
36
  Router: () => Router,
36
37
  RouteNotFoundError: () => RouteNotFoundError
37
38
  });
38
39
  module.exports = __toCommonJS(exports_server);
39
40
  var import_core = require("@richie-rpc/core");
40
-
41
41
  class ValidationError extends Error {
42
42
  field;
43
43
  issues;
@@ -197,4 +197,4 @@ function createRouter(contract, handlers) {
197
197
  }
198
198
  })
199
199
 
200
- //# debugId=17A5462B0AD83EAC64756E2164756E21
200
+ //# debugId=161D525670FA0BC964756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Contract,\n EndpointDefinition,\n ExtractBody,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n// Handler input types\nexport type HandlerInput<T extends EndpointDefinition> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n};\n\n// Handler response type\nexport type HandlerResponse<T extends EndpointDefinition> = {\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\nexport type Handler<T extends EndpointDefinition> = (\n input: HandlerInput<T>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// Contract handlers mapping\nexport type ContractHandlers<T extends Contract> = {\n [K in keyof T]: Handler<T[K]>;\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 EndpointDefinition>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n): Promise<HandlerInput<T>> {\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('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\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 } as HandlerInput<T>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends EndpointDefinition>(\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 error response\n */\nfunction createErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n return Response.json(\n {\n error: 'Validation Error',\n field: error.field,\n issues: error.issues,\n },\n { status: 400 },\n );\n }\n\n if (error instanceof RouteNotFoundError) {\n return Response.json({ error: 'Not Found', message: error.message }, { status: 404 });\n }\n\n console.error('Internal server error:', error);\n return Response.json({ error: 'Internal Server Error' }, { status: 500 });\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract> {\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T>,\n ) {}\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): { name: keyof T; endpoint: EndpointDefinition; params: Record<string, string> } | 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 try {\n const url = new URL(request.url);\n const method = request.method;\n const path = url.pathname;\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 // Parse and validate request\n const input = await parseRequest(request, endpoint, params);\n\n // Call handler\n const handlerResponse = await handler(input as any);\n\n // Create and validate response\n return createResponse(endpoint as T[keyof T], handlerResponse);\n } catch (error) {\n return createErrorResponse(error);\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>(\n contract: T,\n handlers: ContractHandlers<T>,\n): Router<T> {\n return new Router(contract, handlers);\n}\n"
5
+ "import type {\n Contract,\n EndpointDefinition,\n ExtractBody,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n} from '@richie-rpc/core';\nimport { 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\nexport type HandlerInput<T extends EndpointDefinition> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n};\n\n// Handler response type\nexport type HandlerResponse<T extends EndpointDefinition> = {\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\nexport type Handler<T extends EndpointDefinition> = (\n input: HandlerInput<T>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// Contract handlers mapping\nexport type ContractHandlers<T extends Contract> = {\n [K in keyof T]: Handler<T[K]>;\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 EndpointDefinition>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n): Promise<HandlerInput<T>> {\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('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\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 } as HandlerInput<T>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends EndpointDefinition>(\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 error response\n */\nfunction createErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n return Response.json(\n {\n error: 'Validation Error',\n field: error.field,\n issues: error.issues,\n },\n { status: 400 },\n );\n }\n\n if (error instanceof RouteNotFoundError) {\n return Response.json({ error: 'Not Found', message: error.message }, { status: 404 });\n }\n\n console.error('Internal server error:', error);\n return Response.json({ error: 'Internal Server Error' }, { status: 500 });\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract> {\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T>,\n ) {}\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): { name: keyof T; endpoint: EndpointDefinition; params: Record<string, string> } | 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 try {\n const url = new URL(request.url);\n const method = request.method;\n const path = url.pathname;\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 // Parse and validate request\n const input = await parseRequest(request, endpoint, params);\n\n // Call handler\n const handlerResponse = await handler(input as any);\n\n // Create and validate response\n return createResponse(endpoint as T[keyof T], handlerResponse);\n } catch (error) {\n return createErrorResponse(error);\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>(\n contract: T,\n handlers: ContractHandlers<T>,\n): Router<T> {\n return new Router(contract, handlers);\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQsC,IAAtC;AAAA;AAgCO,MAAM,wBAAwB,MAAM;AAAA,EAEhC;AAAA,EACA;AAAA,EAFT,WAAW,CACF,OACA,QACP,SACA;AAAA,IACA,MAAM,WAAW,yBAAyB,OAAO;AAAA,IAJ1C;AAAA,IACA;AAAA,IAIP,KAAK,OAAO;AAAA;AAEhB;AAAA;AAEO,MAAM,2BAA2B,MAAM;AAAA,EAEnC;AAAA,EACA;AAAA,EAFT,WAAW,CACF,MACA,QACP;AAAA,IACA,MAAM,oBAAoB,UAAU,MAAM;AAAA,IAHnC;AAAA,IACA;AAAA,IAGP,KAAK,OAAO;AAAA;AAEhB;AAKA,eAAe,YAA0C,CACvD,SACA,UACA,YAC0B;AAAA,EAC1B,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAAA,EAG/B,IAAI,SAAc;AAAA,EAClB,IAAI,SAAS,QAAQ;AAAA,IACnB,MAAM,SAAS,SAAS,OAAO,UAAU,UAAU;AAAA,IACnD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,gBAAgB,UAAU,OAAO,MAAM,MAAM;AAAA,IACzD;AAAA,IACA,SAAS,OAAO;AAAA,EAClB;AAAA,EAGA,IAAI,QAAa,CAAC;AAAA,EAClB,IAAI,SAAS,OAAO;AAAA,IAClB,MAAM,YAAY,uBAAW,IAAI,YAAY;AAAA,IAC7C,MAAM,SAAS,SAAS,MAAM,UAAU,SAAS;AAAA,IACjD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,gBAAgB,SAAS,OAAO,MAAM,MAAM;AAAA,IACxD;AAAA,IACA,QAAQ,OAAO;AAAA,EACjB;AAAA,EAGA,IAAI,UAAe,CAAC;AAAA,EACpB,IAAI,SAAS,SAAS;AAAA,IACpB,MAAM,aAAqC,CAAC;AAAA,IAC5C,QAAQ,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AAAA,MACtC,WAAW,OAAO;AAAA,KACnB;AAAA,IACD,MAAM,SAAS,SAAS,QAAQ,UAAU,UAAU;AAAA,IACpD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,gBAAgB,WAAW,OAAO,MAAM,MAAM;AAAA,IAC1D;AAAA,IACA,UAAU,OAAO;AAAA,EACnB;AAAA,EAGA,IAAI;AAAA,EACJ,IAAI,SAAS,MAAM;AAAA,IACjB,MAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAAA,IAC3D,IAAI;AAAA,IAEJ,IAAI,YAAY,SAAS,kBAAkB,GAAG;AAAA,MAC5C,WAAW,MAAM,QAAQ,KAAK;AAAA,IAChC,EAAO,SAAI,YAAY,SAAS,mCAAmC,GAAG;AAAA,MACpE,MAAM,WAAW,MAAM,QAAQ,SAAS;AAAA,MACxC,WAAW,OAAO,YAAY,SAAS,QAAQ,CAAC;AAAA,IAClD,EAAO,SAAI,YAAY,SAAS,qBAAqB,GAAG;AAAA,MACtD,MAAM,WAAW,MAAM,QAAQ,SAAS;AAAA,MACxC,WAAW,OAAO,YAAY,SAAS,QAAQ,CAAC;AAAA,IAClD,EAAO;AAAA,MACL,WAAW,MAAM,QAAQ,KAAK;AAAA;AAAA,IAGhC,MAAM,SAAS,SAAS,KAAK,UAAU,QAAQ;AAAA,IAC/C,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,gBAAgB,QAAQ,OAAO,MAAM,MAAM;AAAA,IACvD;AAAA,IACA,OAAO,OAAO;AAAA,EAChB;AAAA,EAEA,OAAO,EAAE,QAAQ,OAAO,SAAS,MAAM,QAAQ;AAAA;AAMjD,SAAS,cAA4C,CACnD,UACA,iBACU;AAAA,EACV,QAAQ,QAAQ,MAAM,SAAS,kBAAkB;AAAA,EAGjD,MAAM,iBAAiB,SAAS,UAAU;AAAA,EAC1C,IAAI,gBAAgB;AAAA,IAClB,MAAM,SAAS,eAAe,UAAU,IAAI;AAAA,IAC5C,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,gBAAgB,YAAY,OAAO,MAAM,MAAM,OAAO,MAAM,MAAM;AAAA,IAC9E;AAAA,EACF;AAAA,EAGA,MAAM,kBAAkB,IAAI,QAAQ,aAAa;AAAA,EAGjD,IAAI,WAAW,KAAK;AAAA,IAClB,OAAO,IAAI,SAAS,MAAM;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAGA,IAAI,CAAC,gBAAgB,IAAI,cAAc,GAAG;AAAA,IACxC,gBAAgB,IAAI,gBAAgB,kBAAkB;AAAA,EACxD;AAAA,EAEA,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AAAA;AAMH,SAAS,mBAAmB,CAAC,OAA0B;AAAA,EACrD,IAAI,iBAAiB,iBAAiB;AAAA,IACpC,OAAO,SAAS,KACd;AAAA,MACE,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,MACb,QAAQ,MAAM;AAAA,IAChB,GACA,EAAE,QAAQ,IAAI,CAChB;AAAA,EACF;AAAA,EAEA,IAAI,iBAAiB,oBAAoB;AAAA,IACvC,OAAO,SAAS,KAAK,EAAE,OAAO,aAAa,SAAS,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtF;AAAA,EAEA,QAAQ,MAAM,0BAA0B,KAAK;AAAA,EAC7C,OAAO,SAAS,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA;AAAA;AAMnE,MAAM,OAA2B;AAAA,EAE5B;AAAA,EACA;AAAA,EAFV,WAAW,CACD,UACA,UACR;AAAA,IAFQ;AAAA,IACA;AAAA;AAAA,EAMF,YAAY,CAClB,QACA,MACwF;AAAA,IACxF,YAAY,MAAM,aAAa,OAAO,QAAQ,KAAK,QAAQ,GAAG;AAAA,MAC5D,IAAI,SAAS,WAAW,QAAQ;AAAA,QAC9B,MAAM,SAAS,sBAAU,SAAS,MAAM,IAAI;AAAA,QAC5C,IAAI,WAAW,MAAM;AAAA,UACnB,OAAO,EAAE,MAAM,UAAU,OAAO;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAAA,IACA,OAAO;AAAA;AAAA,OAMH,OAAM,CAAC,SAAqC;AAAA,IAChD,IAAI;AAAA,MACF,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAAA,MAC/B,MAAM,SAAS,QAAQ;AAAA,MACvB,MAAM,OAAO,IAAI;AAAA,MAEjB,MAAM,QAAQ,KAAK,aAAa,QAAQ,IAAI;AAAA,MAC5C,IAAI,CAAC,OAAO;AAAA,QACV,MAAM,IAAI,mBAAmB,MAAM,MAAM;AAAA,MAC3C;AAAA,MAEA,QAAQ,MAAM,UAAU,WAAW;AAAA,MACnC,MAAM,UAAU,KAAK,SAAS;AAAA,MAG9B,MAAM,QAAQ,MAAM,aAAa,SAAS,UAAU,MAAM;AAAA,MAG1D,MAAM,kBAAkB,MAAM,QAAQ,KAAY;AAAA,MAGlD,OAAO,eAAe,UAAwB,eAAe;AAAA,MAC7D,OAAO,OAAO;AAAA,MACd,OAAO,oBAAoB,KAAK;AAAA;AAAA;AAAA,MAOhC,KAAK,GAAG;AAAA,IACV,OAAO,CAAC,YAAqB,KAAK,OAAO,OAAO;AAAA;AAEpD;AAKO,SAAS,YAAgC,CAC9C,UACA,UACW;AAAA,EACX,OAAO,IAAI,OAAO,UAAU,QAAQ;AAAA;",
8
- "debugId": "17A5462B0AD83EAC64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "161D525670FA0BC964756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "commonjs"
5
5
  }
@@ -1,7 +1,6 @@
1
1
  // @bun
2
2
  // packages/server/index.ts
3
- import { matchPath, parseQuery } from "@richie-rpc/core";
4
-
3
+ import { matchPath, parseQuery, Status } from "@richie-rpc/core";
5
4
  class ValidationError extends Error {
6
5
  field;
7
6
  issues;
@@ -162,8 +161,9 @@ function createRouter(contract, handlers) {
162
161
  export {
163
162
  createRouter,
164
163
  ValidationError,
164
+ Status,
165
165
  Router,
166
166
  RouteNotFoundError
167
167
  };
168
168
 
169
- //# debugId=8E946DD736505E1764756E2164756E21
169
+ //# debugId=137645904680723464756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Contract,\n EndpointDefinition,\n ExtractBody,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n} from '@richie-rpc/core';\nimport { matchPath, parseQuery } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n// Handler input types\nexport type HandlerInput<T extends EndpointDefinition> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n};\n\n// Handler response type\nexport type HandlerResponse<T extends EndpointDefinition> = {\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\nexport type Handler<T extends EndpointDefinition> = (\n input: HandlerInput<T>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// Contract handlers mapping\nexport type ContractHandlers<T extends Contract> = {\n [K in keyof T]: Handler<T[K]>;\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 EndpointDefinition>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n): Promise<HandlerInput<T>> {\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('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\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 } as HandlerInput<T>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends EndpointDefinition>(\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 error response\n */\nfunction createErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n return Response.json(\n {\n error: 'Validation Error',\n field: error.field,\n issues: error.issues,\n },\n { status: 400 },\n );\n }\n\n if (error instanceof RouteNotFoundError) {\n return Response.json({ error: 'Not Found', message: error.message }, { status: 404 });\n }\n\n console.error('Internal server error:', error);\n return Response.json({ error: 'Internal Server Error' }, { status: 500 });\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract> {\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T>,\n ) {}\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): { name: keyof T; endpoint: EndpointDefinition; params: Record<string, string> } | 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 try {\n const url = new URL(request.url);\n const method = request.method;\n const path = url.pathname;\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 // Parse and validate request\n const input = await parseRequest(request, endpoint, params);\n\n // Call handler\n const handlerResponse = await handler(input as any);\n\n // Create and validate response\n return createResponse(endpoint as T[keyof T], handlerResponse);\n } catch (error) {\n return createErrorResponse(error);\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>(\n contract: T,\n handlers: ContractHandlers<T>,\n): Router<T> {\n return new Router(contract, handlers);\n}\n"
5
+ "import type {\n Contract,\n EndpointDefinition,\n ExtractBody,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n} from '@richie-rpc/core';\nimport { 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\nexport type HandlerInput<T extends EndpointDefinition> = {\n params: ExtractParams<T>;\n query: ExtractQuery<T>;\n headers: ExtractHeaders<T>;\n body: ExtractBody<T>;\n request: Request;\n};\n\n// Handler response type\nexport type HandlerResponse<T extends EndpointDefinition> = {\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\nexport type Handler<T extends EndpointDefinition> = (\n input: HandlerInput<T>,\n) => Promise<HandlerResponse<T>> | HandlerResponse<T>;\n\n// Contract handlers mapping\nexport type ContractHandlers<T extends Contract> = {\n [K in keyof T]: Handler<T[K]>;\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 EndpointDefinition>(\n request: Request,\n endpoint: T,\n pathParams: Record<string, string>,\n): Promise<HandlerInput<T>> {\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('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\n } else if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n bodyData = Object.fromEntries(formData.entries());\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 } as HandlerInput<T>;\n}\n\n/**\n * Validate and create response\n */\nfunction createResponse<T extends EndpointDefinition>(\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 error response\n */\nfunction createErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n return Response.json(\n {\n error: 'Validation Error',\n field: error.field,\n issues: error.issues,\n },\n { status: 400 },\n );\n }\n\n if (error instanceof RouteNotFoundError) {\n return Response.json({ error: 'Not Found', message: error.message }, { status: 404 });\n }\n\n console.error('Internal server error:', error);\n return Response.json({ error: 'Internal Server Error' }, { status: 500 });\n}\n\n/**\n * Router class that manages contract endpoints\n */\nexport class Router<T extends Contract> {\n constructor(\n private contract: T,\n private handlers: ContractHandlers<T>,\n ) {}\n\n /**\n * Find matching endpoint for a request\n */\n private findEndpoint(\n method: string,\n path: string,\n ): { name: keyof T; endpoint: EndpointDefinition; params: Record<string, string> } | 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 try {\n const url = new URL(request.url);\n const method = request.method;\n const path = url.pathname;\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 // Parse and validate request\n const input = await parseRequest(request, endpoint, params);\n\n // Call handler\n const handlerResponse = await handler(input as any);\n\n // Create and validate response\n return createResponse(endpoint as T[keyof T], handlerResponse);\n } catch (error) {\n return createErrorResponse(error);\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>(\n contract: T,\n handlers: ContractHandlers<T>,\n): Router<T> {\n return new Router(contract, handlers);\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "8E946DD736505E1764756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "137645904680723464756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module"
5
5
  }
@@ -1,5 +1,7 @@
1
1
  import type { Contract, EndpointDefinition, ExtractBody, ExtractHeaders, ExtractParams, ExtractQuery } from '@richie-rpc/core';
2
+ import { Status } from '@richie-rpc/core';
2
3
  import type { z } from 'zod';
4
+ export { Status };
3
5
  export type HandlerInput<T extends EndpointDefinition> = {
4
6
  params: ExtractParams<T>;
5
7
  query: ExtractQuery<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-rpc/server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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.0.0",
13
+ "@richie-rpc/core": "^1.1.0",
14
14
  "typescript": "^5",
15
15
  "zod": "^4.1.12"
16
16
  },