@jokio/rpc 0.4.0 → 0.5.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 +20 -35
- package/dist/index.d.mts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
|
|
|
4
4
|
|
|
5
5
|
<img width="400" height="400" alt="ChatGPT Image Jan 4, 2026 at 10_15_01 AM" src="https://github.com/user-attachments/assets/5ca6462a-4d3a-46d6-ac09-31ecbc4d06fb" />
|
|
6
6
|
|
|
7
|
+
## Use Cases
|
|
8
|
+
|
|
9
|
+
**Front-end → Backend** | **Backend → Backend**
|
|
7
10
|
|
|
8
11
|
## Features
|
|
9
12
|
|
|
@@ -11,7 +14,6 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
|
|
|
11
14
|
- Runtime validation using Zod schemas
|
|
12
15
|
- Express.js integration for server-side
|
|
13
16
|
- Flexible fetch-based client with custom fetch support
|
|
14
|
-
- Support for both GET and POST routes
|
|
15
17
|
- Query parameters and request body validation
|
|
16
18
|
- Automatic response validation
|
|
17
19
|
|
|
@@ -31,11 +33,8 @@ import { z } from "zod"
|
|
|
31
33
|
|
|
32
34
|
const routes = defineRoutes({
|
|
33
35
|
GET: {
|
|
34
|
-
"/
|
|
35
|
-
|
|
36
|
-
include: z.enum(["posts", "comments"]).optional(),
|
|
37
|
-
}),
|
|
38
|
-
result: z.object({
|
|
36
|
+
"/user/:id": {
|
|
37
|
+
response: z.object({
|
|
39
38
|
id: z.string(),
|
|
40
39
|
name: z.string(),
|
|
41
40
|
email: z.string(),
|
|
@@ -43,15 +42,12 @@ const routes = defineRoutes({
|
|
|
43
42
|
},
|
|
44
43
|
},
|
|
45
44
|
POST: {
|
|
46
|
-
"/
|
|
45
|
+
"/user": {
|
|
47
46
|
body: z.object({
|
|
48
47
|
name: z.string(),
|
|
49
48
|
email: z.string().email(),
|
|
50
49
|
}),
|
|
51
|
-
|
|
52
|
-
sendEmail: z.boolean().optional(),
|
|
53
|
-
}),
|
|
54
|
-
result: z.object({
|
|
50
|
+
response: z.object({
|
|
55
51
|
id: z.string(),
|
|
56
52
|
name: z.string(),
|
|
57
53
|
email: z.string(),
|
|
@@ -65,34 +61,25 @@ const routes = defineRoutes({
|
|
|
65
61
|
|
|
66
62
|
```typescript
|
|
67
63
|
import express from "express"
|
|
68
|
-
import {
|
|
64
|
+
import { registerExpressRoutes } from "@jokio/rpc"
|
|
69
65
|
|
|
70
66
|
const app = express()
|
|
71
67
|
app.use(express.json())
|
|
72
68
|
|
|
73
69
|
const router = express.Router()
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
// Optional: Define a context factory function
|
|
77
|
-
ctx: (req) => ({
|
|
78
|
-
userId: req.headers["x-user-id"] as string,
|
|
79
|
-
// Add other context properties here
|
|
80
|
-
}),
|
|
71
|
+
registerExpressRoutes(router, routes, {
|
|
81
72
|
GET: {
|
|
82
|
-
"/
|
|
83
|
-
// Handler implementation with context
|
|
84
|
-
console.log("Current user:", ctx.userId)
|
|
73
|
+
"/user/:id": async ({ params }) => {
|
|
85
74
|
return {
|
|
86
|
-
id:
|
|
75
|
+
id: params.id,
|
|
87
76
|
name: "John Doe",
|
|
88
77
|
email: "john@example.com",
|
|
89
78
|
}
|
|
90
79
|
},
|
|
91
80
|
},
|
|
92
81
|
POST: {
|
|
93
|
-
"/
|
|
94
|
-
// Handler implementation with context
|
|
95
|
-
console.log("Creating user, requested by:", ctx.userId)
|
|
82
|
+
"/user": async ({ body }) => {
|
|
96
83
|
return {
|
|
97
84
|
id: "2",
|
|
98
85
|
name: body.name,
|
|
@@ -117,15 +104,12 @@ const client = createClient(routes, {
|
|
|
117
104
|
})
|
|
118
105
|
|
|
119
106
|
// Fully typed API calls
|
|
120
|
-
const user = await client.GET("/users/23"
|
|
121
|
-
query: { include: "posts" },
|
|
122
|
-
})
|
|
107
|
+
const user = await client.GET("/users/23")
|
|
123
108
|
|
|
124
|
-
const newUser = await client.POST(
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
109
|
+
const newUser = await client.POST("/users", {
|
|
110
|
+
name: "Jane Doe",
|
|
111
|
+
email: "jane@example.com",
|
|
112
|
+
})
|
|
129
113
|
```
|
|
130
114
|
|
|
131
115
|
## API Reference
|
|
@@ -135,11 +119,12 @@ const newUser = await client.POST(
|
|
|
135
119
|
Helper function to define routes with type inference.
|
|
136
120
|
|
|
137
121
|
**Parameters:**
|
|
122
|
+
|
|
138
123
|
- `routes`: Route definitions object containing GET and POST route configurations
|
|
139
124
|
|
|
140
|
-
### `
|
|
125
|
+
### `registerExpressRoutes(router, routes, handlers)`
|
|
141
126
|
|
|
142
|
-
|
|
127
|
+
Registers route handlers to an Express router with automatic validation.
|
|
143
128
|
|
|
144
129
|
**Parameters:**
|
|
145
130
|
|
package/dist/index.d.mts
CHANGED
|
@@ -3,8 +3,8 @@ import { Router, Request } from 'express';
|
|
|
3
3
|
|
|
4
4
|
type RouteConfig = {
|
|
5
5
|
body: z.ZodType;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
queryParams?: z.ZodType;
|
|
7
|
+
response: z.ZodType;
|
|
8
8
|
};
|
|
9
9
|
type RouterConfig = {
|
|
10
10
|
GET: Record<string, Omit<RouteConfig, "body">>;
|
|
@@ -16,8 +16,8 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "
|
|
19
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response">) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
20
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "response">) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
21
|
};
|
|
22
22
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
23
|
type CreateClientOptions = {
|
|
@@ -36,19 +36,19 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
|
|
|
36
36
|
} : Record<string, never>;
|
|
37
37
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
38
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
39
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response"> & {
|
|
40
40
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["
|
|
41
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
42
42
|
};
|
|
43
43
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "
|
|
44
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "response"> & {
|
|
45
45
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["
|
|
46
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
|
-
declare const
|
|
49
|
+
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
|
50
50
|
ctx?: (req: Request) => TContext;
|
|
51
51
|
schemaFilePath?: string;
|
|
52
52
|
}) => Router;
|
|
53
53
|
|
|
54
|
-
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient,
|
|
54
|
+
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { Router, Request } from 'express';
|
|
|
3
3
|
|
|
4
4
|
type RouteConfig = {
|
|
5
5
|
body: z.ZodType;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
queryParams?: z.ZodType;
|
|
7
|
+
response: z.ZodType;
|
|
8
8
|
};
|
|
9
9
|
type RouterConfig = {
|
|
10
10
|
GET: Record<string, Omit<RouteConfig, "body">>;
|
|
@@ -16,8 +16,8 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "
|
|
19
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response">) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
20
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "response">) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
21
|
};
|
|
22
22
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
23
|
type CreateClientOptions = {
|
|
@@ -36,19 +36,19 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
|
|
|
36
36
|
} : Record<string, never>;
|
|
37
37
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
38
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
39
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response"> & {
|
|
40
40
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["
|
|
41
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
42
42
|
};
|
|
43
43
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "
|
|
44
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "response"> & {
|
|
45
45
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["
|
|
46
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
|
-
declare const
|
|
49
|
+
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
|
50
50
|
ctx?: (req: Request) => TContext;
|
|
51
51
|
schemaFilePath?: string;
|
|
52
52
|
}) => Router;
|
|
53
53
|
|
|
54
|
-
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient,
|
|
54
|
+
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,10 @@ var createClient = (routes, options) => {
|
|
|
15
15
|
POST: {}
|
|
16
16
|
};
|
|
17
17
|
client.GET = async (path, options2) => {
|
|
18
|
-
if (validate && options2?.
|
|
19
|
-
routes.GET[path]?.
|
|
18
|
+
if (validate && options2?.queryParams) {
|
|
19
|
+
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
20
20
|
}
|
|
21
|
-
const queryString = options2?.
|
|
21
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
22
22
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
23
23
|
method: "GET",
|
|
24
24
|
headers: {
|
|
@@ -34,18 +34,18 @@ var createClient = (routes, options) => {
|
|
|
34
34
|
throw new Error(error.message);
|
|
35
35
|
}
|
|
36
36
|
const json = await response.json();
|
|
37
|
-
return routes.GET[path]?.
|
|
37
|
+
return routes.GET[path]?.response.parse(json);
|
|
38
38
|
};
|
|
39
39
|
client.POST = async (path, body, options2) => {
|
|
40
40
|
if (validate) {
|
|
41
41
|
if (body) {
|
|
42
42
|
routes.POST[path]?.body?.parse(body);
|
|
43
43
|
}
|
|
44
|
-
if (options2?.
|
|
45
|
-
routes.POST[path]?.
|
|
44
|
+
if (options2?.queryParams) {
|
|
45
|
+
routes.POST[path]?.queryParams?.parse(options2.queryParams);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
const queryString = options2?.
|
|
48
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
49
49
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
50
50
|
method: "POST",
|
|
51
51
|
headers: {
|
|
@@ -62,21 +62,21 @@ var createClient = (routes, options) => {
|
|
|
62
62
|
throw new Error(error.message);
|
|
63
63
|
}
|
|
64
64
|
const json = await response.json();
|
|
65
|
-
return routes.POST[path]?.
|
|
65
|
+
return routes.POST[path]?.response.parse(json);
|
|
66
66
|
};
|
|
67
67
|
return client;
|
|
68
68
|
};
|
|
69
|
-
var
|
|
69
|
+
var registerExpressRoutes = (router, routes, handlers) => {
|
|
70
70
|
router = Object.keys(routes.GET).reduce(
|
|
71
71
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
72
72
|
try {
|
|
73
73
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
74
74
|
const data = {
|
|
75
75
|
params: req.params,
|
|
76
|
-
|
|
76
|
+
queryParams: routes.GET[x]?.queryParams?.parse(req.query)
|
|
77
77
|
};
|
|
78
78
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
79
|
-
const validatedResult = routes.GET[x]?.
|
|
79
|
+
const validatedResult = routes.GET[x]?.response.parse(result);
|
|
80
80
|
res.json(validatedResult);
|
|
81
81
|
} catch (err) {
|
|
82
82
|
next(err);
|
|
@@ -97,10 +97,10 @@ var createExpressRouter = (router, routes, handlers) => {
|
|
|
97
97
|
const data = {
|
|
98
98
|
params: req.params,
|
|
99
99
|
body: routes.POST[x]?.body.parse(req.body),
|
|
100
|
-
|
|
100
|
+
queryParams: routes.POST[x]?.queryParams?.parse(req.query)
|
|
101
101
|
};
|
|
102
102
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
103
|
-
const validatedResult = routes.POST[x]?.
|
|
103
|
+
const validatedResult = routes.POST[x]?.response.parse(result);
|
|
104
104
|
res.json(validatedResult);
|
|
105
105
|
} catch (err) {
|
|
106
106
|
next(err);
|
|
@@ -115,7 +115,7 @@ var createExpressRouter = (router, routes, handlers) => {
|
|
|
115
115
|
var defineRoutes = (routes) => routes;
|
|
116
116
|
|
|
117
117
|
exports.createClient = createClient;
|
|
118
|
-
exports.createExpressRouter = createExpressRouter;
|
|
119
118
|
exports.defineRoutes = defineRoutes;
|
|
119
|
+
exports.registerExpressRoutes = registerExpressRoutes;
|
|
120
120
|
//# sourceMappingURL=index.js.map
|
|
121
121
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options","readFile"],"mappings":"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","file":"index.js","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.query) {\n routes.GET[path]?.query?.parse(options.query)\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.query) {\n routes.POST[path]?.query?.parse(options.query)\n }\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const createExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n query: routes.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.result.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: routes.POST[x]?.body.parse(req.body),\n query: routes.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouteConfig = {\n body: z.ZodType\n query?: z.ZodType\n result: z.ZodType\n}\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n POST: Record<string, RouteConfig>\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options","readFile"],"mappings":"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","file":"index.js","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"response\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.queryParams) {\n routes.GET[path]?.queryParams?.parse(options.queryParams)\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.GET[path]?.response.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.queryParams) {\n routes.POST[path]?.queryParams?.parse(options.queryParams)\n }\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.POST[path]?.response.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"response\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"response\"]\n }\n}\n\nexport const registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n queryParams: routes.GET[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.response.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: routes.POST[x]?.body.parse(req.body),\n queryParams: routes.POST[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.response.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n POST: Record<string, RouteConfig>\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -13,10 +13,10 @@ var createClient = (routes, options) => {
|
|
|
13
13
|
POST: {}
|
|
14
14
|
};
|
|
15
15
|
client.GET = async (path, options2) => {
|
|
16
|
-
if (validate && options2?.
|
|
17
|
-
routes.GET[path]?.
|
|
16
|
+
if (validate && options2?.queryParams) {
|
|
17
|
+
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
18
18
|
}
|
|
19
|
-
const queryString = options2?.
|
|
19
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
20
20
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
21
21
|
method: "GET",
|
|
22
22
|
headers: {
|
|
@@ -32,18 +32,18 @@ var createClient = (routes, options) => {
|
|
|
32
32
|
throw new Error(error.message);
|
|
33
33
|
}
|
|
34
34
|
const json = await response.json();
|
|
35
|
-
return routes.GET[path]?.
|
|
35
|
+
return routes.GET[path]?.response.parse(json);
|
|
36
36
|
};
|
|
37
37
|
client.POST = async (path, body, options2) => {
|
|
38
38
|
if (validate) {
|
|
39
39
|
if (body) {
|
|
40
40
|
routes.POST[path]?.body?.parse(body);
|
|
41
41
|
}
|
|
42
|
-
if (options2?.
|
|
43
|
-
routes.POST[path]?.
|
|
42
|
+
if (options2?.queryParams) {
|
|
43
|
+
routes.POST[path]?.queryParams?.parse(options2.queryParams);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
const queryString = options2?.
|
|
46
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
47
47
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
48
48
|
method: "POST",
|
|
49
49
|
headers: {
|
|
@@ -60,21 +60,21 @@ var createClient = (routes, options) => {
|
|
|
60
60
|
throw new Error(error.message);
|
|
61
61
|
}
|
|
62
62
|
const json = await response.json();
|
|
63
|
-
return routes.POST[path]?.
|
|
63
|
+
return routes.POST[path]?.response.parse(json);
|
|
64
64
|
};
|
|
65
65
|
return client;
|
|
66
66
|
};
|
|
67
|
-
var
|
|
67
|
+
var registerExpressRoutes = (router, routes, handlers) => {
|
|
68
68
|
router = Object.keys(routes.GET).reduce(
|
|
69
69
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
70
70
|
try {
|
|
71
71
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
72
72
|
const data = {
|
|
73
73
|
params: req.params,
|
|
74
|
-
|
|
74
|
+
queryParams: routes.GET[x]?.queryParams?.parse(req.query)
|
|
75
75
|
};
|
|
76
76
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
77
|
-
const validatedResult = routes.GET[x]?.
|
|
77
|
+
const validatedResult = routes.GET[x]?.response.parse(result);
|
|
78
78
|
res.json(validatedResult);
|
|
79
79
|
} catch (err) {
|
|
80
80
|
next(err);
|
|
@@ -95,10 +95,10 @@ var createExpressRouter = (router, routes, handlers) => {
|
|
|
95
95
|
const data = {
|
|
96
96
|
params: req.params,
|
|
97
97
|
body: routes.POST[x]?.body.parse(req.body),
|
|
98
|
-
|
|
98
|
+
queryParams: routes.POST[x]?.queryParams?.parse(req.query)
|
|
99
99
|
};
|
|
100
100
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
101
|
-
const validatedResult = routes.POST[x]?.
|
|
101
|
+
const validatedResult = routes.POST[x]?.response.parse(result);
|
|
102
102
|
res.json(validatedResult);
|
|
103
103
|
} catch (err) {
|
|
104
104
|
next(err);
|
|
@@ -112,6 +112,6 @@ var createExpressRouter = (router, routes, handlers) => {
|
|
|
112
112
|
// src/types.ts
|
|
113
113
|
var defineRoutes = (routes) => routes;
|
|
114
114
|
|
|
115
|
-
export { createClient,
|
|
115
|
+
export { createClient, defineRoutes, registerExpressRoutes };
|
|
116
116
|
//# sourceMappingURL=index.mjs.map
|
|
117
117
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.query) {\n routes.GET[path]?.query?.parse(options.query)\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.query) {\n routes.POST[path]?.query?.parse(options.query)\n }\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const createExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n query: routes.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.result.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: routes.POST[x]?.body.parse(req.body),\n query: routes.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouteConfig = {\n body: z.ZodType\n query?: z.ZodType\n result: z.ZodType\n}\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n POST: Record<string, RouteConfig>\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"response\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.queryParams) {\n routes.GET[path]?.queryParams?.parse(options.queryParams)\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.GET[path]?.response.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.queryParams) {\n routes.POST[path]?.queryParams?.parse(options.queryParams)\n }\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return routes.POST[path]?.response.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"response\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"response\"]\n }\n}\n\nexport const registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n queryParams: routes.GET[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.response.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: routes.POST[x]?.body.parse(req.body),\n queryParams: routes.POST[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.response.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n POST: Record<string, RouteConfig>\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|