@jokio/rpc 0.3.0 → 0.3.2
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 +47 -35
- package/dist/index.d.mts +15 -5
- package/dist/index.d.ts +15 -5
- package/dist/index.js +20 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A type-safe RPC framework for TypeScript with Zod validation, designed for Express servers and HTTP clients.
|
|
4
4
|
|
|
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
|
+
|
|
7
|
+
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
10
|
- Full TypeScript type safety from server to client
|
|
@@ -15,7 +18,7 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
17
20
|
```bash
|
|
18
|
-
npm install @jokio/rpc
|
|
21
|
+
npm install @jokio/rpc
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
## Usage
|
|
@@ -23,8 +26,8 @@ npm install @jokio/rpc zod express
|
|
|
23
26
|
### 1. Define Your Router Configuration
|
|
24
27
|
|
|
25
28
|
```typescript
|
|
26
|
-
import { defineRouterConfig } from "@jokio/rpc"
|
|
27
|
-
import { z } from "zod"
|
|
29
|
+
import { defineRouterConfig } from "@jokio/rpc"
|
|
30
|
+
import { z } from "zod"
|
|
28
31
|
|
|
29
32
|
const routerConfig = defineRouterConfig({
|
|
30
33
|
GET: {
|
|
@@ -55,66 +58,74 @@ const routerConfig = defineRouterConfig({
|
|
|
55
58
|
}),
|
|
56
59
|
},
|
|
57
60
|
},
|
|
58
|
-
})
|
|
61
|
+
})
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
### 2. Set Up the Server
|
|
62
65
|
|
|
63
66
|
```typescript
|
|
64
|
-
import express from "express"
|
|
65
|
-
import { applyConfigToExpressRouter } from "@jokio/rpc"
|
|
67
|
+
import express from "express"
|
|
68
|
+
import { applyConfigToExpressRouter } from "@jokio/rpc"
|
|
66
69
|
|
|
67
|
-
const app = express()
|
|
68
|
-
app.use(express.json())
|
|
70
|
+
const app = express()
|
|
71
|
+
app.use(express.json())
|
|
69
72
|
|
|
70
|
-
const router = express.Router()
|
|
73
|
+
const router = express.Router()
|
|
71
74
|
|
|
72
75
|
applyConfigToExpressRouter(router, routerConfig, {
|
|
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
|
+
}),
|
|
73
81
|
GET: {
|
|
74
|
-
"/users/:id": async ({ query }) => {
|
|
75
|
-
// Handler implementation
|
|
82
|
+
"/users/:id": async ({ query }, ctx) => {
|
|
83
|
+
// Handler implementation with context
|
|
84
|
+
console.log("Current user:", ctx.userId)
|
|
76
85
|
return {
|
|
77
86
|
id: "1",
|
|
78
87
|
name: "John Doe",
|
|
79
88
|
email: "john@example.com",
|
|
80
|
-
}
|
|
89
|
+
}
|
|
81
90
|
},
|
|
82
91
|
},
|
|
83
92
|
POST: {
|
|
84
|
-
"/users": async ({ body, query }) => {
|
|
85
|
-
// Handler implementation
|
|
93
|
+
"/users": async ({ body, query }, ctx) => {
|
|
94
|
+
// Handler implementation with context
|
|
95
|
+
console.log("Creating user, requested by:", ctx.userId)
|
|
86
96
|
return {
|
|
87
97
|
id: "2",
|
|
88
98
|
name: body.name,
|
|
89
99
|
email: body.email,
|
|
90
|
-
}
|
|
100
|
+
}
|
|
91
101
|
},
|
|
92
102
|
},
|
|
93
|
-
})
|
|
103
|
+
})
|
|
94
104
|
|
|
95
|
-
app.use("/api", router)
|
|
96
|
-
app.listen(3000)
|
|
105
|
+
app.use("/api", router)
|
|
106
|
+
app.listen(3000)
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
### 3. Create a Type-Safe Client
|
|
100
110
|
|
|
101
111
|
```typescript
|
|
102
|
-
import { createClient } from "@jokio/rpc"
|
|
112
|
+
import { createClient } from "@jokio/rpc"
|
|
103
113
|
|
|
104
114
|
const client = createClient(routerConfig, {
|
|
105
115
|
baseUrl: "http://localhost:3000/api",
|
|
106
116
|
validateRequest: true, // Optional: validate requests on client-side
|
|
107
|
-
})
|
|
117
|
+
})
|
|
108
118
|
|
|
109
119
|
// Fully typed API calls
|
|
110
|
-
const user = await client.GET
|
|
120
|
+
const user = await client.GET("/users/23", {
|
|
111
121
|
query: { include: "posts" },
|
|
112
|
-
})
|
|
122
|
+
})
|
|
113
123
|
|
|
114
|
-
const newUser = await client.POST
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
124
|
+
const newUser = await client.POST(
|
|
125
|
+
"/users",
|
|
126
|
+
{ name: "Jane Doe", email: "jane@example.com" },
|
|
127
|
+
{ query: { sendEmail: true } }
|
|
128
|
+
)
|
|
118
129
|
```
|
|
119
130
|
|
|
120
131
|
## API Reference
|
|
@@ -131,7 +142,10 @@ Applies route handlers to an Express router with automatic validation.
|
|
|
131
142
|
|
|
132
143
|
- `router`: Express Router instance
|
|
133
144
|
- `config`: Router configuration object
|
|
134
|
-
- `handlers`: Handler functions for each route
|
|
145
|
+
- `handlers`: Handler functions for each route with optional context factory
|
|
146
|
+
- `ctx`: Optional function `(req: Request) => TContext` to provide context to handlers
|
|
147
|
+
- `GET`: Handler functions that receive `(data, ctx)` parameters
|
|
148
|
+
- `POST`: Handler functions that receive `(data, ctx)` parameters
|
|
135
149
|
|
|
136
150
|
### `createClient(config, options)`
|
|
137
151
|
|
|
@@ -150,15 +164,13 @@ The library provides end-to-end type safety:
|
|
|
150
164
|
|
|
151
165
|
```typescript
|
|
152
166
|
// TypeScript knows the exact shape of requests and responses
|
|
153
|
-
const result = await client.POST
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
});
|
|
167
|
+
const result = await client.POST("/users", {
|
|
168
|
+
name: "John",
|
|
169
|
+
email: "invalid-email", // Zod will catch this at runtime
|
|
170
|
+
})
|
|
159
171
|
|
|
160
172
|
// result is typed as { id: string; name: string; email: string }
|
|
161
|
-
console.log(result.id)
|
|
173
|
+
console.log(result.id)
|
|
162
174
|
```
|
|
163
175
|
|
|
164
176
|
## Error Handling
|
|
@@ -170,7 +182,7 @@ The library throws errors for:
|
|
|
170
182
|
|
|
171
183
|
```typescript
|
|
172
184
|
try {
|
|
173
|
-
await client.POST
|
|
185
|
+
await client.POST("/users", invalidData)
|
|
174
186
|
} catch (error) {
|
|
175
187
|
// Handle validation or HTTP errors
|
|
176
188
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -13,7 +13,7 @@ type RouterConfig = {
|
|
|
13
13
|
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
14
14
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never;
|
|
15
15
|
};
|
|
16
|
-
declare const
|
|
16
|
+
declare const defineRouterSchema: <T extends RouterConfig>(config: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
19
|
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result">) => Promise<InferRouteConfig<T["GET"][K]>["result"]>;
|
|
@@ -28,16 +28,26 @@ type CreateClientOptions = {
|
|
|
28
28
|
};
|
|
29
29
|
declare const createClient: <T extends RouterConfig>(config: T, options: CreateClientOptions) => RouterClient<T>;
|
|
30
30
|
|
|
31
|
+
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
|
|
32
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
33
|
+
} : T extends `${infer _Start}:${infer Param}` ? {
|
|
34
|
+
[K in Param]: string;
|
|
35
|
+
} : Record<string, never>;
|
|
31
36
|
type RouterHandlerConfig<T extends RouterConfig, TContext> = {
|
|
32
37
|
GET: {
|
|
33
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"
|
|
38
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"> & {
|
|
39
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
40
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
|
|
34
41
|
};
|
|
35
42
|
POST: {
|
|
36
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"
|
|
43
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"> & {
|
|
44
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
45
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
|
|
37
46
|
};
|
|
38
47
|
};
|
|
39
|
-
declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router,
|
|
48
|
+
declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router, schema: T, handlers: RouterHandlerConfig<T, TContext> & {
|
|
40
49
|
ctx?: (req: Request) => TContext;
|
|
50
|
+
schemaFilePath?: string;
|
|
41
51
|
}) => Router;
|
|
42
52
|
|
|
43
|
-
export { type InferRouteConfig, type RouteConfig, type RouterClient, type RouterConfig, type RouterHandlerConfig, applyConfigToExpressRouter, createClient,
|
|
53
|
+
export { type InferRouteConfig, type RouteConfig, type RouterClient, type RouterConfig, type RouterHandlerConfig, applyConfigToExpressRouter, createClient, defineRouterSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ type RouterConfig = {
|
|
|
13
13
|
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
14
14
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never;
|
|
15
15
|
};
|
|
16
|
-
declare const
|
|
16
|
+
declare const defineRouterSchema: <T extends RouterConfig>(config: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
19
|
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result">) => Promise<InferRouteConfig<T["GET"][K]>["result"]>;
|
|
@@ -28,16 +28,26 @@ type CreateClientOptions = {
|
|
|
28
28
|
};
|
|
29
29
|
declare const createClient: <T extends RouterConfig>(config: T, options: CreateClientOptions) => RouterClient<T>;
|
|
30
30
|
|
|
31
|
+
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
|
|
32
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
33
|
+
} : T extends `${infer _Start}:${infer Param}` ? {
|
|
34
|
+
[K in Param]: string;
|
|
35
|
+
} : Record<string, never>;
|
|
31
36
|
type RouterHandlerConfig<T extends RouterConfig, TContext> = {
|
|
32
37
|
GET: {
|
|
33
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"
|
|
38
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"> & {
|
|
39
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
40
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
|
|
34
41
|
};
|
|
35
42
|
POST: {
|
|
36
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"
|
|
43
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"> & {
|
|
44
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
45
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
|
|
37
46
|
};
|
|
38
47
|
};
|
|
39
|
-
declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router,
|
|
48
|
+
declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router, schema: T, handlers: RouterHandlerConfig<T, TContext> & {
|
|
40
49
|
ctx?: (req: Request) => TContext;
|
|
50
|
+
schemaFilePath?: string;
|
|
41
51
|
}) => Router;
|
|
42
52
|
|
|
43
|
-
export { type InferRouteConfig, type RouteConfig, type RouterClient, type RouterConfig, type RouterHandlerConfig, applyConfigToExpressRouter, createClient,
|
|
53
|
+
export { type InferRouteConfig, type RouteConfig, type RouterClient, type RouterConfig, type RouterHandlerConfig, applyConfigToExpressRouter, createClient, defineRouterSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
|
|
3
5
|
// src/client.ts
|
|
4
6
|
var createClient = (config, options) => {
|
|
5
7
|
const {
|
|
@@ -60,18 +62,17 @@ var createClient = (config, options) => {
|
|
|
60
62
|
};
|
|
61
63
|
return client;
|
|
62
64
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
66
|
-
router = Object.keys(config.GET).reduce(
|
|
65
|
+
var applyConfigToExpressRouter = (router, schema, handlers) => {
|
|
66
|
+
router = Object.keys(schema.GET).reduce(
|
|
67
67
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
68
68
|
try {
|
|
69
69
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
70
70
|
const data = {
|
|
71
|
-
|
|
71
|
+
params: req.params,
|
|
72
|
+
query: schema.GET[x]?.query?.parse(req.query)
|
|
72
73
|
};
|
|
73
74
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
74
|
-
const validatedResult =
|
|
75
|
+
const validatedResult = schema.GET[x]?.result.parse(result);
|
|
75
76
|
res.json(validatedResult);
|
|
76
77
|
} catch (err) {
|
|
77
78
|
next(err);
|
|
@@ -79,16 +80,23 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
|
79
80
|
}),
|
|
80
81
|
router
|
|
81
82
|
);
|
|
82
|
-
|
|
83
|
+
if (handlers.schemaFilePath) {
|
|
84
|
+
router = router.get(
|
|
85
|
+
"/__schema",
|
|
86
|
+
async (_, res) => res.contentType("text/plain").send(await promises.readFile(handlers.schemaFilePath, "utf8"))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
router = Object.keys(schema.POST).reduce(
|
|
83
90
|
(r, x) => r.post(x, async (req, res, next) => {
|
|
84
91
|
try {
|
|
85
92
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
86
93
|
const data = {
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
params: req.params,
|
|
95
|
+
body: schema.POST[x]?.body.parse(req.body),
|
|
96
|
+
query: schema.POST[x]?.query?.parse(req.query)
|
|
89
97
|
};
|
|
90
98
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
91
|
-
const validatedResult =
|
|
99
|
+
const validatedResult = schema.POST[x]?.result.parse(result);
|
|
92
100
|
res.json(validatedResult);
|
|
93
101
|
} catch (err) {
|
|
94
102
|
next(err);
|
|
@@ -100,10 +108,10 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
|
100
108
|
};
|
|
101
109
|
|
|
102
110
|
// src/types.ts
|
|
103
|
-
var
|
|
111
|
+
var defineRouterSchema = (config) => config;
|
|
104
112
|
|
|
105
113
|
exports.applyConfigToExpressRouter = applyConfigToExpressRouter;
|
|
106
114
|
exports.createClient = createClient;
|
|
107
|
-
exports.
|
|
115
|
+
exports.defineRouterSchema = defineRouterSchema;
|
|
108
116
|
//# sourceMappingURL=index.js.map
|
|
109
117
|
//# 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":[],"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 headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">,\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 ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.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 query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.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 body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["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 headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.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 RouterHandlerConfig<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 applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n schema: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(schema.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: schema.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = schema.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(schema.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: schema.POST[x]?.body.parse(req.body),\n query: schema.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = schema.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 defineRouterSchema = <T extends RouterConfig>(config: T): T =>\n config\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
|
|
1
3
|
// src/client.ts
|
|
2
4
|
var createClient = (config, options) => {
|
|
3
5
|
const {
|
|
@@ -58,18 +60,17 @@ var createClient = (config, options) => {
|
|
|
58
60
|
};
|
|
59
61
|
return client;
|
|
60
62
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
64
|
-
router = Object.keys(config.GET).reduce(
|
|
63
|
+
var applyConfigToExpressRouter = (router, schema, handlers) => {
|
|
64
|
+
router = Object.keys(schema.GET).reduce(
|
|
65
65
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
66
66
|
try {
|
|
67
67
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
68
68
|
const data = {
|
|
69
|
-
|
|
69
|
+
params: req.params,
|
|
70
|
+
query: schema.GET[x]?.query?.parse(req.query)
|
|
70
71
|
};
|
|
71
72
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
72
|
-
const validatedResult =
|
|
73
|
+
const validatedResult = schema.GET[x]?.result.parse(result);
|
|
73
74
|
res.json(validatedResult);
|
|
74
75
|
} catch (err) {
|
|
75
76
|
next(err);
|
|
@@ -77,16 +78,23 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
|
77
78
|
}),
|
|
78
79
|
router
|
|
79
80
|
);
|
|
80
|
-
|
|
81
|
+
if (handlers.schemaFilePath) {
|
|
82
|
+
router = router.get(
|
|
83
|
+
"/__schema",
|
|
84
|
+
async (_, res) => res.contentType("text/plain").send(await readFile(handlers.schemaFilePath, "utf8"))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
router = Object.keys(schema.POST).reduce(
|
|
81
88
|
(r, x) => r.post(x, async (req, res, next) => {
|
|
82
89
|
try {
|
|
83
90
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
84
91
|
const data = {
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
params: req.params,
|
|
93
|
+
body: schema.POST[x]?.body.parse(req.body),
|
|
94
|
+
query: schema.POST[x]?.query?.parse(req.query)
|
|
87
95
|
};
|
|
88
96
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
89
|
-
const validatedResult =
|
|
97
|
+
const validatedResult = schema.POST[x]?.result.parse(result);
|
|
90
98
|
res.json(validatedResult);
|
|
91
99
|
} catch (err) {
|
|
92
100
|
next(err);
|
|
@@ -98,8 +106,8 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
|
|
|
98
106
|
};
|
|
99
107
|
|
|
100
108
|
// src/types.ts
|
|
101
|
-
var
|
|
109
|
+
var defineRouterSchema = (config) => config;
|
|
102
110
|
|
|
103
|
-
export { applyConfigToExpressRouter, createClient,
|
|
111
|
+
export { applyConfigToExpressRouter, createClient, defineRouterSchema };
|
|
104
112
|
//# sourceMappingURL=index.mjs.map
|
|
105
113
|
//# 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":[],"mappings":";AAuBO,IAAM,YAAA,GAAe,CAC1B,MAAA,EACA,OAAA,KACoB;AACpB,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,UAAU,EAAC;AAAA,IACX,OAAO,WAAA,GAAc,KAAA;AAAA,IACrB,eAAA,GAAkB;AAAA,GACpB,GAAI,OAAA;AAEJ,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,KAAK,EAAC;AAAA,IACN,MAAM;AAAC,GACT;AAEA,EAAA,MAAA,CAAO,GAAA,GAAM,OAAO,IAAA,EAAc,IAAA,KAAe;AAC/C,IAAA,IAAI,eAAA,IAAmB,MAAM,KAAA,EAAO;AAClC,MAAA,MAAA,CAAO,IAAI,IAAI,CAAA,EAAG,KAAA,EAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,IAC3C;AACA,IAAA,MAAM,WAAA,GAAc,IAAA,EAAM,KAAA,GACtB,GAAA,GAAM,IAAI,gBAAgB,IAAA,CAAK,KAAK,CAAA,CAAE,QAAA,EAAS,GAC/C,EAAA;AACJ,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,CAAA,EAAG,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI;AAAA,MACpE,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAG;AAAA;AACL,KACD,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AACvC,MAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AACnB,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,OAAO,GAAA,CAAI,IAAI,CAAA,EAAG,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,EAC5C,CAAA;AAEA,EAAA,MAAA,CAAO,IAAA,GAAO,OAAO,IAAA,EAAc,IAAA,EAAW,IAAA,KAAe;AAC3D,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,EAAM,MAAM,IAAI,CAAA;AAAA,MACrC;AACA,MAAA,IAAI,MAAM,KAAA,EAAO;AACf,QAAA,MAAA,CAAO,KAAK,IAAI,CAAA,EAAG,KAAA,EAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,MAC5C;AAAA,IACF;AACA,IAAA,MAAM,WAAA,GAAc,IAAA,EAAM,KAAA,GACtB,GAAA,GAAM,IAAI,gBAAgB,IAAA,CAAK,KAAK,CAAA,CAAE,QAAA,EAAS,GAC/C,EAAA;AACJ,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,CAAA,EAAG,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI;AAAA,MACpE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAG;AAAA,OACL;AAAA,MACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI;AAAA,KAC1B,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AACvC,MAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AACnB,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,OAAO,IAAA,CAAK,IAAI,CAAA,EAAG,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,OAAO,MAAA;AACT;;;AC5EO,IAAM,0BAAA,GAA6B,CACxC,MAAA,EACA,MAAA,EACA,QAAA,KAGG;AACH,EAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA,CAAE,MAAA;AAAA,IAC/B,CAAC,GAAG,CAAA,KACF,CAAA,CAAE,IAAI,CAAA,EAAG,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AACjC,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAO,QAAA,CAAS,GAAA,GAAM,GAAG,KAAK,EAAC;AAErC,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,KAAA,EAAO,OAAO,GAAA,CAAI,CAAC,GAAG,KAAA,EAAO,KAAA,CAAM,IAAI,KAAK;AAAA,SAC9C;AACA,QAAA,MAAM,SAAS,MAAM,QAAA,CAAS,IAAI,CAAC,CAAA,GAAI,MAAa,GAAG,CAAA;AACvD,QAAA,MAAM,kBAAkB,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,MAAA,CAAO,MAAM,MAAM,CAAA;AAC1D,QAAA,GAAA,CAAI,KAAK,eAAe,CAAA;AAAA,MAC1B,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,GAAG,CAAA;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AAEA,EAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA;AAAA,IAChC,CAAC,GAAG,CAAA,KACF,CAAA,CAAE,KAAK,CAAA,EAAG,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AAClC,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAO,QAAA,CAAS,GAAA,GAAM,GAAG,KAAK,EAAC;AAErC,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,IAAA,EAAM,OAAO,IAAA,CAAK,CAAC,GAAG,IAAA,CAAK,KAAA,CAAM,IAAI,IAAI,CAAA;AAAA,UACzC,KAAA,EAAO,OAAO,IAAA,CAAK,CAAC,GAAG,KAAA,EAAO,KAAA,CAAM,IAAI,KAAK;AAAA,SAC/C;AACA,QAAA,MAAM,SAAS,MAAM,QAAA,CAAS,KAAK,CAAC,CAAA,GAAI,MAAa,GAAG,CAAA;AACxD,QAAA,MAAM,kBAAkB,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,MAAA,CAAO,MAAM,MAAM,CAAA;AAC3D,QAAA,GAAA,CAAI,KAAK,eAAe,CAAA;AAAA,MAC1B,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,GAAG,CAAA;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AClDO,IAAM,kBAAA,GAAqB,CAAyB,MAAA,KACzD","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 headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">,\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 ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.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 query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.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 body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":[],"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 headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.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 RouterHandlerConfig<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 applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n schema: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(schema.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: schema.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = schema.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(schema.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: schema.POST[x]?.body.parse(req.body),\n query: schema.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = schema.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 defineRouterSchema = <T extends RouterConfig>(config: T): T =>\n config\n"]}
|