@jokio/rpc 0.7.3 → 0.8.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/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var createClient = (routes, options) => {
24
24
  baseUrl,
25
25
  getHeaders = () => Promise.resolve({}),
26
26
  fetch: customFetch = fetch,
27
- validate = true
27
+ validate = false
28
28
  } = options;
29
29
  const buildUrl = (path, options2) => {
30
30
  const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options"],"mappings":";;;AA2CO,IAAM,iBAAA,GAAoB,CAC/B,IAAA,EACA,MAAA,KACW;AACX,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAY;AACnC,EAAA,MAAM,YAAA,GAAe,WAAA;AACrB,EAAA,IAAI,KAAA;AAGJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,YAAA,CAAa,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACjD,IAAA,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACzB;AAGA,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,IAAI,EAAE,aAAa,MAAA,CAAA,EAAS;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6BAAA,EAAgC,SAAS,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA;AAAA,OAC9D;AAAA,IACF;AAAA,EACF;AAGA,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAC,GAAG,SAAA,KAAc;AACjD,IAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,EACjC,CAAC,CAAA;AACH,CAAA;AAEO,IAAM,YAAA,GAAe,CAC1B,MAAA,EACA,OAAA,KACoB;AACpB,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,UAAA,GAAa,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AAAA,IACrC,OAAO,WAAA,GAAc,KAAA;AAAA,IACrB,QAAA,GAAW;AAAA,GACb,GAAI,OAAA;AAEJ,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,EAAcA,QAAAA,KAA0B;AACxD,IAAA,MAAM,WAAA,GAAcA,QAAAA,EAAS,WAAA,GACzB,GAAA,GAAM,IAAI,gBAAgBA,QAAAA,CAAQ,WAAW,CAAA,CAAE,QAAA,EAAS,GACxD,EAAA;AAEJ,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,GAC/B,iBAAA,CAAkB,IAAA,EAAMA,QAAAA,EAAS,MAAA,IAAU,EAAE,CAAA,GAC7C,IAAA;AAEJ,IAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAG,SAAS,GAAG,WAAW,CAAA,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CACvB,MAAA,EACA,IAAA,EACA,MACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,GAAY,IAAI,CAAA;AAClD,IAAA,IAAI,IAAA,IAAQ,aAAa,IAAA,EAAM;AAC7B,MAAA,WAAA,CAAY,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,IAC7B;AACA,IAAA,IAAIA,QAAAA,EAAS,WAAA,IAAe,WAAA,EAAa,WAAA,EAAa;AACpD,MAAA,WAAA,CAAY,WAAA,CAAY,KAAA,CAAMA,QAAAA,CAAQ,WAAW,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,OACrB,MAAA,EACA,IAAA,EACA,UACAA,QAAAA,KACG;AACH,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AAEvC,MAAA,IAAIA,UAAS,KAAA,EAAO;AAClB,QAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,MACrB;AAEA,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,GAAY,IAAI,CAAA;AAClD,IAAA,IAAI,WAAA,EAAa,QAAA,EAAU,IAAA,KAAS,MAAA,EAAQ;AAC1C,MAAA,MAAM,SAAS,IAAA,EAAK;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,YAAY,WAAA,EAAa,QAAA,GAC5B,YAAY,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GAC/B,IAAA;AAAA,EACN,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,OAClB,MAAA,EACA,IAAA,EACA,MACAA,QAAAA,KACG;AACH,IAAA,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAE5C,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,EAAMA,QAAO,CAAA;AAClC,IAAA,MAAM,YAAA,GAA4B;AAAA,MAChC,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,MAAM,UAAA;AAAW;AACvB,KACF;AAEA,IAAA,IAAI,SAAS,MAAA,EAAW;AACtB,MAAA,YAAA,CAAa,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,IACzC;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,GAAA,EAAK,YAAY,CAAA;AAEpD,IAAA,OAAO,cAAA,CAAe,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAUA,QAAO,CAAA;AAAA,EACvD,CAAA;AAEA,EAAA,MAAM,SAAS,EAAC;AAEhB,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,GAAA,EAAK,OAAO,IAAA,EAAWA,QAAAA,KACrB,YAAY,KAAA,EAAO,IAAA,EAAM,QAAWA,QAAO,CAAA;AAAA,IAC7C,KAAA,EAAO,OAAO,IAAA,EAAW,IAAA,EAAWA,aAClC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IAC1C,IAAA,EAAM,OAAO,IAAA,EAAW,IAAA,EAAWA,aACjC,WAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IACzC,GAAA,EAAK,OAAO,IAAA,EAAW,IAAA,EAAWA,aAChC,WAAA,CAAY,KAAA,EAAO,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IACxC,KAAA,EAAO,OAAO,IAAA,EAAW,IAAA,EAAWA,aAClC,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,IAAA,EAAMA,QAAO,CAAA;AAAA,IAC1C,MAAA,EAAQ,OAAO,IAAA,EAAW,IAAA,EAAWA,aACnC,WAAA,CAAY,QAAA,EAAU,IAAA,EAAM,IAAA,EAAMA,QAAO;AAAA,GAC7C;AAEA,EAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAEpC;AACD,IAAA,IAAI,UAAU,cAAA,EAAgB;AAC3B,MAAC,MAAA,CAAe,MAAM,CAAA,GAAI,cAAA,CAAe,MAAM,CAAA;AAAA,IAClD;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC/JA,IAAM,qBAAqB,CAKzB,MAAA,EACA,QACA,MAAA,EACA,QAAA,EACA,OACA,UAAA,KACG;AACH,EAAA,OAAO,OAAO,GAAA,EAAc,GAAA,EAAU,IAAA,KAAc;AAClD,IAAA,IAAI;AACF,MAAA,IAAI,MAAA,KAAW,OAAA,IAAW,GAAA,CAAI,MAAA,KAAW,OAAA,EAAS;AAChD,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,oBAAoB,CAAA;AACzC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,GAAA,GAAO,MAAA,CAAO,GAAG,CAAA,IAAK,EAAC;AAC7B,MAAA,MAAM,WAAA,GAAe,MAAA,CAAO,MAAM,CAAA,CAAU,KAAK,CAAA;AAEjD,MAAA,MAAM,IAAA,GAAO;AAAA,QACX,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,GAAI,WAAA,EAAa,IAAA,IAAQ,EAAE,IAAA,EAAM,YAAY,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAE;AAAA,QAClE,GAAI,aAAa,WAAA,IAAe;AAAA,UAC9B,WAAA,EAAa,WAAA,CAAY,WAAA,CAAY,KAAA,CAAM,IAAI,KAAK;AAAA;AACtD,OACF;AAEA,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,MAAM,EAAE,KAAK,CAAA,GAAI,MAAa,GAAG,CAAA;AAE/D,MAAA,GAAA,CAAI,KAAK,UAAA,GAAa,WAAA,EAAa,SAAS,KAAA,CAAM,MAAM,IAAI,MAAM,CAAA;AAAA,IACpE,SAAS,GAAA,EAAK;AACZ,MAAA,IAAA,CAAK,GAAG,CAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF,CAAA;AAEO,IAAM,qBAAA,GAAwB,CAInC,MAAA,EACA,MAAA,EACA,QAKA,QAAA,KACG;AACH,EAAA,MAAM,EAAE,UAAA,EAAY,UAAA,GAAa,MAAM,GAAA,GAAM,MAAM,MAAiB,GAAI,MAAA;AAExE,EAAA,MAAM,gBAAA,GAAmB;AAAA,IACvB,GAAA,EAAK,KAAA;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,GAAA,EAAK,KAAA;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,MAAA,EAAQ,QAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AAEA,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,YAAY,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AACrE,IAAA,MAAM,SAAA,GAAY,MAAA;AAClB,IAAA,MAAM,YAAA,GAAe,OAAO,SAAS,CAAA;AAErC,IAAA,IAAI,CAAC,YAAA,EAAc;AAEnB,IAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,YAAsB,CAAA,CAAE,MAAA;AAAA,MAC3C,CAAC,CAAA,EAAG,KAAA,KACF,CAAA,CAAE,YAAY,CAAA;AAAA,QACZ,KAAA;AAAA,QACA,kBAAA;AAAA,UACE,SAAA;AAAA,UACA,MAAA;AAAA,UACA,GAAA;AAAA,UACA,QAAA;AAAA,UACA,KAAA;AAAA,UACA;AAAA;AACF,OACF;AAAA,MACF;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAAA,MAAI,WAAA;AAAA,MAAa,OAAO,GAAG,GAAA,KACzC,GAAA,CAAI,YAAY,YAAY,CAAA,CAAE,KAAK,UAAU;AAAA,KAC/C;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACvGO,IAAM,YAAA,GAAe,CAC1B,MAAA,KACM","file":"index.js","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<RouterConfig>>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = true,\n } = options\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\n ) => {\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options?.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options"],"mappings":"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","file":"index.js","sourcesContent":["import {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for client options with optional params\ntype ClientOptions<TConfig, K> = Omit<TConfig, \"response\"> & {\n params?: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouterClient<T extends Partial<RouterConfig>> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? M extends \"GET\"\n ? <K extends keyof T[M]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<RouterConfig>>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\n ) => {\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options?.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ var createClient = (routes, options) => {
22
22
  baseUrl,
23
23
  getHeaders = () => Promise.resolve({}),
24
24
  fetch: customFetch = fetch,
25
- validate = true
25
+ validate = false
26
26
  } = options;
27
27
  const buildUrl = (path, options2) => {
28
28
  const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/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<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<RouterConfig>>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = true,\n } = options\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\n ) => {\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options?.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/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<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : <K extends keyof T[M]>(\n path: K,\n body: InferRouteConfig<T[M][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\n/**\n * Replaces path parameters with their values.\n * @param path - The path template with parameters (e.g., \"/:id/test/:name/info\")\n * @param params - The parameter values (e.g., {id: \"123\", name: \"434\"})\n * @returns The resolved path (e.g., \"/123/test/434/info\")\n * @throws Error if a required parameter is missing\n */\nexport const replacePathParams = (\n path: string,\n params: Record<string, string | number>\n): string => {\n const paramNames = new Set<string>()\n const paramPattern = /:([^/]+)/g\n let match: RegExpExecArray | null\n\n // Extract all parameter names from the path\n while ((match = paramPattern.exec(path)) !== null) {\n paramNames.add(match[1])\n }\n\n // Check if all required parameters are provided\n for (const paramName of paramNames) {\n if (!(paramName in params)) {\n throw new Error(\n `Missing required parameter: \"${paramName}\" for path \"${path}\"`\n )\n }\n }\n\n // Replace all parameters with their values\n return path.replace(/:([^/]+)/g, (_, paramName) => {\n return String(params[paramName])\n })\n}\n\nexport const createClient = <T extends Partial<RouterConfig>>(\n routes: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n getHeaders = () => Promise.resolve({}),\n fetch: customFetch = fetch,\n validate = false,\n } = options\n\n const buildUrl = (path: string, options?: any): string => {\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const finalPath = path.includes(\":\")\n ? replacePathParams(path, options?.params ?? {})\n : path\n\n return `${baseUrl}${finalPath}${queryString}`\n }\n\n const handleValidation = (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n if (!validate) return\n\n const routeConfig = (routes[method] as any)?.[path]\n if (body && routeConfig?.body) {\n routeConfig.body.parse(body)\n }\n if (options?.queryParams && routeConfig?.queryParams) {\n routeConfig.queryParams.parse(options.queryParams)\n }\n }\n\n const handleResponse = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n response: Response,\n options?: any\n ) => {\n if (!response.ok) {\n const error: any = await response.json()\n\n if (options?.debug) {\n console.debug(error)\n }\n\n throw new Error(error.message)\n }\n\n const routeConfig = (routes[method] as any)?.[path]\n if (routeConfig?.response?.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate && routeConfig?.response\n ? routeConfig.response.parse(json)\n : json\n }\n\n const makeRequest = async (\n method: keyof T & keyof RouterConfig,\n path: string,\n body?: any,\n options?: any\n ) => {\n handleValidation(method, path, body, options)\n\n const url = buildUrl(path, options)\n const fetchOptions: RequestInit = {\n method: method as string,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n }\n\n if (body !== undefined) {\n fetchOptions.body = JSON.stringify(body)\n }\n\n const response = await customFetch(url, fetchOptions)\n\n return handleResponse(method, path, response, options)\n }\n\n const client = {} as RouterClient<T>\n\n const methodHandlers = {\n GET: async (path: any, options?: any) =>\n makeRequest(\"GET\", path, undefined, options),\n QUERY: async (path: any, body: any, options?: any) =>\n makeRequest(\"QUERY\", path, body, options),\n POST: async (path: any, body: any, options?: any) =>\n makeRequest(\"POST\", path, body, options),\n PUT: async (path: any, body: any, options?: any) =>\n makeRequest(\"PUT\", path, body, options),\n PATCH: async (path: any, body: any, options?: any) =>\n makeRequest(\"PATCH\", path, body, options),\n DELETE: async (path: any, body: any, options?: any) =>\n makeRequest(\"DELETE\", path, body, options),\n }\n\n for (const method of Object.keys(routes) as Array<\n keyof T & keyof RouterConfig\n >) {\n if (method in methodHandlers) {\n ;(client as any)[method] = methodHandlers[method]\n }\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport {\n type ExtractRouteParams,\n type InferRouteConfig,\n type RouteConfig,\n type RouterConfig,\n} from \"./types\"\n\n// Reusable type for sync or async responses\ntype MaybePromise<T> = Promise<T> | T\n\n// Reusable type for handler data with params\ntype HandlerData<TConfig, K> = Omit<TConfig, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n}\n\nexport type RouteHandlers<T extends Partial<RouterConfig>, TContext> = {\n [M in keyof T & keyof RouterConfig]: T[M] extends Record<string, any>\n ? {\n [K in keyof T[M]]: T[M][K] extends\n | RouteConfig\n | Omit<RouteConfig, \"body\">\n ? (\n data: M extends \"GET\"\n ? HandlerData<Omit<InferRouteConfig<T[M][K]>, \"body\">, K>\n : HandlerData<InferRouteConfig<T[M][K]>, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[M][K]>[\"response\"]>\n : never\n }\n : never\n}\n\nconst createRouteHandler = <\n T extends Partial<RouterConfig>,\n TContext,\n M extends keyof RouteHandlers<T, TContext>\n>(\n method: M,\n routes: T,\n getCtx: (req: Request) => TContext,\n handlers: RouteHandlers<T, TContext> & {},\n route: string,\n validation: boolean\n) => {\n return async (req: Request, res: any, next: any) => {\n try {\n if (method === \"QUERY\" && req.method !== \"QUERY\") {\n res.status(405).send(\"Method Not Allowed\")\n return\n }\n\n const ctx = (getCtx(req) ?? {}) as TContext\n const routeConfig = (routes[method] as any)[route]\n\n const data = {\n params: req.params,\n ...(routeConfig?.body && { body: routeConfig.body.parse(req.body) }),\n ...(routeConfig?.queryParams && {\n queryParams: routeConfig.queryParams.parse(req.query),\n }),\n }\n\n const result = await handlers[method][route]?.(data as any, ctx)\n\n res.json(validation ? routeConfig?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }\n}\n\nexport const registerExpressRoutes = <\n T extends Partial<RouterConfig>,\n TContext\n>(\n router: Router,\n routes: T,\n config: {\n ctx?: (req: Request) => TContext\n schemaFile?: string\n validation?: boolean\n },\n handlers: RouteHandlers<T, TContext>\n) => {\n const { schemaFile, validation = true, ctx = () => null as TContext } = config\n\n const expressMethodMap = {\n GET: \"get\",\n POST: \"post\",\n PUT: \"put\",\n PATCH: \"patch\",\n DELETE: \"delete\",\n QUERY: \"all\",\n } as const\n\n for (const [method, routerMethod] of Object.entries(expressMethodMap)) {\n const methodKey = method as keyof RouteHandlers<T, TContext>\n const methodRoutes = routes[methodKey]\n\n if (!methodRoutes) continue\n\n router = Object.keys(methodRoutes as object).reduce(\n (r, route) =>\n r[routerMethod](\n route,\n createRouteHandler(\n methodKey,\n routes,\n ctx,\n handlers,\n route,\n validation\n )\n ),\n router\n )\n }\n\n if (schemaFile) {\n router = router.get(\"/__routes\", async (_, res) =>\n res.contentType(\"text/plain\").send(schemaFile)\n )\n }\n\n return router\n}\n","import type z from \"zod\"\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>\n QUERY: Record<string, RouteConfig>\n POST: Record<string, RouteConfig>\n PUT: Record<string, RouteConfig>\n PATCH: Record<string, RouteConfig>\n DELETE: Record<string, RouteConfig>\n}\n\nexport type RouteConfig = {\n body: z.ZodType\n queryParams?: z.ZodType\n response: z.ZodType\n}\n\nexport type InferRouteConfig<\n T extends RouteConfig | Omit<RouteConfig, \"body\">\n> = {\n [K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never\n}\n\nexport const defineRoutes = <T extends Partial<RouterConfig>>(\n routes: T\n): T => routes\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/info\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\nexport type ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Rest extends `:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : Rest extends `${string}/:${string}`\n ? {\n [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string\n }\n : { [K in Param]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokio/rpc",
3
- "version": "0.7.3",
3
+ "version": "0.8.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",