@jokio/rpc 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -213,69 +213,6 @@ Creates a type-safe HTTP client.
213
213
  - `validate`: Enable client-side request validation (default: false)
214
214
  - `debug`: Enable debug logging (default: false)
215
215
 
216
- **Client Methods:**
217
-
218
- Each HTTP method has a type-safe method on the client:
219
-
220
- - `GET(path, options?)`: For GET requests
221
- - `options.params`: Path parameters
222
- - `options.queryParams`: Query parameters
223
- - `POST(path, payload, options?)`: For POST requests
224
- - `PUT(path, payload, options?)`: For PUT requests
225
- - `PATCH(path, payload, options?)`: For PATCH requests
226
- - `DELETE(path, payload, options?)`: For DELETE requests
227
- - `QUERY(path, payload, options?)`: For QUERY requests (custom method)
228
-
229
- ## Type Safety
230
-
231
- The library provides end-to-end type safety with both approaches:
232
-
233
- - **Zod schemas**: Types are inferred from schemas + runtime validation is available
234
- - **Plain TypeScript types**: Types are enforced at compile time with zero runtime overhead
235
-
236
- ```typescript
237
- // With Zod — types are inferred, runtime validation available
238
- const client = createHttpClient("http://localhost:3000/api", { routes })
239
- const room = await client.GET("/room/:id")
240
- room.name // string — inferred from z.object({ name: z.string() })
241
-
242
- // With plain TS types — same type safety, no runtime cost
243
- registerExpressRoutes<ApiRoutes>(
244
- router,
245
- {},
246
- {
247
- POST: {
248
- "/room": ({ payload }) => payload.name.length, // payload typed as { name: string }
249
- },
250
- },
251
- )
252
- ```
253
-
254
- ## Error Handling
255
-
256
- The library throws errors for:
257
-
258
- - HTTP errors (non-2xx responses)
259
- - Validation errors (invalid request/response data)
260
- - Missing path parameters
261
-
262
- ```typescript
263
- try {
264
- await client.POST("/user", invalidData)
265
- } catch (error) {
266
- // Handle validation or HTTP errors
267
- }
268
-
269
- // Missing path parameters will throw an error
270
- try {
271
- await client.GET("/user/:id", {
272
- params: {}, // Missing 'id' parameter
273
- })
274
- } catch (error) {
275
- // Error: Missing required parameter: "id" for path "/user/:id"
276
- }
277
- ```
278
-
279
216
  ## License
280
217
 
281
218
  MIT
package/dist/index.d.mts CHANGED
@@ -32,6 +32,7 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
32
32
 
33
33
  type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
34
34
  params?: K extends string ? ExtractRouteParams<K> : unknown;
35
+ requestInit?: RequestInit;
35
36
  };
36
37
  type RouterClient<T extends Partial<RouterConfig>> = {
37
38
  [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;
@@ -43,6 +44,7 @@ type CreateHttpClientOptions<T extends Partial<RouterConfig>> = {
43
44
  fetch?: FetchFunction;
44
45
  validate?: boolean;
45
46
  debug?: boolean;
47
+ requestInit?: RequestInit;
46
48
  };
47
49
  declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateHttpClientOptions<T>) => RouterClient<T>;
48
50
 
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
32
32
 
33
33
  type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
34
34
  params?: K extends string ? ExtractRouteParams<K> : unknown;
35
+ requestInit?: RequestInit;
35
36
  };
36
37
  type RouterClient<T extends Partial<RouterConfig>> = {
37
38
  [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;
@@ -43,6 +44,7 @@ type CreateHttpClientOptions<T extends Partial<RouterConfig>> = {
43
44
  fetch?: FetchFunction;
44
45
  validate?: boolean;
45
46
  debug?: boolean;
47
+ requestInit?: RequestInit;
46
48
  };
47
49
  declare const createHttpClient: <T extends Partial<RouterConfig>>(baseUrl: string, options?: CreateHttpClientOptions<T>) => RouterClient<T>;
48
50
 
package/dist/index.js CHANGED
@@ -24,7 +24,8 @@ var createHttpClient = (baseUrl, options) => {
24
24
  routes,
25
25
  getHeaders = () => Promise.resolve({}),
26
26
  fetch: customFetch = fetch,
27
- validate = false
27
+ validate = false,
28
+ requestInit: genericRequestInit
28
29
  } = options ?? {};
29
30
  const buildUrl = (path, options2) => {
30
31
  const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
@@ -61,6 +62,8 @@ var createHttpClient = (baseUrl, options) => {
61
62
  handleValidation(method, path, payload, options2);
62
63
  const url = buildUrl(path, options2);
63
64
  const fetchOptions = {
65
+ ...genericRequestInit,
66
+ ...options2.requestInit,
64
67
  method,
65
68
  headers: {
66
69
  "Content-Type": "application/json",
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 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: any = routes?.[method]?.[route]\n\n const data = {\n params: req.params,\n\n payload:\n routeConfig?.payload && validationCheck.payload\n ? routeConfig.payload.parse(req.body)\n : req.body,\n\n queryParams:\n routeConfig?.queryParams && validationCheck.queryParams\n ? routeConfig.queryParams.parse(req.query)\n : req.query,\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\n// TS-only way of defining routes\nexport type DefineRoutes<T extends Routes> = T\n\n// Zod way of defining routes\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
+ {"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 requestInit?: RequestInit\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 requestInit?: RequestInit\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 requestInit: genericRequestInit,\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 ...genericRequestInit,\n ...options.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: any = routes?.[method]?.[route]\n\n const data = {\n params: req.params,\n\n payload:\n routeConfig?.payload && validationCheck.payload\n ? routeConfig.payload.parse(req.body)\n : req.body,\n\n queryParams:\n routeConfig?.queryParams && validationCheck.queryParams\n ? routeConfig.queryParams.parse(req.query)\n : req.query,\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\n// TS-only way of defining routes\nexport type DefineRoutes<T extends Routes> = T\n\n// Zod way of defining routes\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 CHANGED
@@ -22,7 +22,8 @@ var createHttpClient = (baseUrl, options) => {
22
22
  routes,
23
23
  getHeaders = () => Promise.resolve({}),
24
24
  fetch: customFetch = fetch,
25
- validate = false
25
+ validate = false,
26
+ requestInit: genericRequestInit
26
27
  } = options ?? {};
27
28
  const buildUrl = (path, options2) => {
28
29
  const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
@@ -59,6 +60,8 @@ var createHttpClient = (baseUrl, options) => {
59
60
  handleValidation(method, path, payload, options2);
60
61
  const url = buildUrl(path, options2);
61
62
  const fetchOptions = {
63
+ ...genericRequestInit,
64
+ ...options2.requestInit,
62
65
  method,
63
66
  headers: {
64
67
  "Content-Type": "application/json",
@@ -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 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: any = routes?.[method]?.[route]\n\n const data = {\n params: req.params,\n\n payload:\n routeConfig?.payload && validationCheck.payload\n ? routeConfig.payload.parse(req.body)\n : req.body,\n\n queryParams:\n routeConfig?.queryParams && validationCheck.queryParams\n ? routeConfig.queryParams.parse(req.query)\n : req.query,\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\n// TS-only way of defining routes\nexport type DefineRoutes<T extends Routes> = T\n\n// Zod way of defining routes\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
+ {"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 requestInit?: RequestInit\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 requestInit?: RequestInit\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 requestInit: genericRequestInit,\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 ...genericRequestInit,\n ...options.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: any = routes?.[method]?.[route]\n\n const data = {\n params: req.params,\n\n payload:\n routeConfig?.payload && validationCheck.payload\n ? routeConfig.payload.parse(req.body)\n : req.body,\n\n queryParams:\n routeConfig?.queryParams && validationCheck.queryParams\n ? routeConfig.queryParams.parse(req.query)\n : req.query,\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\n// TS-only way of defining routes\nexport type DefineRoutes<T extends Routes> = T\n\n// Zod way of defining routes\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.2.3",
3
+ "version": "1.3.0",
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",