@richie-rpc/server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # @richie-rpc/server
2
+
3
+ Server implementation package for Richie RPC with Bun.serve compatibility.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @richie-rpc/server @richie-rpc/core zod
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Creating a Router
14
+
15
+ ```typescript
16
+ import { createRouter } from '@richie-rpc/server';
17
+ import { contract } from './contract';
18
+
19
+ const router = createRouter(contract, {
20
+ getUser: async ({ params }) => {
21
+ // params is fully typed based on the contract
22
+ const user = await db.getUser(params.id);
23
+
24
+ if (!user) {
25
+ return { status: 404, body: { error: 'User not found' } };
26
+ }
27
+
28
+ return { status: 200, body: user };
29
+ },
30
+
31
+ createUser: async ({ body }) => {
32
+ // body is fully typed and already validated
33
+ const user = await db.createUser(body);
34
+ return { status: 201, body: user };
35
+ }
36
+ });
37
+ ```
38
+
39
+ ### Using with Bun.serve
40
+
41
+ ```typescript
42
+ Bun.serve({
43
+ port: 3000,
44
+ fetch: router.fetch
45
+ });
46
+ ```
47
+
48
+ Or with custom routing:
49
+
50
+ ```typescript
51
+ Bun.serve({
52
+ port: 3000,
53
+ fetch(request) {
54
+ const url = new URL(request.url);
55
+
56
+ if (url.pathname.startsWith('/api')) {
57
+ return router.handle(request);
58
+ }
59
+
60
+ return new Response('Not Found', { status: 404 });
61
+ }
62
+ });
63
+ ```
64
+
65
+ ## Features
66
+
67
+ - ✅ Automatic request validation (params, query, headers, body)
68
+ - ✅ Automatic response validation
69
+ - ✅ Type-safe handler inputs
70
+ - ✅ Path parameter matching
71
+ - ✅ Query parameter parsing
72
+ - ✅ JSON body parsing
73
+ - ✅ Form data support
74
+ - ✅ Detailed validation errors
75
+ - ✅ 404 handling for unknown routes
76
+ - ✅ Error handling and reporting
77
+
78
+ ## Handler Input
79
+
80
+ Each handler receives a typed input object with:
81
+
82
+ ```typescript
83
+ {
84
+ params: Record<string, string>, // Path parameters
85
+ query: Record<string, any>, // Query parameters
86
+ headers: Record<string, string>, // Request headers
87
+ body: any, // Request body
88
+ request: Request // Original Request object
89
+ }
90
+ ```
91
+
92
+ ## Handler Response
93
+
94
+ Each handler must return a response object with:
95
+
96
+ ```typescript
97
+ {
98
+ status: number, // HTTP status code (must match contract)
99
+ body: any, // Response body (must match schema)
100
+ headers?: Record<string, string> // Optional custom headers
101
+ }
102
+ ```
103
+
104
+ ## Error Handling
105
+
106
+ The router automatically handles:
107
+
108
+ - **Validation Errors** (400): Invalid request data
109
+ - **Route Not Found** (404): Unknown endpoints
110
+ - **Internal Errors** (500): Uncaught exceptions
111
+
112
+ Custom error responses:
113
+
114
+ ```typescript
115
+ return {
116
+ status: 400,
117
+ body: { error: 'Bad Request', message: 'Invalid input' }
118
+ };
119
+ ```
120
+
121
+ ## Validation
122
+
123
+ Both request and response data are validated against the contract schemas:
124
+
125
+ - Request validation happens before calling the handler
126
+ - Response validation happens before sending to the client
127
+ - Validation errors return detailed Zod error information
128
+
129
+ ## Links
130
+
131
+ - **npm:** https://www.npmjs.com/package/@richie-rpc/server
132
+ - **Repository:** https://github.com/ricsam/richie-rpc
133
+
134
+ ## License
135
+
136
+ MIT
137
+
@@ -0,0 +1,200 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // packages/server/index.ts
31
+ var exports_server = {};
32
+ __export(exports_server, {
33
+ createRouter: () => createRouter,
34
+ ValidationError: () => ValidationError,
35
+ Router: () => Router,
36
+ RouteNotFoundError: () => RouteNotFoundError
37
+ });
38
+ module.exports = __toCommonJS(exports_server);
39
+ var import_core = require("@richie-rpc/core");
40
+
41
+ class ValidationError extends Error {
42
+ field;
43
+ issues;
44
+ constructor(field, issues, message) {
45
+ super(message || `Validation failed for ${field}`);
46
+ this.field = field;
47
+ this.issues = issues;
48
+ this.name = "ValidationError";
49
+ }
50
+ }
51
+
52
+ class RouteNotFoundError extends Error {
53
+ path;
54
+ method;
55
+ constructor(path, method) {
56
+ super(`Route not found: ${method} ${path}`);
57
+ this.path = path;
58
+ this.method = method;
59
+ this.name = "RouteNotFoundError";
60
+ }
61
+ }
62
+ async function parseRequest(request, endpoint, pathParams) {
63
+ const url = new URL(request.url);
64
+ let params = pathParams;
65
+ if (endpoint.params) {
66
+ const result = endpoint.params.safeParse(pathParams);
67
+ if (!result.success) {
68
+ throw new ValidationError("params", result.error.issues);
69
+ }
70
+ params = result.data;
71
+ }
72
+ let query = {};
73
+ if (endpoint.query) {
74
+ const queryData = import_core.parseQuery(url.searchParams);
75
+ const result = endpoint.query.safeParse(queryData);
76
+ if (!result.success) {
77
+ throw new ValidationError("query", result.error.issues);
78
+ }
79
+ query = result.data;
80
+ }
81
+ let headers = {};
82
+ if (endpoint.headers) {
83
+ const headersObj = {};
84
+ request.headers.forEach((value, key) => {
85
+ headersObj[key] = value;
86
+ });
87
+ const result = endpoint.headers.safeParse(headersObj);
88
+ if (!result.success) {
89
+ throw new ValidationError("headers", result.error.issues);
90
+ }
91
+ headers = result.data;
92
+ }
93
+ let body;
94
+ if (endpoint.body) {
95
+ const contentType = request.headers.get("content-type") || "";
96
+ let bodyData;
97
+ if (contentType.includes("application/json")) {
98
+ bodyData = await request.json();
99
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
100
+ const formData = await request.formData();
101
+ bodyData = Object.fromEntries(formData.entries());
102
+ } else if (contentType.includes("multipart/form-data")) {
103
+ const formData = await request.formData();
104
+ bodyData = Object.fromEntries(formData.entries());
105
+ } else {
106
+ bodyData = await request.text();
107
+ }
108
+ const result = endpoint.body.safeParse(bodyData);
109
+ if (!result.success) {
110
+ throw new ValidationError("body", result.error.issues);
111
+ }
112
+ body = result.data;
113
+ }
114
+ return { params, query, headers, body, request };
115
+ }
116
+ function createResponse(endpoint, handlerResponse) {
117
+ const { status, body, headers: customHeaders } = handlerResponse;
118
+ const responseSchema = endpoint.responses[status];
119
+ if (responseSchema) {
120
+ const result = responseSchema.safeParse(body);
121
+ if (!result.success) {
122
+ throw new ValidationError(`response[${String(status)}]`, result.error.issues);
123
+ }
124
+ }
125
+ const responseHeaders = new Headers(customHeaders);
126
+ if (status === 204) {
127
+ return new Response(null, {
128
+ status: 204,
129
+ headers: responseHeaders
130
+ });
131
+ }
132
+ if (!responseHeaders.has("content-type")) {
133
+ responseHeaders.set("content-type", "application/json");
134
+ }
135
+ return new Response(JSON.stringify(body), {
136
+ status,
137
+ headers: responseHeaders
138
+ });
139
+ }
140
+ function createErrorResponse(error) {
141
+ if (error instanceof ValidationError) {
142
+ return Response.json({
143
+ error: "Validation Error",
144
+ field: error.field,
145
+ issues: error.issues
146
+ }, { status: 400 });
147
+ }
148
+ if (error instanceof RouteNotFoundError) {
149
+ return Response.json({ error: "Not Found", message: error.message }, { status: 404 });
150
+ }
151
+ console.error("Internal server error:", error);
152
+ return Response.json({ error: "Internal Server Error" }, { status: 500 });
153
+ }
154
+
155
+ class Router {
156
+ contract;
157
+ handlers;
158
+ constructor(contract, handlers) {
159
+ this.contract = contract;
160
+ this.handlers = handlers;
161
+ }
162
+ findEndpoint(method, path) {
163
+ for (const [name, endpoint] of Object.entries(this.contract)) {
164
+ if (endpoint.method === method) {
165
+ const params = import_core.matchPath(endpoint.path, path);
166
+ if (params !== null) {
167
+ return { name, endpoint, params };
168
+ }
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ async handle(request) {
174
+ try {
175
+ const url = new URL(request.url);
176
+ const method = request.method;
177
+ const path = url.pathname;
178
+ const match = this.findEndpoint(method, path);
179
+ if (!match) {
180
+ throw new RouteNotFoundError(path, method);
181
+ }
182
+ const { name, endpoint, params } = match;
183
+ const handler = this.handlers[name];
184
+ const input = await parseRequest(request, endpoint, params);
185
+ const handlerResponse = await handler(input);
186
+ return createResponse(endpoint, handlerResponse);
187
+ } catch (error) {
188
+ return createErrorResponse(error);
189
+ }
190
+ }
191
+ get fetch() {
192
+ return (request) => this.handle(request);
193
+ }
194
+ }
195
+ function createRouter(contract, handlers) {
196
+ return new Router(contract, handlers);
197
+ }
198
+ })
199
+
200
+ //# debugId=17A5462B0AD83EAC64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../index.ts"],
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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "17A5462B0AD83EAC64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@richie-rpc/server",
3
+ "version": "0.1.0",
4
+ "type": "commonjs"
5
+ }
@@ -0,0 +1,169 @@
1
+ // @bun
2
+ // packages/server/index.ts
3
+ import { matchPath, parseQuery } from "@richie-rpc/core";
4
+
5
+ class ValidationError extends Error {
6
+ field;
7
+ issues;
8
+ constructor(field, issues, message) {
9
+ super(message || `Validation failed for ${field}`);
10
+ this.field = field;
11
+ this.issues = issues;
12
+ this.name = "ValidationError";
13
+ }
14
+ }
15
+
16
+ class RouteNotFoundError extends Error {
17
+ path;
18
+ method;
19
+ constructor(path, method) {
20
+ super(`Route not found: ${method} ${path}`);
21
+ this.path = path;
22
+ this.method = method;
23
+ this.name = "RouteNotFoundError";
24
+ }
25
+ }
26
+ async function parseRequest(request, endpoint, pathParams) {
27
+ const url = new URL(request.url);
28
+ let params = pathParams;
29
+ if (endpoint.params) {
30
+ const result = endpoint.params.safeParse(pathParams);
31
+ if (!result.success) {
32
+ throw new ValidationError("params", result.error.issues);
33
+ }
34
+ params = result.data;
35
+ }
36
+ let query = {};
37
+ if (endpoint.query) {
38
+ const queryData = parseQuery(url.searchParams);
39
+ const result = endpoint.query.safeParse(queryData);
40
+ if (!result.success) {
41
+ throw new ValidationError("query", result.error.issues);
42
+ }
43
+ query = result.data;
44
+ }
45
+ let headers = {};
46
+ if (endpoint.headers) {
47
+ const headersObj = {};
48
+ request.headers.forEach((value, key) => {
49
+ headersObj[key] = value;
50
+ });
51
+ const result = endpoint.headers.safeParse(headersObj);
52
+ if (!result.success) {
53
+ throw new ValidationError("headers", result.error.issues);
54
+ }
55
+ headers = result.data;
56
+ }
57
+ let body;
58
+ if (endpoint.body) {
59
+ const contentType = request.headers.get("content-type") || "";
60
+ let bodyData;
61
+ if (contentType.includes("application/json")) {
62
+ bodyData = await request.json();
63
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
64
+ const formData = await request.formData();
65
+ bodyData = Object.fromEntries(formData.entries());
66
+ } else if (contentType.includes("multipart/form-data")) {
67
+ const formData = await request.formData();
68
+ bodyData = Object.fromEntries(formData.entries());
69
+ } else {
70
+ bodyData = await request.text();
71
+ }
72
+ const result = endpoint.body.safeParse(bodyData);
73
+ if (!result.success) {
74
+ throw new ValidationError("body", result.error.issues);
75
+ }
76
+ body = result.data;
77
+ }
78
+ return { params, query, headers, body, request };
79
+ }
80
+ function createResponse(endpoint, handlerResponse) {
81
+ const { status, body, headers: customHeaders } = handlerResponse;
82
+ const responseSchema = endpoint.responses[status];
83
+ if (responseSchema) {
84
+ const result = responseSchema.safeParse(body);
85
+ if (!result.success) {
86
+ throw new ValidationError(`response[${String(status)}]`, result.error.issues);
87
+ }
88
+ }
89
+ const responseHeaders = new Headers(customHeaders);
90
+ if (status === 204) {
91
+ return new Response(null, {
92
+ status: 204,
93
+ headers: responseHeaders
94
+ });
95
+ }
96
+ if (!responseHeaders.has("content-type")) {
97
+ responseHeaders.set("content-type", "application/json");
98
+ }
99
+ return new Response(JSON.stringify(body), {
100
+ status,
101
+ headers: responseHeaders
102
+ });
103
+ }
104
+ function createErrorResponse(error) {
105
+ if (error instanceof ValidationError) {
106
+ return Response.json({
107
+ error: "Validation Error",
108
+ field: error.field,
109
+ issues: error.issues
110
+ }, { status: 400 });
111
+ }
112
+ if (error instanceof RouteNotFoundError) {
113
+ return Response.json({ error: "Not Found", message: error.message }, { status: 404 });
114
+ }
115
+ console.error("Internal server error:", error);
116
+ return Response.json({ error: "Internal Server Error" }, { status: 500 });
117
+ }
118
+
119
+ class Router {
120
+ contract;
121
+ handlers;
122
+ constructor(contract, handlers) {
123
+ this.contract = contract;
124
+ this.handlers = handlers;
125
+ }
126
+ findEndpoint(method, path) {
127
+ for (const [name, endpoint] of Object.entries(this.contract)) {
128
+ if (endpoint.method === method) {
129
+ const params = matchPath(endpoint.path, path);
130
+ if (params !== null) {
131
+ return { name, endpoint, params };
132
+ }
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ async handle(request) {
138
+ try {
139
+ const url = new URL(request.url);
140
+ const method = request.method;
141
+ const path = url.pathname;
142
+ const match = this.findEndpoint(method, path);
143
+ if (!match) {
144
+ throw new RouteNotFoundError(path, method);
145
+ }
146
+ const { name, endpoint, params } = match;
147
+ const handler = this.handlers[name];
148
+ const input = await parseRequest(request, endpoint, params);
149
+ const handlerResponse = await handler(input);
150
+ return createResponse(endpoint, handlerResponse);
151
+ } catch (error) {
152
+ return createErrorResponse(error);
153
+ }
154
+ }
155
+ get fetch() {
156
+ return (request) => this.handle(request);
157
+ }
158
+ }
159
+ function createRouter(contract, handlers) {
160
+ return new Router(contract, handlers);
161
+ }
162
+ export {
163
+ createRouter,
164
+ ValidationError,
165
+ Router,
166
+ RouteNotFoundError
167
+ };
168
+
169
+ //# debugId=8E946DD736505E1764756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../index.ts"],
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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "8E946DD736505E1764756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@richie-rpc/server",
3
+ "version": "0.1.0",
4
+ "type": "module"
5
+ }
@@ -0,0 +1,54 @@
1
+ import type { Contract, EndpointDefinition, ExtractBody, ExtractHeaders, ExtractParams, ExtractQuery } from '@richie-rpc/core';
2
+ import type { z } from 'zod';
3
+ export type HandlerInput<T extends EndpointDefinition> = {
4
+ params: ExtractParams<T>;
5
+ query: ExtractQuery<T>;
6
+ headers: ExtractHeaders<T>;
7
+ body: ExtractBody<T>;
8
+ request: Request;
9
+ };
10
+ export type HandlerResponse<T extends EndpointDefinition> = {
11
+ [Status in keyof T['responses']]: {
12
+ status: Status;
13
+ body: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;
14
+ headers?: Record<string, string>;
15
+ };
16
+ }[keyof T['responses']];
17
+ export type Handler<T extends EndpointDefinition> = (input: HandlerInput<T>) => Promise<HandlerResponse<T>> | HandlerResponse<T>;
18
+ export type ContractHandlers<T extends Contract> = {
19
+ [K in keyof T]: Handler<T[K]>;
20
+ };
21
+ export declare class ValidationError extends Error {
22
+ field: string;
23
+ issues: z.ZodIssue[];
24
+ constructor(field: string, issues: z.ZodIssue[], message?: string);
25
+ }
26
+ export declare class RouteNotFoundError extends Error {
27
+ path: string;
28
+ method: string;
29
+ constructor(path: string, method: string);
30
+ }
31
+ /**
32
+ * Router class that manages contract endpoints
33
+ */
34
+ export declare class Router<T extends Contract> {
35
+ private contract;
36
+ private handlers;
37
+ constructor(contract: T, handlers: ContractHandlers<T>);
38
+ /**
39
+ * Find matching endpoint for a request
40
+ */
41
+ private findEndpoint;
42
+ /**
43
+ * Handle a request
44
+ */
45
+ handle(request: Request): Promise<Response>;
46
+ /**
47
+ * Get fetch handler compatible with Bun.serve
48
+ */
49
+ get fetch(): (request: Request) => Promise<Response>;
50
+ }
51
+ /**
52
+ * Create a router from a contract and handlers
53
+ */
54
+ export declare function createRouter<T extends Contract>(contract: T, handlers: ContractHandlers<T>): Router<T>;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@richie-rpc/server",
3
+ "version": "0.1.0",
4
+ "main": "./dist/cjs/index.cjs",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/types/index.d.ts",
8
+ "require": "./dist/cjs/index.cjs",
9
+ "import": "./dist/mjs/index.mjs"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@richie-rpc/core": "^0.1.0",
14
+ "zod": "^3.23.8"
15
+ },
16
+ "peerDependencies": {
17
+ "typescript": "^5"
18
+ },
19
+ "author": "Richie <oss@ricsam.dev>",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/ricsam/richie-rpc.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/ricsam/richie-rpc/issues"
27
+ },
28
+ "homepage": "https://github.com/ricsam/richie-rpc#readme",
29
+ "keywords": [
30
+ "typescript",
31
+ "bun",
32
+ "zod",
33
+ "api",
34
+ "contract",
35
+ "rpc",
36
+ "rest",
37
+ "openapi",
38
+ "type-safe"
39
+ ],
40
+ "description": "Server implementation for Bun.serve with automatic validation",
41
+ "module": "./dist/mjs/index.mjs",
42
+ "types": "./dist/types/index.d.ts",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md"
49
+ ]
50
+ }