@jokio/rpc 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @jokio/rpc zod express
18
+ npm install @jokio/rpc
19
19
  ```
20
20
 
21
21
  ## Usage
@@ -23,8 +23,8 @@ npm install @jokio/rpc zod express
23
23
  ### 1. Define Your Router Configuration
24
24
 
25
25
  ```typescript
26
- import { defineRouterConfig } from "@jokio/rpc";
27
- import { z } from "zod";
26
+ import { defineRouterConfig } from "@jokio/rpc"
27
+ import { z } from "zod"
28
28
 
29
29
  const routerConfig = defineRouterConfig({
30
30
  GET: {
@@ -55,66 +55,74 @@ const routerConfig = defineRouterConfig({
55
55
  }),
56
56
  },
57
57
  },
58
- });
58
+ })
59
59
  ```
60
60
 
61
61
  ### 2. Set Up the Server
62
62
 
63
63
  ```typescript
64
- import express from "express";
65
- import { applyConfigToExpressRouter } from "@jokio/rpc";
64
+ import express from "express"
65
+ import { applyConfigToExpressRouter } from "@jokio/rpc"
66
66
 
67
- const app = express();
68
- app.use(express.json());
67
+ const app = express()
68
+ app.use(express.json())
69
69
 
70
- const router = express.Router();
70
+ const router = express.Router()
71
71
 
72
72
  applyConfigToExpressRouter(router, routerConfig, {
73
+ // Optional: Define a context factory function
74
+ ctx: (req) => ({
75
+ userId: req.headers["x-user-id"] as string,
76
+ // Add other context properties here
77
+ }),
73
78
  GET: {
74
- "/users/:id": async ({ query }) => {
75
- // Handler implementation
79
+ "/users/:id": async ({ query }, ctx) => {
80
+ // Handler implementation with context
81
+ console.log("Current user:", ctx.userId)
76
82
  return {
77
83
  id: "1",
78
84
  name: "John Doe",
79
85
  email: "john@example.com",
80
- };
86
+ }
81
87
  },
82
88
  },
83
89
  POST: {
84
- "/users": async ({ body, query }) => {
85
- // Handler implementation
90
+ "/users": async ({ body, query }, ctx) => {
91
+ // Handler implementation with context
92
+ console.log("Creating user, requested by:", ctx.userId)
86
93
  return {
87
94
  id: "2",
88
95
  name: body.name,
89
96
  email: body.email,
90
- };
97
+ }
91
98
  },
92
99
  },
93
- });
100
+ })
94
101
 
95
- app.use("/api", router);
96
- app.listen(3000);
102
+ app.use("/api", router)
103
+ app.listen(3000)
97
104
  ```
98
105
 
99
106
  ### 3. Create a Type-Safe Client
100
107
 
101
108
  ```typescript
102
- import { createClient } from "@jokio/rpc";
109
+ import { createClient } from "@jokio/rpc"
103
110
 
104
111
  const client = createClient(routerConfig, {
105
112
  baseUrl: "http://localhost:3000/api",
106
113
  validateRequest: true, // Optional: validate requests on client-side
107
- });
114
+ })
108
115
 
109
116
  // Fully typed API calls
110
- const user = await client.GET["/users/:id"]({
117
+ const user = await client.GET("/users/23", {
111
118
  query: { include: "posts" },
112
- });
119
+ })
113
120
 
114
- const newUser = await client.POST["/users"]({
115
- body: { name: "Jane Doe", email: "jane@example.com" },
116
- query: { sendEmail: true },
117
- });
121
+ const newUser = await client.POST(
122
+ "/users",
123
+ { name: "Jane Doe", email: "jane@example.com" },
124
+ { query: { sendEmail: true } }
125
+ )
118
126
  ```
119
127
 
120
128
  ## API Reference
@@ -131,7 +139,10 @@ Applies route handlers to an Express router with automatic validation.
131
139
 
132
140
  - `router`: Express Router instance
133
141
  - `config`: Router configuration object
134
- - `handlers`: Handler functions for each route
142
+ - `handlers`: Handler functions for each route with optional context factory
143
+ - `ctx`: Optional function `(req: Request) => TContext` to provide context to handlers
144
+ - `GET`: Handler functions that receive `(data, ctx)` parameters
145
+ - `POST`: Handler functions that receive `(data, ctx)` parameters
135
146
 
136
147
  ### `createClient(config, options)`
137
148
 
@@ -150,15 +161,13 @@ The library provides end-to-end type safety:
150
161
 
151
162
  ```typescript
152
163
  // TypeScript knows the exact shape of requests and responses
153
- const result = await client.POST["/users"]({
154
- body: {
155
- name: "John",
156
- email: "invalid-email", // Zod will catch this at runtime
157
- },
158
- });
164
+ const result = await client.POST("/users", {
165
+ name: "John",
166
+ email: "invalid-email", // Zod will catch this at runtime
167
+ })
159
168
 
160
169
  // result is typed as { id: string; name: string; email: string }
161
- console.log(result.id);
170
+ console.log(result.id)
162
171
  ```
163
172
 
164
173
  ## Error Handling
@@ -170,7 +179,7 @@ The library throws errors for:
170
179
 
171
180
  ```typescript
172
181
  try {
173
- await client.POST["/users"]({ body: invalidData });
182
+ await client.POST("/users", invalidData)
174
183
  } catch (error) {
175
184
  // Handle validation or HTTP errors
176
185
  }
package/dist/index.d.mts CHANGED
@@ -28,12 +28,21 @@ type CreateClientOptions = {
28
28
  };
29
29
  declare const createClient: <T extends RouterConfig>(config: T, options: CreateClientOptions) => RouterClient<T>;
30
30
 
31
+ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
32
+ [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
33
+ } : T extends `${infer _Start}:${infer Param}` ? {
34
+ [K in Param]: string;
35
+ } : Record<string, never>;
31
36
  type RouterHandlerConfig<T extends RouterConfig, TContext> = {
32
37
  GET: {
33
- [K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result">, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
38
+ [K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"> & {
39
+ params: K extends string ? ExtractRouteParams<K> : unknown;
40
+ }, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
34
41
  };
35
42
  POST: {
36
- [K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result">, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
43
+ [K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"> & {
44
+ params: K extends string ? ExtractRouteParams<K> : unknown;
45
+ }, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
37
46
  };
38
47
  };
39
48
  declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router, config: T, handlers: RouterHandlerConfig<T, TContext> & {
package/dist/index.d.ts CHANGED
@@ -28,12 +28,21 @@ type CreateClientOptions = {
28
28
  };
29
29
  declare const createClient: <T extends RouterConfig>(config: T, options: CreateClientOptions) => RouterClient<T>;
30
30
 
31
+ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
32
+ [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
33
+ } : T extends `${infer _Start}:${infer Param}` ? {
34
+ [K in Param]: string;
35
+ } : Record<string, never>;
31
36
  type RouterHandlerConfig<T extends RouterConfig, TContext> = {
32
37
  GET: {
33
- [K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result">, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
38
+ [K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "result"> & {
39
+ params: K extends string ? ExtractRouteParams<K> : unknown;
40
+ }, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["result"]> | InferRouteConfig<T["GET"][K]>["result"];
34
41
  };
35
42
  POST: {
36
- [K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result">, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
43
+ [K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "result"> & {
44
+ params: K extends string ? ExtractRouteParams<K> : unknown;
45
+ }, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["result"]> | InferRouteConfig<T["POST"][K]>["result"];
37
46
  };
38
47
  };
39
48
  declare const applyConfigToExpressRouter: <T extends RouterConfig, TContext>(router: Router, config: T, handlers: RouterHandlerConfig<T, TContext> & {
package/dist/index.js CHANGED
@@ -68,6 +68,7 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
68
68
  try {
69
69
  const ctx = handlers.ctx?.(req) ?? {};
70
70
  const data = {
71
+ params: req.params,
71
72
  query: config.GET[x]?.query?.parse(req.query)
72
73
  };
73
74
  const result = await handlers.GET[x]?.(data, ctx);
@@ -84,6 +85,7 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
84
85
  try {
85
86
  const ctx = handlers.ctx?.(req) ?? {};
86
87
  const data = {
88
+ params: req.params,
87
89
  body: config.POST[x]?.body.parse(req.body),
88
90
  query: config.POST[x]?.query?.parse(req.query)
89
91
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">,\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\">,\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\";\n\nexport type RouteConfig = {\n body: z.ZodType;\n query?: z.ZodType;\n result: z.ZodType;\n};\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>;\n POST: Record<string, RouteConfig>;\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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\";\n\nexport type RouteConfig = {\n body: z.ZodType;\n query?: z.ZodType;\n result: z.ZodType;\n};\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>;\n POST: Record<string, RouteConfig>;\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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
package/dist/index.mjs CHANGED
@@ -66,6 +66,7 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
66
66
  try {
67
67
  const ctx = handlers.ctx?.(req) ?? {};
68
68
  const data = {
69
+ params: req.params,
69
70
  query: config.GET[x]?.query?.parse(req.query)
70
71
  };
71
72
  const result = await handlers.GET[x]?.(data, ctx);
@@ -82,6 +83,7 @@ var applyConfigToExpressRouter = (router, config, handlers) => {
82
83
  try {
83
84
  const ctx = handlers.ctx?.(req) ?? {};
84
85
  const data = {
86
+ params: req.params,
85
87
  body: config.POST[x]?.body.parse(req.body),
86
88
  query: config.POST[x]?.query?.parse(req.query)
87
89
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">,\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\">,\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\";\n\nexport type RouteConfig = {\n body: z.ZodType;\n query?: z.ZodType;\n result: z.ZodType;\n};\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>;\n POST: Record<string, RouteConfig>;\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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { InferRouteConfig, RouterConfig } from \"./types\"\n\nexport type RouterClient<T extends RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\" | \"result\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n}\n\ntype FetchFunction = (url: string, options: RequestInit) => Promise<Response>\n\ntype CreateClientOptions = {\n baseUrl: string\n headers?: Record<string, string>\n fetch?: FetchFunction\n validateRequest?: boolean\n}\n\nexport const createClient = <T extends RouterConfig>(\n config: T,\n options: CreateClientOptions\n): RouterClient<T> => {\n const {\n baseUrl,\n headers = {},\n fetch: customFetch = fetch,\n validateRequest = false,\n } = options\n\n const client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, data?: any) => {\n if (validateRequest && data?.query) {\n config.GET[path]?.query?.parse(data.query)\n }\n const queryString = data?.query\n ? \"?\" + new URLSearchParams(data.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, rest?: any) => {\n if (validateRequest) {\n if (body) {\n config.POST[path]?.body?.parse(body)\n }\n if (rest?.query) {\n config.POST[path]?.query?.parse(rest.query)\n }\n }\n const queryString = rest?.query\n ? \"?\" + new URLSearchParams(rest.query).toString()\n : \"\"\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error: any = await response.json()\n console.error(error)\n throw new Error(error.message)\n }\n\n const json = await response.json()\n\n return config.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport type { InferRouteConfig, RouterConfig } from \"./types\"\n\n// Extract path parameters from route string\n// e.g., \"/user/:id\" -> { id: string }, \"/user/:id/post/:postId\" -> { id: string, postId: string }\ntype ExtractRouteParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }\n : T extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Record<string, never>\n\nexport type RouterHandlerConfig<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"result\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"result\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"result\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"result\"]\n }\n}\n\nexport const applyConfigToExpressRouter = <T extends RouterConfig, TContext>(\n router: Router,\n config: T,\n handlers: RouterHandlerConfig<T, TContext> & {\n ctx?: (req: Request) => TContext\n }\n) => {\n router = Object.keys(config.GET).reduce(\n (r, x) =>\n r.get(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n query: config.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = config.GET[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n router = Object.keys(config.POST).reduce(\n (r, x) =>\n r.post(x, async (req, res, next) => {\n try {\n const ctx = (handlers.ctx?.(req) ?? {}) as TContext\n\n const data = {\n params: req.params,\n body: config.POST[x]?.body.parse(req.body),\n query: config.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = config.POST[x]?.result.parse(result)\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n return router\n}\n","import type z from \"zod\";\n\nexport type RouteConfig = {\n body: z.ZodType;\n query?: z.ZodType;\n result: z.ZodType;\n};\n\nexport type RouterConfig = {\n GET: Record<string, Omit<RouteConfig, \"body\">>;\n POST: Record<string, RouteConfig>;\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 defineRouterConfig = <T extends RouterConfig>(config: T): T =>\n config;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokio/rpc",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Type-safe RPC framework with Zod validation for Express and TypeScript",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",