@jokio/rpc 1.1.0 → 1.2.1
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 +42 -16
- package/dist/index.d.mts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
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
|
+
An implementation of [RESTspec](https://restspec.org/)
|
|
6
|
+
|
|
5
7
|
<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
8
|
|
|
7
9
|
## Use Cases
|
|
@@ -91,15 +93,19 @@ app.use(express.json())
|
|
|
91
93
|
|
|
92
94
|
const router = express.Router()
|
|
93
95
|
|
|
94
|
-
registerExpressRoutes(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
registerExpressRoutes(
|
|
97
|
+
router,
|
|
98
|
+
{ routes },
|
|
99
|
+
{
|
|
100
|
+
GET: {
|
|
101
|
+
"/room/:id": ({ params }) => ({ name: params.id }),
|
|
102
|
+
"/rooms": () => ({ count: 10 }),
|
|
103
|
+
},
|
|
104
|
+
POST: {
|
|
105
|
+
"/room": ({ payload }) => ({ id: "1" }),
|
|
106
|
+
},
|
|
101
107
|
},
|
|
102
|
-
|
|
108
|
+
)
|
|
103
109
|
|
|
104
110
|
app.use("/api", router)
|
|
105
111
|
app.listen(3000)
|
|
@@ -127,18 +133,34 @@ registerExpressRoutes<ApiRoutes, { userId: number }>(
|
|
|
127
133
|
|
|
128
134
|
### 3. Create a Type-Safe Client
|
|
129
135
|
|
|
136
|
+
#### With Zod Routes
|
|
137
|
+
|
|
130
138
|
The client uses the Zod route definitions for both type inference and optional runtime validation.
|
|
131
139
|
|
|
132
140
|
```typescript
|
|
133
|
-
import {
|
|
141
|
+
import { createHttpClient } from "@jokio/rpc"
|
|
134
142
|
|
|
135
|
-
const client =
|
|
143
|
+
const client = createHttpClient("http://localhost:3000/api", { routes })
|
|
136
144
|
|
|
137
145
|
// Fully typed response — .name is inferred from the Zod schema
|
|
138
146
|
const room = await client.GET("/room/:id")
|
|
139
147
|
console.log(room.name)
|
|
140
148
|
```
|
|
141
149
|
|
|
150
|
+
#### With TypeScript Types
|
|
151
|
+
|
|
152
|
+
When using plain TypeScript types, pass the type as a generic parameter. No `routes` object is needed.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { createHttpClient } from "@jokio/rpc"
|
|
156
|
+
|
|
157
|
+
const client = createHttpClient<ApiRoutes>("http://localhost:3000/api")
|
|
158
|
+
|
|
159
|
+
// Fully typed response — .name is inferred from the ApiRoutes type
|
|
160
|
+
const room = await client.GET("/room/:id")
|
|
161
|
+
console.log(room.name)
|
|
162
|
+
```
|
|
163
|
+
|
|
142
164
|
## API Reference
|
|
143
165
|
|
|
144
166
|
### `defineRoutes(routes)`
|
|
@@ -177,7 +199,7 @@ Registers route handlers to an Express router with automatic validation.
|
|
|
177
199
|
|
|
178
200
|
When using plain TypeScript types, pass the type as a generic: `registerExpressRoutes<MyRoutes>(...)`. Zod validation is skipped since there are no schemas.
|
|
179
201
|
|
|
180
|
-
### `
|
|
202
|
+
### `createHttpClient(baseUrl, options)`
|
|
181
203
|
|
|
182
204
|
Creates a type-safe HTTP client.
|
|
183
205
|
|
|
@@ -213,16 +235,20 @@ The library provides end-to-end type safety with both approaches:
|
|
|
213
235
|
|
|
214
236
|
```typescript
|
|
215
237
|
// With Zod — types are inferred, runtime validation available
|
|
216
|
-
const client =
|
|
238
|
+
const client = createHttpClient("http://localhost:3000/api", { routes })
|
|
217
239
|
const room = await client.GET("/room/:id")
|
|
218
240
|
room.name // string — inferred from z.object({ name: z.string() })
|
|
219
241
|
|
|
220
242
|
// With plain TS types — same type safety, no runtime cost
|
|
221
|
-
registerExpressRoutes<ApiRoutes>(
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
registerExpressRoutes<ApiRoutes>(
|
|
244
|
+
router,
|
|
245
|
+
{},
|
|
246
|
+
{
|
|
247
|
+
POST: {
|
|
248
|
+
"/room": ({ payload }) => payload.name.length, // payload typed as { name: string }
|
|
249
|
+
},
|
|
224
250
|
},
|
|
225
|
-
|
|
251
|
+
)
|
|
226
252
|
```
|
|
227
253
|
|
|
228
254
|
## Error Handling
|
package/dist/index.d.mts
CHANGED
|
@@ -14,10 +14,11 @@ type RouteConfig = {
|
|
|
14
14
|
queryParams?: z.ZodType | unknown;
|
|
15
15
|
response: z.ZodType | unknown;
|
|
16
16
|
};
|
|
17
|
+
type Routes = Partial<RouterConfig>;
|
|
17
18
|
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "payload">> = {
|
|
18
19
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K];
|
|
19
20
|
};
|
|
20
|
-
declare const defineRoutes: <T extends
|
|
21
|
+
declare const defineRoutes: <T extends Routes>(routes: T) => T;
|
|
21
22
|
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
22
23
|
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
23
24
|
} : Rest extends `${string}/:${string}` ? {
|
|
@@ -35,14 +36,14 @@ type RouterClient<T extends Partial<RouterConfig>> = {
|
|
|
35
36
|
[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
37
|
};
|
|
37
38
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
38
|
-
type
|
|
39
|
+
type CreateHttpClientOptions<T extends Partial<RouterConfig>> = {
|
|
39
40
|
routes?: T;
|
|
40
41
|
getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
41
42
|
fetch?: FetchFunction;
|
|
42
43
|
validate?: boolean;
|
|
43
44
|
debug?: boolean;
|
|
44
45
|
};
|
|
45
|
-
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?:
|
|
46
|
+
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateHttpClientOptions<T>) => RouterClient<T>;
|
|
46
47
|
|
|
47
48
|
type MaybePromise<T> = Promise<T> | T;
|
|
48
49
|
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
@@ -57,7 +58,11 @@ declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>
|
|
|
57
58
|
routes?: T;
|
|
58
59
|
ctx?: (req: Request) => TContext;
|
|
59
60
|
schemaFile?: string;
|
|
60
|
-
validation?: boolean
|
|
61
|
+
validation?: boolean | {
|
|
62
|
+
payload?: boolean;
|
|
63
|
+
queryParams?: boolean;
|
|
64
|
+
response?: boolean;
|
|
65
|
+
};
|
|
61
66
|
}, handlers: RouteHandlers<T, TContext>) => Router;
|
|
62
67
|
|
|
63
|
-
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createHttpClient, defineRoutes, registerExpressRoutes };
|
|
68
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, type Routes, createHttpClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,11 @@ type RouteConfig = {
|
|
|
14
14
|
queryParams?: z.ZodType | unknown;
|
|
15
15
|
response: z.ZodType | unknown;
|
|
16
16
|
};
|
|
17
|
+
type Routes = Partial<RouterConfig>;
|
|
17
18
|
type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "payload">> = {
|
|
18
19
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : T[K];
|
|
19
20
|
};
|
|
20
|
-
declare const defineRoutes: <T extends
|
|
21
|
+
declare const defineRoutes: <T extends Routes>(routes: T) => T;
|
|
21
22
|
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
22
23
|
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
23
24
|
} : Rest extends `${string}/:${string}` ? {
|
|
@@ -35,14 +36,14 @@ type RouterClient<T extends Partial<RouterConfig>> = {
|
|
|
35
36
|
[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
37
|
};
|
|
37
38
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
38
|
-
type
|
|
39
|
+
type CreateHttpClientOptions<T extends Partial<RouterConfig>> = {
|
|
39
40
|
routes?: T;
|
|
40
41
|
getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
41
42
|
fetch?: FetchFunction;
|
|
42
43
|
validate?: boolean;
|
|
43
44
|
debug?: boolean;
|
|
44
45
|
};
|
|
45
|
-
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?:
|
|
46
|
+
declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateHttpClientOptions<T>) => RouterClient<T>;
|
|
46
47
|
|
|
47
48
|
type MaybePromise<T> = Promise<T> | T;
|
|
48
49
|
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
@@ -57,7 +58,11 @@ declare const registerExpressRoutes: <T extends Partial<RouterConfig>, TContext>
|
|
|
57
58
|
routes?: T;
|
|
58
59
|
ctx?: (req: Request) => TContext;
|
|
59
60
|
schemaFile?: string;
|
|
60
|
-
validation?: boolean
|
|
61
|
+
validation?: boolean | {
|
|
62
|
+
payload?: boolean;
|
|
63
|
+
queryParams?: boolean;
|
|
64
|
+
response?: boolean;
|
|
65
|
+
};
|
|
61
66
|
}, handlers: RouteHandlers<T, TContext>) => Router;
|
|
62
67
|
|
|
63
|
-
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createHttpClient, defineRoutes, registerExpressRoutes };
|
|
68
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, type Routes, createHttpClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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.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"]}
|
|
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 CreateHttpClientOptions<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?: CreateHttpClientOptions<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?:\n | boolean\n | {\n payload?: boolean\n queryParams?: boolean\n response?: boolean\n }\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 Routes = Partial<RouterConfig>\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 Routes>(routes: T): 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"]}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http.client.ts","../src/http.server.ts","../src/types.ts"],"names":["options"],"mappings":"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","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/http.client.ts","../src/http.server.ts","../src/types.ts"],"names":["options"],"mappings":"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","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 CreateHttpClientOptions<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?: CreateHttpClientOptions<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?:\n | boolean\n | {\n payload?: boolean\n queryParams?: boolean\n response?: boolean\n }\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 Routes = Partial<RouterConfig>\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 Routes>(routes: T): 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"]}
|