@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 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(router, { routes }, {
95
- GET: {
96
- "/room/:id": ({ params }) => ({ name: params.id }),
97
- "/rooms": () => ({ count: 10 }),
98
- },
99
- POST: {
100
- "/room": ({ payload }) => ({ id: "1" }),
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 { createClient } from "@jokio/rpc"
141
+ import { createHttpClient } from "@jokio/rpc"
134
142
 
135
- const client = createClient("http://localhost:3000/api", { routes })
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
- ### `createClient(baseUrl, options)`
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 = createClient("http://localhost:3000/api", { routes })
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>(router, {}, {
222
- POST: {
223
- "/room": ({ payload }) => payload.name.length, // payload typed as { name: string }
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 Partial<RouterConfig>>(routes: T) => T;
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 CreateClientOptions<T extends Partial<RouterConfig>> = {
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?: CreateClientOptions<T>) => RouterClient<T>;
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 Partial<RouterConfig>>(routes: T) => T;
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 CreateClientOptions<T extends Partial<RouterConfig>> = {
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?: CreateClientOptions<T>) => RouterClient<T>;
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":"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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"]}
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"]}
@@ -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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokio/rpc",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Type-safe RPC framework with Zod validation for Express and TypeScript",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",