@jokio/rpc 0.8.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -127
- package/dist/index.d.mts +14 -13
- package/dist/index.d.ts +14 -13
- package/dist/index.js +42 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jokio/rpc
|
|
2
2
|
|
|
3
|
-
A type-safe RPC framework for TypeScript
|
|
3
|
+
A type-safe RPC framework for TypeScript designed for Express servers and HTTP clients. Supports both Zod schemas (with runtime validation) and plain TypeScript types (for type safety without runtime overhead).
|
|
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
|
|
|
@@ -11,71 +11,77 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- Full TypeScript type safety from server to client
|
|
14
|
-
-
|
|
14
|
+
- Two route definition styles: **Zod schemas** (with runtime validation) or **plain TypeScript types** (type-only, no runtime overhead)
|
|
15
15
|
- Express.js integration for server-side
|
|
16
16
|
- Flexible fetch-based client with custom fetch support
|
|
17
17
|
- Support for multiple HTTP methods (GET, POST, PUT, PATCH, DELETE, QUERY)
|
|
18
|
-
- Path parameters, query parameters, and request
|
|
18
|
+
- Path parameters, query parameters, and request payload validation
|
|
19
19
|
- Automatic response validation
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install @jokio/rpc
|
|
24
|
+
npm install @jokio/rpc
|
|
25
|
+
|
|
26
|
+
# Optional: install zod if you want runtime validation
|
|
27
|
+
npm install zod
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
## Usage
|
|
28
31
|
|
|
29
32
|
### 1. Define Your Routes
|
|
30
33
|
|
|
34
|
+
You can define routes using **Zod schemas** (enables runtime validation) or **plain TypeScript types** (type safety only, no runtime cost).
|
|
35
|
+
|
|
36
|
+
#### Option A: Zod Schemas
|
|
37
|
+
|
|
31
38
|
```typescript
|
|
32
39
|
import { defineRoutes } from "@jokio/rpc"
|
|
33
40
|
import { z } from "zod"
|
|
34
41
|
|
|
35
42
|
const routes = defineRoutes({
|
|
36
43
|
GET: {
|
|
37
|
-
"/
|
|
38
|
-
|
|
39
|
-
include: z.string().optional(),
|
|
40
|
-
}),
|
|
41
|
-
response: z.object({
|
|
42
|
-
id: z.string(),
|
|
43
|
-
name: z.string(),
|
|
44
|
-
email: z.string(),
|
|
45
|
-
}),
|
|
44
|
+
"/room/:id": {
|
|
45
|
+
response: z.object({ name: z.string() }),
|
|
46
46
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"/user": {
|
|
50
|
-
body: z.object({
|
|
51
|
-
name: z.string(),
|
|
52
|
-
email: z.string().email(),
|
|
53
|
-
}),
|
|
54
|
-
response: z.object({
|
|
55
|
-
id: z.string(),
|
|
56
|
-
name: z.string(),
|
|
57
|
-
email: z.string(),
|
|
58
|
-
}),
|
|
47
|
+
"/rooms": {
|
|
48
|
+
response: z.any(),
|
|
59
49
|
},
|
|
60
50
|
},
|
|
61
|
-
|
|
62
|
-
"/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
email: z.string().email().optional(),
|
|
66
|
-
}),
|
|
67
|
-
response: z.object({
|
|
68
|
-
id: z.string(),
|
|
69
|
-
name: z.string(),
|
|
70
|
-
email: z.string(),
|
|
71
|
-
}),
|
|
51
|
+
POST: {
|
|
52
|
+
"/room": {
|
|
53
|
+
payload: z.any(),
|
|
54
|
+
response: z.any(),
|
|
72
55
|
},
|
|
73
56
|
},
|
|
74
57
|
})
|
|
75
58
|
```
|
|
76
59
|
|
|
60
|
+
#### Option B: Plain TypeScript Types
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
type ApiRoutes = {
|
|
64
|
+
GET: {
|
|
65
|
+
"/room/:id": {
|
|
66
|
+
response: { name: string }
|
|
67
|
+
}
|
|
68
|
+
"/rooms": {
|
|
69
|
+
response: { count: number }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
POST: {
|
|
73
|
+
"/room": {
|
|
74
|
+
payload: { name: string }
|
|
75
|
+
response: number
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
77
81
|
### 2. Set Up the Server
|
|
78
82
|
|
|
83
|
+
#### With Zod Routes
|
|
84
|
+
|
|
79
85
|
```typescript
|
|
80
86
|
import express from "express"
|
|
81
87
|
import { registerExpressRoutes } from "@jokio/rpc"
|
|
@@ -85,37 +91,13 @@ app.use(express.json())
|
|
|
85
91
|
|
|
86
92
|
const router = express.Router()
|
|
87
93
|
|
|
88
|
-
registerExpressRoutes(router, routes, {
|
|
94
|
+
registerExpressRoutes(router, { routes }, {
|
|
89
95
|
GET: {
|
|
90
|
-
"/
|
|
91
|
-
|
|
92
|
-
// queryParams.include is validated by Zod
|
|
93
|
-
return {
|
|
94
|
-
id: params.id,
|
|
95
|
-
name: "John Doe",
|
|
96
|
-
email: "john@example.com",
|
|
97
|
-
}
|
|
98
|
-
},
|
|
96
|
+
"/room/:id": ({ params }) => ({ name: params.id }),
|
|
97
|
+
"/rooms": () => ({ count: 10 }),
|
|
99
98
|
},
|
|
100
99
|
POST: {
|
|
101
|
-
"/
|
|
102
|
-
// body is validated by Zod
|
|
103
|
-
return {
|
|
104
|
-
id: "2",
|
|
105
|
-
name: body.name,
|
|
106
|
-
email: body.email,
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
PUT: {
|
|
111
|
-
"/user/:id": async ({ params, body }) => {
|
|
112
|
-
// Both params and body are type-safe
|
|
113
|
-
return {
|
|
114
|
-
id: params.id,
|
|
115
|
-
name: body.name ?? "John Doe",
|
|
116
|
-
email: body.email ?? "john@example.com",
|
|
117
|
-
}
|
|
118
|
-
},
|
|
100
|
+
"/room": ({ payload }) => ({ id: "1" }),
|
|
119
101
|
},
|
|
120
102
|
})
|
|
121
103
|
|
|
@@ -123,38 +105,38 @@ app.use("/api", router)
|
|
|
123
105
|
app.listen(3000)
|
|
124
106
|
```
|
|
125
107
|
|
|
126
|
-
|
|
108
|
+
#### With TypeScript Types
|
|
109
|
+
|
|
110
|
+
When using plain TypeScript types, pass the type as a generic parameter. No `routes` object is needed — you get full type safety without runtime validation.
|
|
127
111
|
|
|
128
112
|
```typescript
|
|
129
|
-
|
|
113
|
+
registerExpressRoutes<ApiRoutes, { userId: number }>(
|
|
114
|
+
router,
|
|
115
|
+
{ ctx: (req) => ({ userId: 123 }) },
|
|
116
|
+
{
|
|
117
|
+
GET: {
|
|
118
|
+
"/room/:id": ({ params }) => ({ name: params.id }),
|
|
119
|
+
"/rooms": (_, ctx) => ({ count: ctx.userId }),
|
|
120
|
+
},
|
|
121
|
+
POST: {
|
|
122
|
+
"/room": ({ payload }) => payload.name.length,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
```
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
baseUrl: "http://localhost:3000/api",
|
|
133
|
-
validate: true, // Optional: validate requests on client-side
|
|
134
|
-
})
|
|
128
|
+
### 3. Create a Type-Safe Client
|
|
135
129
|
|
|
136
|
-
|
|
137
|
-
const user = await client.GET("/user/:id", {
|
|
138
|
-
params: { id: "23" },
|
|
139
|
-
queryParams: { include: "profile" },
|
|
140
|
-
})
|
|
130
|
+
The client uses the Zod route definitions for both type inference and optional runtime validation.
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
name: "Jane Doe",
|
|
145
|
-
email: "jane@example.com",
|
|
146
|
-
})
|
|
132
|
+
```typescript
|
|
133
|
+
import { createClient } from "@jokio/rpc"
|
|
147
134
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
params: { id: "23" },
|
|
156
|
-
}
|
|
157
|
-
)
|
|
135
|
+
const client = createClient("http://localhost:3000/api", { routes })
|
|
136
|
+
|
|
137
|
+
// Fully typed response — .name is inferred from the Zod schema
|
|
138
|
+
const room = await client.GET("/room/:id")
|
|
139
|
+
console.log(room.name)
|
|
158
140
|
```
|
|
159
141
|
|
|
160
142
|
## API Reference
|
|
@@ -169,39 +151,44 @@ Helper function to define routes with type inference.
|
|
|
169
151
|
|
|
170
152
|
**Route Configuration:**
|
|
171
153
|
|
|
172
|
-
|
|
173
|
-
- `queryParams`: Zod schema for query parameters (optional)
|
|
174
|
-
- `response`: Zod schema for response data
|
|
154
|
+
Each route accepts the following fields as either a Zod schema or a plain TypeScript type:
|
|
175
155
|
|
|
176
|
-
|
|
156
|
+
- `payload`: Request body (not available for GET)
|
|
157
|
+
- `queryParams`: Query parameters (optional)
|
|
158
|
+
- `response`: Response data
|
|
159
|
+
|
|
160
|
+
### `registerExpressRoutes(router, config, handlers)`
|
|
177
161
|
|
|
178
162
|
Registers route handlers to an Express router with automatic validation.
|
|
179
163
|
|
|
180
164
|
**Parameters:**
|
|
181
165
|
|
|
182
166
|
- `router`: Express Router instance
|
|
183
|
-
- `
|
|
184
|
-
- `
|
|
167
|
+
- `config`: Configuration object
|
|
168
|
+
- `routes`: Optional route definitions object (Zod schemas — omit when using plain TS types)
|
|
185
169
|
- `ctx`: Optional function `(req: Request) => TContext` to provide context to handlers
|
|
186
|
-
- `validation`: Optional boolean to enable response validation (default:
|
|
187
|
-
- `schemaFile`: Optional
|
|
170
|
+
- `validation`: Optional boolean to enable response validation (default: true)
|
|
171
|
+
- `schemaFile`: Optional string to expose route schemas at `/__routes` endpoint
|
|
172
|
+
- `handlers`: Handler functions for each route
|
|
188
173
|
- `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `QUERY`: Handler functions that receive `(data, ctx)` parameters
|
|
189
174
|
- `data.params`: Path parameters (e.g., `:id` in `/user/:id`)
|
|
190
|
-
- `data.
|
|
191
|
-
- `data.queryParams`: Query parameters (validated by Zod)
|
|
175
|
+
- `data.payload`: Request payload (validated by Zod if schemas provided)
|
|
176
|
+
- `data.queryParams`: Query parameters (validated by Zod if schemas provided)
|
|
177
|
+
|
|
178
|
+
When using plain TypeScript types, pass the type as a generic: `registerExpressRoutes<MyRoutes>(...)`. Zod validation is skipped since there are no schemas.
|
|
192
179
|
|
|
193
|
-
### `createClient(
|
|
180
|
+
### `createClient(baseUrl, options)`
|
|
194
181
|
|
|
195
182
|
Creates a type-safe HTTP client.
|
|
196
183
|
|
|
197
184
|
**Parameters:**
|
|
198
185
|
|
|
199
|
-
- `
|
|
186
|
+
- `baseUrl`: Base URL for API requests
|
|
200
187
|
- `options`: Client configuration options
|
|
201
|
-
- `
|
|
188
|
+
- `routes`: Route definitions object (Zod schemas for type inference)
|
|
202
189
|
- `getHeaders`: Optional function that returns headers (sync or async)
|
|
203
190
|
- `fetch`: Optional custom fetch function (useful for Node.js or testing)
|
|
204
|
-
- `validate`: Enable client-side request validation (default:
|
|
191
|
+
- `validate`: Enable client-side request validation (default: false)
|
|
205
192
|
- `debug`: Enable debug logging (default: false)
|
|
206
193
|
|
|
207
194
|
**Client Methods:**
|
|
@@ -211,35 +198,30 @@ Each HTTP method has a type-safe method on the client:
|
|
|
211
198
|
- `GET(path, options?)`: For GET requests
|
|
212
199
|
- `options.params`: Path parameters
|
|
213
200
|
- `options.queryParams`: Query parameters
|
|
214
|
-
- `POST(path,
|
|
215
|
-
- `PUT(path,
|
|
216
|
-
- `PATCH(path,
|
|
217
|
-
- `DELETE(path,
|
|
218
|
-
- `QUERY(path,
|
|
201
|
+
- `POST(path, payload, options?)`: For POST requests
|
|
202
|
+
- `PUT(path, payload, options?)`: For PUT requests
|
|
203
|
+
- `PATCH(path, payload, options?)`: For PATCH requests
|
|
204
|
+
- `DELETE(path, payload, options?)`: For DELETE requests
|
|
205
|
+
- `QUERY(path, payload, options?)`: For QUERY requests (custom method)
|
|
219
206
|
|
|
220
207
|
## Type Safety
|
|
221
208
|
|
|
222
|
-
The library provides end-to-end type safety:
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
// TypeScript knows the exact shape of requests and responses
|
|
226
|
-
const result = await client.POST("/user", {
|
|
227
|
-
name: "John",
|
|
228
|
-
email: "invalid-email", // Zod will catch this at runtime
|
|
229
|
-
})
|
|
209
|
+
The library provides end-to-end type safety with both approaches:
|
|
230
210
|
|
|
231
|
-
|
|
232
|
-
|
|
211
|
+
- **Zod schemas**: Types are inferred from schemas + runtime validation is available
|
|
212
|
+
- **Plain TypeScript types**: Types are enforced at compile time with zero runtime overhead
|
|
233
213
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
// With Zod — types are inferred, runtime validation available
|
|
216
|
+
const client = createClient("http://localhost:3000/api", { routes })
|
|
217
|
+
const room = await client.GET("/room/:id")
|
|
218
|
+
room.name // string — inferred from z.object({ name: z.string() })
|
|
238
219
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
220
|
+
// With plain TS types — same type safety, no runtime cost
|
|
221
|
+
registerExpressRoutes<ApiRoutes>(router, {}, {
|
|
222
|
+
POST: {
|
|
223
|
+
"/room": ({ payload }) => payload.name.length, // payload typed as { name: string }
|
|
224
|
+
},
|
|
243
225
|
})
|
|
244
226
|
```
|
|
245
227
|
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import z from 'zod';
|
|
|
2
2
|
import { Router, Request } from 'express';
|
|
3
3
|
|
|
4
4
|
type RouterConfig = {
|
|
5
|
-
GET: Record<string, Omit<RouteConfig, "
|
|
5
|
+
GET: Record<string, Omit<RouteConfig, "payload">>;
|
|
6
6
|
QUERY: Record<string, RouteConfig>;
|
|
7
7
|
POST: Record<string, RouteConfig>;
|
|
8
8
|
PUT: Record<string, RouteConfig>;
|
|
@@ -10,12 +10,12 @@ type RouterConfig = {
|
|
|
10
10
|
DELETE: Record<string, RouteConfig>;
|
|
11
11
|
};
|
|
12
12
|
type RouteConfig = {
|
|
13
|
-
|
|
14
|
-
queryParams?: z.ZodType;
|
|
15
|
-
response: z.ZodType;
|
|
13
|
+
payload: z.ZodType | unknown;
|
|
14
|
+
queryParams?: z.ZodType | unknown;
|
|
15
|
+
response: z.ZodType | unknown;
|
|
16
16
|
};
|
|
17
|
-
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "
|
|
18
|
-
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> :
|
|
17
|
+
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "payload">> = {
|
|
18
|
+
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K];
|
|
19
19
|
};
|
|
20
20
|
declare const defineRoutes: <T extends Partial<RouterConfig>>(routes: T) => T;
|
|
21
21
|
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
@@ -32,17 +32,17 @@ type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
|
|
|
32
32
|
params?: K extends string ? ExtractRouteParams<K> : unknown;
|
|
33
33
|
};
|
|
34
34
|
type RouterClient<T extends Partial<RouterConfig>> = {
|
|
35
|
-
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? M extends "GET" ? <K extends keyof T[M]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "
|
|
35
|
+
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? M extends "GET" ? <K extends keyof T[M]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "payload">, K>) => Promise<InferRouteConfig<T[M][K]>["response"]> : <K extends keyof T[M]>(path: K, payload: InferRouteConfig<T[M][K]>["payload"], options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "payload">, K>) => Promise<InferRouteConfig<T[M][K]>["response"]> : never;
|
|
36
36
|
};
|
|
37
37
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
38
|
-
type CreateClientOptions = {
|
|
39
|
-
|
|
38
|
+
type CreateClientOptions<T extends Partial<RouterConfig>> = {
|
|
39
|
+
routes?: T;
|
|
40
40
|
getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
41
41
|
fetch?: FetchFunction;
|
|
42
42
|
validate?: boolean;
|
|
43
43
|
debug?: boolean;
|
|
44
44
|
};
|
|
45
|
-
declare const
|
|
45
|
+
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateClientOptions<T>) => RouterClient<T>;
|
|
46
46
|
|
|
47
47
|
type MaybePromise<T> = Promise<T> | T;
|
|
48
48
|
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
@@ -50,13 +50,14 @@ type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
|
50
50
|
};
|
|
51
51
|
type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {
|
|
52
52
|
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? {
|
|
53
|
-
[K in keyof T[M]]: T[M][K] extends RouteConfig | Omit<RouteConfig, "
|
|
53
|
+
[K in keyof T[M]]: T[M][K] extends RouteConfig | Omit<RouteConfig, "payload"> ? (data: M extends "GET" ? HandlerData<Omit<InferRouteConfig<T[M][K]>, "payload">, K> : HandlerData<InferRouteConfig<T[M][K]>, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T[M][K]>["response"]> : never;
|
|
54
54
|
} : never;
|
|
55
55
|
};
|
|
56
|
-
declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>(router: Router,
|
|
56
|
+
declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>(router: Router, config: {
|
|
57
|
+
routes?: T;
|
|
57
58
|
ctx?: (req: Request) => TContext;
|
|
58
59
|
schemaFile?: string;
|
|
59
60
|
validation?: boolean;
|
|
60
61
|
}, handlers: RouteHandlers<T, TContext>) => Router;
|
|
61
62
|
|
|
62
|
-
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig,
|
|
63
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createHttpClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import z from 'zod';
|
|
|
2
2
|
import { Router, Request } from 'express';
|
|
3
3
|
|
|
4
4
|
type RouterConfig = {
|
|
5
|
-
GET: Record<string, Omit<RouteConfig, "
|
|
5
|
+
GET: Record<string, Omit<RouteConfig, "payload">>;
|
|
6
6
|
QUERY: Record<string, RouteConfig>;
|
|
7
7
|
POST: Record<string, RouteConfig>;
|
|
8
8
|
PUT: Record<string, RouteConfig>;
|
|
@@ -10,12 +10,12 @@ type RouterConfig = {
|
|
|
10
10
|
DELETE: Record<string, RouteConfig>;
|
|
11
11
|
};
|
|
12
12
|
type RouteConfig = {
|
|
13
|
-
|
|
14
|
-
queryParams?: z.ZodType;
|
|
15
|
-
response: z.ZodType;
|
|
13
|
+
payload: z.ZodType | unknown;
|
|
14
|
+
queryParams?: z.ZodType | unknown;
|
|
15
|
+
response: z.ZodType | unknown;
|
|
16
16
|
};
|
|
17
|
-
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "
|
|
18
|
-
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> :
|
|
17
|
+
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "payload">> = {
|
|
18
|
+
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K];
|
|
19
19
|
};
|
|
20
20
|
declare const defineRoutes: <T extends Partial<RouterConfig>>(routes: T) => T;
|
|
21
21
|
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
@@ -32,17 +32,17 @@ type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
|
|
|
32
32
|
params?: K extends string ? ExtractRouteParams<K> : unknown;
|
|
33
33
|
};
|
|
34
34
|
type RouterClient<T extends Partial<RouterConfig>> = {
|
|
35
|
-
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? M extends "GET" ? <K extends keyof T[M]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "
|
|
35
|
+
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? M extends "GET" ? <K extends keyof T[M]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "payload">, K>) => Promise<InferRouteConfig<T[M][K]>["response"]> : <K extends keyof T[M]>(path: K, payload: InferRouteConfig<T[M][K]>["payload"], options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, "payload">, K>) => Promise<InferRouteConfig<T[M][K]>["response"]> : never;
|
|
36
36
|
};
|
|
37
37
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
38
|
-
type CreateClientOptions = {
|
|
39
|
-
|
|
38
|
+
type CreateClientOptions<T extends Partial<RouterConfig>> = {
|
|
39
|
+
routes?: T;
|
|
40
40
|
getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
41
41
|
fetch?: FetchFunction;
|
|
42
42
|
validate?: boolean;
|
|
43
43
|
debug?: boolean;
|
|
44
44
|
};
|
|
45
|
-
declare const
|
|
45
|
+
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateClientOptions<T>) => RouterClient<T>;
|
|
46
46
|
|
|
47
47
|
type MaybePromise<T> = Promise<T> | T;
|
|
48
48
|
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
@@ -50,13 +50,14 @@ type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
|
50
50
|
};
|
|
51
51
|
type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {
|
|
52
52
|
[M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any> ? {
|
|
53
|
-
[K in keyof T[M]]: T[M][K] extends RouteConfig | Omit<RouteConfig, "
|
|
53
|
+
[K in keyof T[M]]: T[M][K] extends RouteConfig | Omit<RouteConfig, "payload"> ? (data: M extends "GET" ? HandlerData<Omit<InferRouteConfig<T[M][K]>, "payload">, K> : HandlerData<InferRouteConfig<T[M][K]>, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T[M][K]>["response"]> : never;
|
|
54
54
|
} : never;
|
|
55
55
|
};
|
|
56
|
-
declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>(router: Router,
|
|
56
|
+
declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>(router: Router, config: {
|
|
57
|
+
routes?: T;
|
|
57
58
|
ctx?: (req: Request) => TContext;
|
|
58
59
|
schemaFile?: string;
|
|
59
60
|
validation?: boolean;
|
|
60
61
|
}, handlers: RouteHandlers<T, TContext>) => Router;
|
|
61
62
|
|
|
62
|
-
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig,
|
|
63
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createHttpClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/client.ts
|
|
3
|
+
// src/http.client.ts
|
|
4
4
|
var replacePathParams = (path, params) => {
|
|
5
5
|
const paramNames = /* @__PURE__ */ new Set();
|
|
6
6
|
const paramPattern = /:([^/]+)/g;
|
|
@@ -19,23 +19,23 @@ var replacePathParams = (path, params) => {
|
|
|
19
19
|
return String(params[paramName]);
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
|
-
var
|
|
22
|
+
var createHttpClient = (baseUrl, options) => {
|
|
23
23
|
const {
|
|
24
|
-
|
|
24
|
+
routes,
|
|
25
25
|
getHeaders = () => Promise.resolve({}),
|
|
26
26
|
fetch: customFetch = fetch,
|
|
27
27
|
validate = false
|
|
28
|
-
} = options;
|
|
28
|
+
} = options ?? {};
|
|
29
29
|
const buildUrl = (path, options2) => {
|
|
30
30
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
31
31
|
const finalPath = path.includes(":") ? replacePathParams(path, options2?.params ?? {}) : path;
|
|
32
32
|
return `${baseUrl}${finalPath}${queryString}`;
|
|
33
33
|
};
|
|
34
|
-
const handleValidation = (method, path,
|
|
34
|
+
const handleValidation = (method, path, payload, options2) => {
|
|
35
35
|
if (!validate) return;
|
|
36
|
-
const routeConfig = routes[method]?.[path];
|
|
37
|
-
if (
|
|
38
|
-
routeConfig.
|
|
36
|
+
const routeConfig = routes?.[method]?.[path];
|
|
37
|
+
if (payload && routeConfig?.payload) {
|
|
38
|
+
routeConfig.payload.parse(payload);
|
|
39
39
|
}
|
|
40
40
|
if (options2?.queryParams && routeConfig?.queryParams) {
|
|
41
41
|
routeConfig.queryParams.parse(options2.queryParams);
|
|
@@ -49,7 +49,7 @@ var createClient = (routes, options) => {
|
|
|
49
49
|
}
|
|
50
50
|
throw new Error(error.message);
|
|
51
51
|
}
|
|
52
|
-
const routeConfig = routes[method]?.[path];
|
|
52
|
+
const routeConfig = routes?.[method]?.[path];
|
|
53
53
|
if (routeConfig?.response?.type === "void") {
|
|
54
54
|
await response.text();
|
|
55
55
|
return;
|
|
@@ -57,8 +57,8 @@ var createClient = (routes, options) => {
|
|
|
57
57
|
const json = await response.json();
|
|
58
58
|
return validate && routeConfig?.response ? routeConfig.response.parse(json) : json;
|
|
59
59
|
};
|
|
60
|
-
const makeRequest = async (method, path,
|
|
61
|
-
handleValidation(method, path,
|
|
60
|
+
const makeRequest = async (method, path, payload, options2) => {
|
|
61
|
+
handleValidation(method, path, payload, options2);
|
|
62
62
|
const url = buildUrl(path, options2);
|
|
63
63
|
const fetchOptions = {
|
|
64
64
|
method,
|
|
@@ -67,30 +67,25 @@ var createClient = (routes, options) => {
|
|
|
67
67
|
...await getHeaders()
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
-
if (
|
|
71
|
-
fetchOptions.body = JSON.stringify(
|
|
70
|
+
if (payload !== void 0) {
|
|
71
|
+
fetchOptions.body = JSON.stringify(payload);
|
|
72
72
|
}
|
|
73
73
|
const response = await customFetch(url, fetchOptions);
|
|
74
74
|
return handleResponse(method, path, response, options2);
|
|
75
75
|
};
|
|
76
|
-
const client = {};
|
|
77
76
|
const methodHandlers = {
|
|
78
77
|
GET: async (path, options2) => makeRequest("GET", path, void 0, options2),
|
|
79
|
-
QUERY: async (path,
|
|
80
|
-
POST: async (path,
|
|
81
|
-
PUT: async (path,
|
|
82
|
-
PATCH: async (path,
|
|
83
|
-
DELETE: async (path,
|
|
78
|
+
QUERY: async (path, payload, options2) => makeRequest("QUERY", path, payload, options2),
|
|
79
|
+
POST: async (path, payload, options2) => makeRequest("POST", path, payload, options2),
|
|
80
|
+
PUT: async (path, payload, options2) => makeRequest("PUT", path, payload, options2),
|
|
81
|
+
PATCH: async (path, payload, options2) => makeRequest("PATCH", path, payload, options2),
|
|
82
|
+
DELETE: async (path, payload, options2) => makeRequest("DELETE", path, payload, options2)
|
|
84
83
|
};
|
|
85
|
-
|
|
86
|
-
if (method in methodHandlers) {
|
|
87
|
-
client[method] = methodHandlers[method];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
84
|
+
const client = methodHandlers;
|
|
90
85
|
return client;
|
|
91
86
|
};
|
|
92
87
|
|
|
93
|
-
// src/server.ts
|
|
88
|
+
// src/http.server.ts
|
|
94
89
|
var createRouteHandler = (method, routes, getCtx, handlers, route, validation) => {
|
|
95
90
|
return async (req, res, next) => {
|
|
96
91
|
try {
|
|
@@ -98,25 +93,39 @@ var createRouteHandler = (method, routes, getCtx, handlers, route, validation) =
|
|
|
98
93
|
res.status(405).send("Method Not Allowed");
|
|
99
94
|
return;
|
|
100
95
|
}
|
|
96
|
+
const validationCheck = {
|
|
97
|
+
payload: typeof validation === "boolean" ? validation : validation.payload ?? false,
|
|
98
|
+
queryParams: typeof validation === "boolean" ? validation : validation.queryParams ?? false,
|
|
99
|
+
response: typeof validation === "boolean" ? validation : validation.response ?? false
|
|
100
|
+
};
|
|
101
101
|
const ctx = getCtx(req) ?? {};
|
|
102
|
-
const routeConfig = routes[method][route];
|
|
102
|
+
const routeConfig = (routes?.[method])[route];
|
|
103
103
|
const data = {
|
|
104
104
|
params: req.params,
|
|
105
|
-
...routeConfig?.
|
|
106
|
-
|
|
105
|
+
...routeConfig?.payload && validationCheck.payload && {
|
|
106
|
+
payload: routeConfig.payload.parse(req.body)
|
|
107
|
+
},
|
|
108
|
+
...routeConfig?.queryParams && validationCheck.queryParams && {
|
|
107
109
|
queryParams: routeConfig.queryParams.parse(req.query)
|
|
108
110
|
}
|
|
109
111
|
};
|
|
110
112
|
const result = await handlers[method][route]?.(data, ctx);
|
|
111
|
-
res.json(
|
|
113
|
+
res.json(
|
|
114
|
+
routeConfig?.response && validationCheck.response ? routeConfig?.response.parse(result) : result
|
|
115
|
+
);
|
|
112
116
|
} catch (err) {
|
|
113
117
|
console.warn(method, route, err?.message);
|
|
114
118
|
next(err);
|
|
115
119
|
}
|
|
116
120
|
};
|
|
117
121
|
};
|
|
118
|
-
var registerExpressRoutes = (router,
|
|
119
|
-
const {
|
|
122
|
+
var registerExpressRoutes = (router, config, handlers) => {
|
|
123
|
+
const {
|
|
124
|
+
schemaFile,
|
|
125
|
+
validation = true,
|
|
126
|
+
ctx = () => null,
|
|
127
|
+
routes
|
|
128
|
+
} = config;
|
|
120
129
|
const expressMethodMap = {
|
|
121
130
|
GET: "get",
|
|
122
131
|
POST: "post",
|
|
@@ -127,7 +136,7 @@ var registerExpressRoutes = (router, routes, config, handlers) => {
|
|
|
127
136
|
};
|
|
128
137
|
for (const [method, routerMethod] of Object.entries(expressMethodMap)) {
|
|
129
138
|
const methodKey = method;
|
|
130
|
-
const methodRoutes =
|
|
139
|
+
const methodRoutes = handlers[methodKey];
|
|
131
140
|
if (!methodRoutes) continue;
|
|
132
141
|
router = Object.keys(methodRoutes).reduce(
|
|
133
142
|
(r, route) => r[routerMethod](
|
|
@@ -156,7 +165,7 @@ var registerExpressRoutes = (router, routes, config, handlers) => {
|
|
|
156
165
|
// src/types.ts
|
|
157
166
|
var defineRoutes = (routes) => routes;
|
|
158
167
|
|
|
159
|
-
exports.
|
|
168
|
+
exports.createHttpClient = createHttpClient;
|
|
160
169
|
exports.defineRoutes = defineRoutes;
|
|
161
170
|
exports.registerExpressRoutes = registerExpressRoutes;
|
|
162
171
|
//# 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"],"mappings":"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","file":"index.js","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\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\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<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 buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\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 routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err: any) {\n console.warn(method, route, err?.message)\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\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 Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/http.client.ts","../src/http.server.ts","../src/types.ts"],"names":["options"],"mappings":"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","file":"index.js","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<\n Omit<InferRouteConfig<T[M][K]>, \"payload\">,\n K\n >,\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n payload: InferRouteConfig<T[M][K]>[\"payload\"],\n options?: ClientOptions<\n Omit<InferRouteConfig<T[M][K]>, \"payload\">,\n K\n >,\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions<T extends Partial<RouterConfig>> = {\n routes?: T\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>,\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`,\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createHttpClient = <T extends Partial<RouterConfig>>(\n baseUrl: string,\n options?: CreateClientOptions<T>,\n): RouterClient<T> => {\n const {\n routes,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options ?? {}\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n payload?: any,\n options?: any,\n ) => {\n if (!validate) return\n\n const routeConfig = (routes?.[method] as any)?.[path]\n if (payload && routeConfig?.payload) {\n routeConfig.payload.parse(payload)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any,\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 routeConfig = (routes?.[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n payload?: any,\n options?: any,\n ) => {\n handleValidation(method, path, payload, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (payload !== undefined) {\n fetchOptions.body = JSON.stringify(payload)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, payload: any, options?: any) =>\n makeRequest(\"QUERY\", path, payload, options),\n POST: async (path: any, payload: any, options?: any) =>\n makeRequest(\"POST\", path, payload, options),\n PUT: async (path: any, payload: any, options?: any) =>\n makeRequest(\"PUT\", path, payload, options),\n PATCH: async (path: any, payload: any, options?: any) =>\n makeRequest(\"PATCH\", path, payload, options),\n DELETE: async (path: any, payload: any, options?: any) =>\n makeRequest(\"DELETE\", path, payload, options),\n }\n\n const client = methodHandlers as RouterClient<T>\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"payload\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"payload\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext,\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>,\n>(\n method: M,\n routes: T | undefined,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation:\n | boolean\n | {\n payload?: boolean\n queryParams?: boolean\n response?: boolean\n },\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const validationCheck = {\n payload:\n typeof validation === \"boolean\"\n ? validation\n : (validation.payload ?? false),\n\n queryParams:\n typeof validation === \"boolean\"\n ? validation\n : (validation.queryParams ?? false),\n\n response:\n typeof validation === \"boolean\"\n ? validation\n : (validation.response ?? false),\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes?.[method] as any)[route]\n\n const data = {\n params: req.params,\n\n ...(routeConfig?.payload &&\n validationCheck.payload && {\n payload: routeConfig.payload.parse(req.body),\n }),\n\n ...(routeConfig?.queryParams &&\n validationCheck.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(\n routeConfig?.response && validationCheck.response\n ? routeConfig?.response.parse(result)\n : result,\n )\n } catch (err: any) {\n console.warn(method, route, err?.message)\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext,\n>(\n router: Router,\n config: {\n routes?: T\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>,\n) => {\n const {\n schemaFile,\n validation = true,\n ctx = () => null as TContext,\n routes,\n } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = handlers[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation,\n ),\n ),\n router,\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile),\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"payload\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n payload: z.ZodType | unknown\n queryParams?: z.ZodType | unknown\n response: z.ZodType | unknown\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"payload\">,\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K]\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(routes: T): T =>\n routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/client.ts
|
|
1
|
+
// src/http.client.ts
|
|
2
2
|
var replacePathParams = (path, params) => {
|
|
3
3
|
const paramNames = /* @__PURE__ */ new Set();
|
|
4
4
|
const paramPattern = /:([^/]+)/g;
|
|
@@ -17,23 +17,23 @@ var replacePathParams = (path, params) => {
|
|
|
17
17
|
return String(params[paramName]);
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
-
var
|
|
20
|
+
var createHttpClient = (baseUrl, options) => {
|
|
21
21
|
const {
|
|
22
|
-
|
|
22
|
+
routes,
|
|
23
23
|
getHeaders = () => Promise.resolve({}),
|
|
24
24
|
fetch: customFetch = fetch,
|
|
25
25
|
validate = false
|
|
26
|
-
} = options;
|
|
26
|
+
} = options ?? {};
|
|
27
27
|
const buildUrl = (path, options2) => {
|
|
28
28
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
29
29
|
const finalPath = path.includes(":") ? replacePathParams(path, options2?.params ?? {}) : path;
|
|
30
30
|
return `${baseUrl}${finalPath}${queryString}`;
|
|
31
31
|
};
|
|
32
|
-
const handleValidation = (method, path,
|
|
32
|
+
const handleValidation = (method, path, payload, options2) => {
|
|
33
33
|
if (!validate) return;
|
|
34
|
-
const routeConfig = routes[method]?.[path];
|
|
35
|
-
if (
|
|
36
|
-
routeConfig.
|
|
34
|
+
const routeConfig = routes?.[method]?.[path];
|
|
35
|
+
if (payload && routeConfig?.payload) {
|
|
36
|
+
routeConfig.payload.parse(payload);
|
|
37
37
|
}
|
|
38
38
|
if (options2?.queryParams && routeConfig?.queryParams) {
|
|
39
39
|
routeConfig.queryParams.parse(options2.queryParams);
|
|
@@ -47,7 +47,7 @@ var createClient = (routes, options) => {
|
|
|
47
47
|
}
|
|
48
48
|
throw new Error(error.message);
|
|
49
49
|
}
|
|
50
|
-
const routeConfig = routes[method]?.[path];
|
|
50
|
+
const routeConfig = routes?.[method]?.[path];
|
|
51
51
|
if (routeConfig?.response?.type === "void") {
|
|
52
52
|
await response.text();
|
|
53
53
|
return;
|
|
@@ -55,8 +55,8 @@ var createClient = (routes, options) => {
|
|
|
55
55
|
const json = await response.json();
|
|
56
56
|
return validate && routeConfig?.response ? routeConfig.response.parse(json) : json;
|
|
57
57
|
};
|
|
58
|
-
const makeRequest = async (method, path,
|
|
59
|
-
handleValidation(method, path,
|
|
58
|
+
const makeRequest = async (method, path, payload, options2) => {
|
|
59
|
+
handleValidation(method, path, payload, options2);
|
|
60
60
|
const url = buildUrl(path, options2);
|
|
61
61
|
const fetchOptions = {
|
|
62
62
|
method,
|
|
@@ -65,30 +65,25 @@ var createClient = (routes, options) => {
|
|
|
65
65
|
...await getHeaders()
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
|
-
if (
|
|
69
|
-
fetchOptions.body = JSON.stringify(
|
|
68
|
+
if (payload !== void 0) {
|
|
69
|
+
fetchOptions.body = JSON.stringify(payload);
|
|
70
70
|
}
|
|
71
71
|
const response = await customFetch(url, fetchOptions);
|
|
72
72
|
return handleResponse(method, path, response, options2);
|
|
73
73
|
};
|
|
74
|
-
const client = {};
|
|
75
74
|
const methodHandlers = {
|
|
76
75
|
GET: async (path, options2) => makeRequest("GET", path, void 0, options2),
|
|
77
|
-
QUERY: async (path,
|
|
78
|
-
POST: async (path,
|
|
79
|
-
PUT: async (path,
|
|
80
|
-
PATCH: async (path,
|
|
81
|
-
DELETE: async (path,
|
|
76
|
+
QUERY: async (path, payload, options2) => makeRequest("QUERY", path, payload, options2),
|
|
77
|
+
POST: async (path, payload, options2) => makeRequest("POST", path, payload, options2),
|
|
78
|
+
PUT: async (path, payload, options2) => makeRequest("PUT", path, payload, options2),
|
|
79
|
+
PATCH: async (path, payload, options2) => makeRequest("PATCH", path, payload, options2),
|
|
80
|
+
DELETE: async (path, payload, options2) => makeRequest("DELETE", path, payload, options2)
|
|
82
81
|
};
|
|
83
|
-
|
|
84
|
-
if (method in methodHandlers) {
|
|
85
|
-
client[method] = methodHandlers[method];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
82
|
+
const client = methodHandlers;
|
|
88
83
|
return client;
|
|
89
84
|
};
|
|
90
85
|
|
|
91
|
-
// src/server.ts
|
|
86
|
+
// src/http.server.ts
|
|
92
87
|
var createRouteHandler = (method, routes, getCtx, handlers, route, validation) => {
|
|
93
88
|
return async (req, res, next) => {
|
|
94
89
|
try {
|
|
@@ -96,25 +91,39 @@ var createRouteHandler = (method, routes, getCtx, handlers, route, validation) =
|
|
|
96
91
|
res.status(405).send("Method Not Allowed");
|
|
97
92
|
return;
|
|
98
93
|
}
|
|
94
|
+
const validationCheck = {
|
|
95
|
+
payload: typeof validation === "boolean" ? validation : validation.payload ?? false,
|
|
96
|
+
queryParams: typeof validation === "boolean" ? validation : validation.queryParams ?? false,
|
|
97
|
+
response: typeof validation === "boolean" ? validation : validation.response ?? false
|
|
98
|
+
};
|
|
99
99
|
const ctx = getCtx(req) ?? {};
|
|
100
|
-
const routeConfig = routes[method][route];
|
|
100
|
+
const routeConfig = (routes?.[method])[route];
|
|
101
101
|
const data = {
|
|
102
102
|
params: req.params,
|
|
103
|
-
...routeConfig?.
|
|
104
|
-
|
|
103
|
+
...routeConfig?.payload && validationCheck.payload && {
|
|
104
|
+
payload: routeConfig.payload.parse(req.body)
|
|
105
|
+
},
|
|
106
|
+
...routeConfig?.queryParams && validationCheck.queryParams && {
|
|
105
107
|
queryParams: routeConfig.queryParams.parse(req.query)
|
|
106
108
|
}
|
|
107
109
|
};
|
|
108
110
|
const result = await handlers[method][route]?.(data, ctx);
|
|
109
|
-
res.json(
|
|
111
|
+
res.json(
|
|
112
|
+
routeConfig?.response && validationCheck.response ? routeConfig?.response.parse(result) : result
|
|
113
|
+
);
|
|
110
114
|
} catch (err) {
|
|
111
115
|
console.warn(method, route, err?.message);
|
|
112
116
|
next(err);
|
|
113
117
|
}
|
|
114
118
|
};
|
|
115
119
|
};
|
|
116
|
-
var registerExpressRoutes = (router,
|
|
117
|
-
const {
|
|
120
|
+
var registerExpressRoutes = (router, config, handlers) => {
|
|
121
|
+
const {
|
|
122
|
+
schemaFile,
|
|
123
|
+
validation = true,
|
|
124
|
+
ctx = () => null,
|
|
125
|
+
routes
|
|
126
|
+
} = config;
|
|
118
127
|
const expressMethodMap = {
|
|
119
128
|
GET: "get",
|
|
120
129
|
POST: "post",
|
|
@@ -125,7 +134,7 @@ var registerExpressRoutes = (router, routes, config, handlers) => {
|
|
|
125
134
|
};
|
|
126
135
|
for (const [method, routerMethod] of Object.entries(expressMethodMap)) {
|
|
127
136
|
const methodKey = method;
|
|
128
|
-
const methodRoutes =
|
|
137
|
+
const methodRoutes = handlers[methodKey];
|
|
129
138
|
if (!methodRoutes) continue;
|
|
130
139
|
router = Object.keys(methodRoutes).reduce(
|
|
131
140
|
(r, route) => r[routerMethod](
|
|
@@ -154,6 +163,6 @@ var registerExpressRoutes = (router, routes, config, handlers) => {
|
|
|
154
163
|
// src/types.ts
|
|
155
164
|
var defineRoutes = (routes) => routes;
|
|
156
165
|
|
|
157
|
-
export {
|
|
166
|
+
export { createHttpClient, defineRoutes, registerExpressRoutes };
|
|
158
167
|
//# sourceMappingURL=index.mjs.map
|
|
159
168
|
//# 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":";AA2CO,IAAM,iBAAA,GAAoB,CAC/B,IAAA,EACA,MAAA,KACW;AACX,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAY;AACnC,EAAA,MAAM,YAAA,GAAe,WAAA;AACrB,EAAA,IAAI,KAAA;AAGJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,YAAA,CAAa,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACjD,IAAA,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACzB;AAGA,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,IAAI,EAAE,aAAa,MAAA,CAAA,EAAS;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6BAAA,EAAgC,SAAS,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA;AAAA,OAC9D;AAAA,IACF;AAAA,EACF;AAGA,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAC,GAAG,SAAA,KAAc;AACjD,IAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,EACjC,CAAC,CAAA;AACH,CAAA;AAEO,IAAM,YAAA,GAAe,CAC1B,MAAA,EACA,OAAA,KACoB;AACpB,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,UAAA,GAAa,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AAAA,IACrC,OAAO,WAAA,GAAc,KAAA;AAAA,IACrB,QAAA,GAAW;AAAA,GACb,GAAI,OAAA;AAEJ,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,EAAcA,QAAAA,KAA0B;AACxD,IAAA,MAAM,WAAA,GAAcA,QAAAA,EAAS,WAAA,GACzB,GAAA,GAAM,IAAI,gBAAgBA,QAAAA,CAAQ,WAAW,CAAA,CAAE,QAAA,EAAS,GACxD,EAAA;AAEJ,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,GAC/B,iBAAA,CAAkB,IAAA,EAAMA,QAAAA,EAAS,MAAA,IAAU,EAAE,CAAA,GAC7C,IAAA;AAEJ,IAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAG,SAAS,GAAG,WAAW,CAAA,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CACvB,MAAA,EACA,IAAA,EACA,MACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,GAAY,IAAI,CAAA;AAClD,IAAA,IAAI,IAAA,IAAQ,aAAa,IAAA,EAAM;AAC7B,MAAA,WAAA,CAAY,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,IAC7B;AACA,IAAA,IAAIA,QAAAA,EAAS,WAAA,IAAe,WAAA,EAAa,WAAA,EAAa;AACpD,MAAA,WAAA,CAAY,WAAA,CAAY,KAAA,CAAMA,QAAAA,CAAQ,WAAW,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,OACrB,MAAA,EACA,IAAA,EACA,UACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AAEvC,MAAA,IAAIA,UAAS,KAAA,EAAO;AAClB,QAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,MACrB;AAEA,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,GAAY,IAAI,CAAA;AAClD,IAAA,IAAI,WAAA,EAAa,QAAA,EAAU,IAAA,KAAS,MAAA,EAAQ;AAC1C,MAAA,MAAM,SAAS,IAAA,EAAK;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,YAAY,WAAA,EAAa,QAAA,GAC5B,YAAY,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GAC/B,IAAA;AAAA,EACN,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,OAClB,MAAA,EACA,IAAA,EACA,MACAA,QAAAA,KACG;AACH,IAAA,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAE5C,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,EAAMA,QAAO,CAAA;AAClC,IAAA,MAAM,YAAA,GAA4B;AAAA,MAChC,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,MAAM,UAAA;AAAW;AACvB,KACF;AAEA,IAAA,IAAI,SAAS,MAAA,EAAW;AACtB,MAAA,YAAA,CAAa,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,IACzC;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,GAAA,EAAK,YAAY,CAAA;AAEpD,IAAA,OAAO,cAAA,CAAe,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAUA,QAAO,CAAA;AAAA,EACvD,CAAA;AAEA,EAAA,MAAM,SAAS,EAAC;AAEhB,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,GAAA,EAAK,OAAO,IAAA,EAAWA,QAAAA,KACrB,YAAY,KAAA,EAAO,IAAA,EAAM,QAAWA,QAAO,CAAA;AAAA,IAC7C,KAAA,EAAO,OAAO,IAAA,EAAW,IAAA,EAAWA,aAClC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IAC1C,IAAA,EAAM,OAAO,IAAA,EAAW,IAAA,EAAWA,aACjC,WAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IACzC,GAAA,EAAK,OAAO,IAAA,EAAW,IAAA,EAAWA,aAChC,WAAA,CAAY,KAAA,EAAO,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IACxC,KAAA,EAAO,OAAO,IAAA,EAAW,IAAA,EAAWA,aAClC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IAC1C,MAAA,EAAQ,OAAO,IAAA,EAAW,IAAA,EAAWA,aACnC,WAAA,CAAY,QAAA,EAAU,IAAA,EAAM,IAAA,EAAMA,QAAO;AAAA,GAC7C;AAEA,EAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAEpC;AACD,IAAA,IAAI,UAAU,cAAA,EAAgB;AAC3B,MAAC,MAAA,CAAe,MAAM,CAAA,GAAI,cAAA,CAAe,MAAM,CAAA;AAAA,IAClD;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC/JA,IAAM,qBAAqB,CAKzB,MAAA,EACA,QACA,MAAA,EACA,QAAA,EACA,OACA,UAAA,KACG;AACH,EAAA,OAAO,OAAO,GAAA,EAAc,GAAA,EAAU,IAAA,KAAc;AAClD,IAAA,IAAI;AACF,MAAA,IAAI,MAAA,KAAW,OAAA,IAAW,GAAA,CAAI,MAAA,KAAW,OAAA,EAAS;AAChD,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,oBAAoB,CAAA;AACzC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,GAAA,GAAO,MAAA,CAAO,GAAG,CAAA,IAAK,EAAC;AAC7B,MAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,CAAU,KAAK,CAAA;AAEjD,MAAA,MAAM,IAAA,GAAO;AAAA,QACX,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,GAAI,WAAA,EAAa,IAAA,IAAQ,EAAE,IAAA,EAAM,YAAY,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAE;AAAA,QAClE,GAAI,aAAa,WAAA,IAAe;AAAA,UAC9B,WAAA,EAAa,WAAA,CAAY,WAAA,CAAY,KAAA,CAAM,IAAI,KAAK;AAAA;AACtD,OACF;AAEA,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,MAAM,EAAE,KAAK,CAAA,GAAI,MAAa,GAAG,CAAA;AAE/D,MAAA,GAAA,CAAI,KAAK,UAAA,GAAa,WAAA,EAAa,SAAS,KAAA,CAAM,MAAM,IAAI,MAAM,CAAA;AAAA,IACpE,SAAS,GAAA,EAAU;AACjB,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,GAAA,EAAK,OAAO,CAAA;AACxC,MAAA,IAAA,CAAK,GAAG,CAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF,CAAA;AAEO,IAAM,qBAAA,GAAwB,CAInC,MAAA,EACA,MAAA,EACA,QAKA,QAAA,KACG;AACH,EAAA,MAAM,EAAE,UAAA,EAAY,UAAA,GAAa,MAAM,GAAA,GAAM,MAAM,MAAiB,GAAI,MAAA;AAExE,EAAA,MAAM,gBAAA,GAAmB;AAAA,IACvB,GAAA,EAAK,KAAA;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,GAAA,EAAK,KAAA;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,MAAA,EAAQ,QAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AAEA,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,YAAY,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AACrE,IAAA,MAAM,SAAA,GAAY,MAAA;AAClB,IAAA,MAAM,YAAA,GAAe,OAAO,SAAS,CAAA;AAErC,IAAA,IAAI,CAAC,YAAA,EAAc;AAEnB,IAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,YAAsB,CAAA,CAAE,MAAA;AAAA,MAC3C,CAAC,CAAA,EAAG,KAAA,KACF,CAAA,CAAE,YAAY,CAAA;AAAA,QACZ,KAAA;AAAA,QACA,kBAAA;AAAA,UACE,SAAA;AAAA,UACA,MAAA;AAAA,UACA,GAAA;AAAA,UACA,QAAA;AAAA,UACA,KAAA;AAAA,UACA;AAAA;AACF,OACF;AAAA,MACF;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAAA,MAAI,WAAA;AAAA,MAAa,OAAO,GAAG,GAAA,KACzC,GAAA,CAAI,YAAY,YAAY,CAAA,CAAE,KAAK,UAAU;AAAA,KAC/C;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACxGO,IAAM,YAAA,GAAe,CAC1B,MAAA,KACM","file":"index.mjs","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\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\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<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 buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\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 routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err: any) {\n console.warn(method, route, err?.message)\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\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 Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/http.client.ts","../src/http.server.ts","../src/types.ts"],"names":["options"],"mappings":";AAiDO,IAAM,iBAAA,GAAoB,CAC/B,IAAA,EACA,MAAA,KACW;AACX,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAY;AACnC,EAAA,MAAM,YAAA,GAAe,WAAA;AACrB,EAAA,IAAI,KAAA;AAGJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,YAAA,CAAa,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACjD,IAAA,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACzB;AAGA,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,IAAI,EAAE,aAAa,MAAA,CAAA,EAAS;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6BAAA,EAAgC,SAAS,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA;AAAA,OAC9D;AAAA,IACF;AAAA,EACF;AAGA,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAC,GAAG,SAAA,KAAc;AACjD,IAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,EACjC,CAAC,CAAA;AACH,CAAA;AAEO,IAAM,gBAAA,GAAmB,CAC9B,OAAA,EACA,OAAA,KACoB;AACpB,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,UAAA,GAAa,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AAAA,IACrC,OAAO,WAAA,GAAc,KAAA;AAAA,IACrB,QAAA,GAAW;AAAA,GACb,GAAI,WAAW,EAAC;AAEhB,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,EAAcA,QAAAA,KAA0B;AACxD,IAAA,MAAM,WAAA,GAAcA,QAAAA,EAAS,WAAA,GACzB,GAAA,GAAM,IAAI,gBAAgBA,QAAAA,CAAQ,WAAW,CAAA,CAAE,QAAA,EAAS,GACxD,EAAA;AAEJ,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,GAC/B,iBAAA,CAAkB,IAAA,EAAMA,QAAAA,EAAS,MAAA,IAAU,EAAE,CAAA,GAC7C,IAAA;AAEJ,IAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAG,SAAS,GAAG,WAAW,CAAA,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CACvB,MAAA,EACA,IAAA,EACA,SACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,WAAA,GAAe,MAAA,GAAS,MAAM,CAAA,GAAY,IAAI,CAAA;AACpD,IAAA,IAAI,OAAA,IAAW,aAAa,OAAA,EAAS;AACnC,MAAA,WAAA,CAAY,OAAA,CAAQ,MAAM,OAAO,CAAA;AAAA,IACnC;AACA,IAAA,IAAIA,QAAAA,EAAS,WAAA,IAAe,WAAA,EAAa,WAAA,EAAa;AACpD,MAAA,WAAA,CAAY,WAAA,CAAY,KAAA,CAAMA,QAAAA,CAAQ,WAAW,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,OACrB,MAAA,EACA,IAAA,EACA,UACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AAEvC,MAAA,IAAIA,UAAS,KAAA,EAAO;AAClB,QAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,MACrB;AAEA,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,WAAA,GAAe,MAAA,GAAS,MAAM,CAAA,GAAY,IAAI,CAAA;AACpD,IAAA,IAAI,WAAA,EAAa,QAAA,EAAU,IAAA,KAAS,MAAA,EAAQ;AAC1C,MAAA,MAAM,SAAS,IAAA,EAAK;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,YAAY,WAAA,EAAa,QAAA,GAC5B,YAAY,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GAC/B,IAAA;AAAA,EACN,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,OAClB,MAAA,EACA,IAAA,EACA,SACAA,QAAAA,KACG;AACH,IAAA,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,OAAA,EAASA,QAAO,CAAA;AAE/C,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,EAAMA,QAAO,CAAA;AAClC,IAAA,MAAM,YAAA,GAA4B;AAAA,MAChC,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,MAAM,UAAA;AAAW;AACvB,KACF;AAEA,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,YAAA,CAAa,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,GAAA,EAAK,YAAY,CAAA;AAEpD,IAAA,OAAO,cAAA,CAAe,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAUA,QAAO,CAAA;AAAA,EACvD,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,GAAA,EAAK,OAAO,IAAA,EAAWA,QAAAA,KACrB,YAAY,KAAA,EAAO,IAAA,EAAM,QAAWA,QAAO,CAAA;AAAA,IAC7C,KAAA,EAAO,OAAO,IAAA,EAAW,OAAA,EAAcA,aACrC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,OAAA,EAASA,QAAO,CAAA;AAAA,IAC7C,IAAA,EAAM,OAAO,IAAA,EAAW,OAAA,EAAcA,aACpC,WAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,OAAA,EAASA,QAAO,CAAA;AAAA,IAC5C,GAAA,EAAK,OAAO,IAAA,EAAW,OAAA,EAAcA,aACnC,WAAA,CAAY,KAAA,EAAO,IAAA,EAAM,OAAA,EAASA,QAAO,CAAA;AAAA,IAC3C,KAAA,EAAO,OAAO,IAAA,EAAW,OAAA,EAAcA,aACrC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,OAAA,EAASA,QAAO,CAAA;AAAA,IAC7C,MAAA,EAAQ,OAAO,IAAA,EAAW,OAAA,EAAcA,aACtC,WAAA,CAAY,QAAA,EAAU,IAAA,EAAM,OAAA,EAASA,QAAO;AAAA,GAChD;AAEA,EAAA,MAAM,MAAA,GAAS,cAAA;AAEf,EAAA,OAAO,MAAA;AACT;;;AC7JA,IAAM,qBAAqB,CAKzB,MAAA,EACA,QACA,MAAA,EACA,QAAA,EACA,OACA,UAAA,KAOG;AACH,EAAA,OAAO,OAAO,GAAA,EAAc,GAAA,EAAU,IAAA,KAAc;AAClD,IAAA,IAAI;AACF,MAAA,IAAI,MAAA,KAAW,OAAA,IAAW,GAAA,CAAI,MAAA,KAAW,OAAA,EAAS;AAChD,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,oBAAoB,CAAA;AACzC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,eAAA,GAAkB;AAAA,QACtB,SACE,OAAO,UAAA,KAAe,SAAA,GAClB,UAAA,GACC,WAAW,OAAA,IAAW,KAAA;AAAA,QAE7B,aACE,OAAO,UAAA,KAAe,SAAA,GAClB,UAAA,GACC,WAAW,WAAA,IAAe,KAAA;AAAA,QAEjC,UACE,OAAO,UAAA,KAAe,SAAA,GAClB,UAAA,GACC,WAAW,QAAA,IAAY;AAAA,OAChC;AAEA,MAAA,MAAM,GAAA,GAAO,MAAA,CAAO,GAAG,CAAA,IAAK,EAAC;AAC7B,MAAA,MAAM,WAAA,GAAA,CAAe,MAAA,GAAS,MAAM,CAAA,EAAU,KAAK,CAAA;AAEnD,MAAA,MAAM,IAAA,GAAO;AAAA,QACX,QAAQ,GAAA,CAAI,MAAA;AAAA,QAEZ,GAAI,WAAA,EAAa,OAAA,IACf,eAAA,CAAgB,OAAA,IAAW;AAAA,UACzB,OAAA,EAAS,WAAA,CAAY,OAAA,CAAQ,KAAA,CAAM,IAAI,IAAI;AAAA,SAC7C;AAAA,QAEF,GAAI,WAAA,EAAa,WAAA,IACf,eAAA,CAAgB,WAAA,IAAe;AAAA,UAC7B,WAAA,EAAa,WAAA,CAAY,WAAA,CAAY,KAAA,CAAM,IAAI,KAAK;AAAA;AACtD,OACJ;AAEA,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,MAAM,EAAE,KAAK,CAAA,GAAI,MAAa,GAAG,CAAA;AAE/D,MAAA,GAAA,CAAI,IAAA;AAAA,QACF,WAAA,EAAa,YAAY,eAAA,CAAgB,QAAA,GACrC,aAAa,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,GAClC;AAAA,OACN;AAAA,IACF,SAAS,GAAA,EAAU;AACjB,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,GAAA,EAAK,OAAO,CAAA;AACxC,MAAA,IAAA,CAAK,GAAG,CAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF,CAAA;AAEO,IAAM,qBAAA,GAAwB,CAInC,MAAA,EACA,MAAA,EAMA,QAAA,KACG;AACH,EAAA,MAAM;AAAA,IACJ,UAAA;AAAA,IACA,UAAA,GAAa,IAAA;AAAA,IACb,MAAM,MAAM,IAAA;AAAA,IACZ;AAAA,GACF,GAAI,MAAA;AAEJ,EAAA,MAAM,gBAAA,GAAmB;AAAA,IACvB,GAAA,EAAK,KAAA;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,GAAA,EAAK,KAAA;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,MAAA,EAAQ,QAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AAEA,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,YAAY,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AACrE,IAAA,MAAM,SAAA,GAAY,MAAA;AAClB,IAAA,MAAM,YAAA,GAAe,SAAS,SAAS,CAAA;AAEvC,IAAA,IAAI,CAAC,YAAA,EAAc;AAEnB,IAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,YAAsB,CAAA,CAAE,MAAA;AAAA,MAC3C,CAAC,CAAA,EAAG,KAAA,KACF,CAAA,CAAE,YAAY,CAAA;AAAA,QACZ,KAAA;AAAA,QACA,kBAAA;AAAA,UACE,SAAA;AAAA,UACA,MAAA;AAAA,UACA,GAAA;AAAA,UACA,QAAA;AAAA,UACA,KAAA;AAAA,UACA;AAAA;AACF,OACF;AAAA,MACF;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAAA,MAAI,WAAA;AAAA,MAAa,OAAO,GAAG,GAAA,KACzC,GAAA,CAAI,YAAY,YAAY,CAAA,CAAE,KAAK,UAAU;AAAA,KAC/C;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC9IO,IAAM,YAAA,GAAe,CAAkC,MAAA,KAC5D","file":"index.mjs","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<\n Omit<InferRouteConfig<T[M][K]>, \"payload\">,\n K\n >,\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n payload: InferRouteConfig<T[M][K]>[\"payload\"],\n options?: ClientOptions<\n Omit<InferRouteConfig<T[M][K]>, \"payload\">,\n K\n >,\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions<T extends Partial<RouterConfig>> = {\n routes?: T\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>,\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`,\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createHttpClient = <T extends Partial<RouterConfig>>(\n baseUrl: string,\n options?: CreateClientOptions<T>,\n): RouterClient<T> => {\n const {\n routes,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options ?? {}\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n payload?: any,\n options?: any,\n ) => {\n if (!validate) return\n\n const routeConfig = (routes?.[method] as any)?.[path]\n if (payload && routeConfig?.payload) {\n routeConfig.payload.parse(payload)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any,\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 routeConfig = (routes?.[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n payload?: any,\n options?: any,\n ) => {\n handleValidation(method, path, payload, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (payload !== undefined) {\n fetchOptions.body = JSON.stringify(payload)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, payload: any, options?: any) =>\n makeRequest(\"QUERY\", path, payload, options),\n POST: async (path: any, payload: any, options?: any) =>\n makeRequest(\"POST\", path, payload, options),\n PUT: async (path: any, payload: any, options?: any) =>\n makeRequest(\"PUT\", path, payload, options),\n PATCH: async (path: any, payload: any, options?: any) =>\n makeRequest(\"PATCH\", path, payload, options),\n DELETE: async (path: any, payload: any, options?: any) =>\n makeRequest(\"DELETE\", path, payload, options),\n }\n\n const client = methodHandlers as RouterClient<T>\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"payload\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"payload\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext,\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>,\n>(\n method: M,\n routes: T | undefined,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation:\n | boolean\n | {\n payload?: boolean\n queryParams?: boolean\n response?: boolean\n },\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const validationCheck = {\n payload:\n typeof validation === \"boolean\"\n ? validation\n : (validation.payload ?? false),\n\n queryParams:\n typeof validation === \"boolean\"\n ? validation\n : (validation.queryParams ?? false),\n\n response:\n typeof validation === \"boolean\"\n ? validation\n : (validation.response ?? false),\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes?.[method] as any)[route]\n\n const data = {\n params: req.params,\n\n ...(routeConfig?.payload &&\n validationCheck.payload && {\n payload: routeConfig.payload.parse(req.body),\n }),\n\n ...(routeConfig?.queryParams &&\n validationCheck.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(\n routeConfig?.response && validationCheck.response\n ? routeConfig?.response.parse(result)\n : result,\n )\n } catch (err: any) {\n console.warn(method, route, err?.message)\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext,\n>(\n router: Router,\n config: {\n routes?: T\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>,\n) => {\n const {\n schemaFile,\n validation = true,\n ctx = () => null as TContext,\n routes,\n } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = handlers[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation,\n ),\n ),\n router,\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile),\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"payload\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n payload: z.ZodType | unknown\n queryParams?: z.ZodType | unknown\n response: z.ZodType | unknown\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"payload\">,\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K]\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(routes: T): T =>\n routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
|