@jokio/rpc 0.4.1 → 0.5.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 +6 -3
- package/dist/index.d.mts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,13 +4,16 @@ A type-safe RPC framework for TypeScript with Zod validation, designed for Expre
|
|
|
4
4
|
|
|
5
5
|
<img width="400" height="400" alt="ChatGPT Image Jan 4, 2026 at 10_15_01 AM" src="https://github.com/user-attachments/assets/5ca6462a-4d3a-46d6-ac09-31ecbc4d06fb" />
|
|
6
6
|
|
|
7
|
+
## Use Cases
|
|
8
|
+
|
|
9
|
+
**Front-end → Backend** | **Backend → Backend**
|
|
10
|
+
|
|
7
11
|
## Features
|
|
8
12
|
|
|
9
13
|
- Full TypeScript type safety from server to client
|
|
10
14
|
- Runtime validation using Zod schemas
|
|
11
15
|
- Express.js integration for server-side
|
|
12
16
|
- Flexible fetch-based client with custom fetch support
|
|
13
|
-
- Support for both GET and POST routes
|
|
14
17
|
- Query parameters and request body validation
|
|
15
18
|
- Automatic response validation
|
|
16
19
|
|
|
@@ -31,7 +34,7 @@ import { z } from "zod"
|
|
|
31
34
|
const routes = defineRoutes({
|
|
32
35
|
GET: {
|
|
33
36
|
"/user/:id": {
|
|
34
|
-
|
|
37
|
+
response: z.object({
|
|
35
38
|
id: z.string(),
|
|
36
39
|
name: z.string(),
|
|
37
40
|
email: z.string(),
|
|
@@ -44,7 +47,7 @@ const routes = defineRoutes({
|
|
|
44
47
|
name: z.string(),
|
|
45
48
|
email: z.string().email(),
|
|
46
49
|
}),
|
|
47
|
-
|
|
50
|
+
response: z.object({
|
|
48
51
|
id: z.string(),
|
|
49
52
|
name: z.string(),
|
|
50
53
|
email: z.string(),
|
package/dist/index.d.mts
CHANGED
|
@@ -3,8 +3,8 @@ import { Router, Request } from 'express';
|
|
|
3
3
|
|
|
4
4
|
type RouteConfig = {
|
|
5
5
|
body: z.ZodType;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
queryParams?: z.ZodType;
|
|
7
|
+
response: z.ZodType;
|
|
8
8
|
};
|
|
9
9
|
type RouterConfig = {
|
|
10
10
|
GET: Record<string, Omit<RouteConfig, "body">>;
|
|
@@ -16,8 +16,8 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "
|
|
19
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response">) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
20
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "response">) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
21
|
};
|
|
22
22
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
23
|
type CreateClientOptions = {
|
|
@@ -36,14 +36,14 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
|
|
|
36
36
|
} : Record<string, never>;
|
|
37
37
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
38
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
39
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response"> & {
|
|
40
40
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["
|
|
41
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
42
42
|
};
|
|
43
43
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "
|
|
44
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "response"> & {
|
|
45
45
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["
|
|
46
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
49
|
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { Router, Request } from 'express';
|
|
|
3
3
|
|
|
4
4
|
type RouteConfig = {
|
|
5
5
|
body: z.ZodType;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
queryParams?: z.ZodType;
|
|
7
|
+
response: z.ZodType;
|
|
8
8
|
};
|
|
9
9
|
type RouterConfig = {
|
|
10
10
|
GET: Record<string, Omit<RouteConfig, "body">>;
|
|
@@ -16,8 +16,8 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
17
|
|
|
18
18
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "
|
|
19
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response">) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
20
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body" | "response">) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
21
|
};
|
|
22
22
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
23
|
type CreateClientOptions = {
|
|
@@ -36,14 +36,14 @@ type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer P
|
|
|
36
36
|
} : Record<string, never>;
|
|
37
37
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
38
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "
|
|
39
|
+
[K in keyof T["GET"]]: (data: Omit<Omit<InferRouteConfig<T["GET"][K]>, "body">, "response"> & {
|
|
40
40
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["
|
|
41
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
42
42
|
};
|
|
43
43
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "
|
|
44
|
+
[K in keyof T["POST"]]: (data: Omit<InferRouteConfig<T["POST"][K]>, "response"> & {
|
|
45
45
|
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["
|
|
46
|
+
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
49
|
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,10 @@ var createClient = (routes, options) => {
|
|
|
15
15
|
POST: {}
|
|
16
16
|
};
|
|
17
17
|
client.GET = async (path, options2) => {
|
|
18
|
-
if (validate && options2?.
|
|
19
|
-
routes.GET[path]?.
|
|
18
|
+
if (validate && options2?.queryParams) {
|
|
19
|
+
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
20
20
|
}
|
|
21
|
-
const queryString = options2?.
|
|
21
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
22
22
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
23
23
|
method: "GET",
|
|
24
24
|
headers: {
|
|
@@ -34,18 +34,18 @@ var createClient = (routes, options) => {
|
|
|
34
34
|
throw new Error(error.message);
|
|
35
35
|
}
|
|
36
36
|
const json = await response.json();
|
|
37
|
-
return routes.GET[path]?.
|
|
37
|
+
return routes.GET[path]?.response.parse(json);
|
|
38
38
|
};
|
|
39
39
|
client.POST = async (path, body, options2) => {
|
|
40
40
|
if (validate) {
|
|
41
41
|
if (body) {
|
|
42
42
|
routes.POST[path]?.body?.parse(body);
|
|
43
43
|
}
|
|
44
|
-
if (options2?.
|
|
45
|
-
routes.POST[path]?.
|
|
44
|
+
if (options2?.queryParams) {
|
|
45
|
+
routes.POST[path]?.queryParams?.parse(options2.queryParams);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
const queryString = options2?.
|
|
48
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
49
49
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
50
50
|
method: "POST",
|
|
51
51
|
headers: {
|
|
@@ -62,7 +62,7 @@ var createClient = (routes, options) => {
|
|
|
62
62
|
throw new Error(error.message);
|
|
63
63
|
}
|
|
64
64
|
const json = await response.json();
|
|
65
|
-
return routes.POST[path]?.
|
|
65
|
+
return routes.POST[path]?.response.parse(json);
|
|
66
66
|
};
|
|
67
67
|
return client;
|
|
68
68
|
};
|
|
@@ -73,10 +73,10 @@ var registerExpressRoutes = (router, routes, handlers) => {
|
|
|
73
73
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
74
74
|
const data = {
|
|
75
75
|
params: req.params,
|
|
76
|
-
|
|
76
|
+
queryParams: routes.GET[x]?.queryParams?.parse(req.query)
|
|
77
77
|
};
|
|
78
78
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
79
|
-
const validatedResult = routes.GET[x]?.
|
|
79
|
+
const validatedResult = routes.GET[x]?.response.parse(result);
|
|
80
80
|
res.json(validatedResult);
|
|
81
81
|
} catch (err) {
|
|
82
82
|
next(err);
|
|
@@ -97,10 +97,10 @@ var registerExpressRoutes = (router, routes, handlers) => {
|
|
|
97
97
|
const data = {
|
|
98
98
|
params: req.params,
|
|
99
99
|
body: routes.POST[x]?.body.parse(req.body),
|
|
100
|
-
|
|
100
|
+
queryParams: routes.POST[x]?.queryParams?.parse(req.query)
|
|
101
101
|
};
|
|
102
102
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
103
|
-
const validatedResult = routes.POST[x]?.
|
|
103
|
+
const validatedResult = routes.POST[x]?.response.parse(result);
|
|
104
104
|
res.json(validatedResult);
|
|
105
105
|
} catch (err) {
|
|
106
106
|
next(err);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options","readFile"],"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 getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends 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 client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.query) {\n routes.GET[path]?.query?.parse(options.query)\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\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 json = await response.json()\n\n return routes.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.query) {\n routes.POST[path]?.query?.parse(options.query)\n }\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\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 json = await response.json()\n\n return routes.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\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 RouteHandlers<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 registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.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: routes.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.result.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.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: routes.POST[x]?.body.parse(req.body),\n query: routes.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.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 defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts","../src/types.ts"],"names":["options","readFile"],"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\">, \"response\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\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\" | \"response\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\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\nexport const createClient = <T extends 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 client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.queryParams) {\n routes.GET[path]?.queryParams?.parse(options.queryParams)\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\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 json = await response.json()\n\n return routes.GET[path]?.response.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.queryParams) {\n routes.POST[path]?.queryParams?.parse(options.queryParams)\n }\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\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 json = await response.json()\n\n return routes.POST[path]?.response.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\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 RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"response\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"response\"]\n }\n}\n\nexport const registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.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 queryParams: routes.GET[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.response.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.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: routes.POST[x]?.body.parse(req.body),\n queryParams: routes.POST[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.response.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 queryParams?: z.ZodType\n response: 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 defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -13,10 +13,10 @@ var createClient = (routes, options) => {
|
|
|
13
13
|
POST: {}
|
|
14
14
|
};
|
|
15
15
|
client.GET = async (path, options2) => {
|
|
16
|
-
if (validate && options2?.
|
|
17
|
-
routes.GET[path]?.
|
|
16
|
+
if (validate && options2?.queryParams) {
|
|
17
|
+
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
18
18
|
}
|
|
19
|
-
const queryString = options2?.
|
|
19
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
20
20
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
21
21
|
method: "GET",
|
|
22
22
|
headers: {
|
|
@@ -32,18 +32,18 @@ var createClient = (routes, options) => {
|
|
|
32
32
|
throw new Error(error.message);
|
|
33
33
|
}
|
|
34
34
|
const json = await response.json();
|
|
35
|
-
return routes.GET[path]?.
|
|
35
|
+
return routes.GET[path]?.response.parse(json);
|
|
36
36
|
};
|
|
37
37
|
client.POST = async (path, body, options2) => {
|
|
38
38
|
if (validate) {
|
|
39
39
|
if (body) {
|
|
40
40
|
routes.POST[path]?.body?.parse(body);
|
|
41
41
|
}
|
|
42
|
-
if (options2?.
|
|
43
|
-
routes.POST[path]?.
|
|
42
|
+
if (options2?.queryParams) {
|
|
43
|
+
routes.POST[path]?.queryParams?.parse(options2.queryParams);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
const queryString = options2?.
|
|
46
|
+
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
47
47
|
const response = await customFetch(`${baseUrl}${path}${queryString}`, {
|
|
48
48
|
method: "POST",
|
|
49
49
|
headers: {
|
|
@@ -60,7 +60,7 @@ var createClient = (routes, options) => {
|
|
|
60
60
|
throw new Error(error.message);
|
|
61
61
|
}
|
|
62
62
|
const json = await response.json();
|
|
63
|
-
return routes.POST[path]?.
|
|
63
|
+
return routes.POST[path]?.response.parse(json);
|
|
64
64
|
};
|
|
65
65
|
return client;
|
|
66
66
|
};
|
|
@@ -71,10 +71,10 @@ var registerExpressRoutes = (router, routes, handlers) => {
|
|
|
71
71
|
const ctx = handlers.ctx?.(req) ?? {};
|
|
72
72
|
const data = {
|
|
73
73
|
params: req.params,
|
|
74
|
-
|
|
74
|
+
queryParams: routes.GET[x]?.queryParams?.parse(req.query)
|
|
75
75
|
};
|
|
76
76
|
const result = await handlers.GET[x]?.(data, ctx);
|
|
77
|
-
const validatedResult = routes.GET[x]?.
|
|
77
|
+
const validatedResult = routes.GET[x]?.response.parse(result);
|
|
78
78
|
res.json(validatedResult);
|
|
79
79
|
} catch (err) {
|
|
80
80
|
next(err);
|
|
@@ -95,10 +95,10 @@ var registerExpressRoutes = (router, routes, handlers) => {
|
|
|
95
95
|
const data = {
|
|
96
96
|
params: req.params,
|
|
97
97
|
body: routes.POST[x]?.body.parse(req.body),
|
|
98
|
-
|
|
98
|
+
queryParams: routes.POST[x]?.queryParams?.parse(req.query)
|
|
99
99
|
};
|
|
100
100
|
const result = await handlers.POST[x]?.(data, ctx);
|
|
101
|
-
const validatedResult = routes.POST[x]?.
|
|
101
|
+
const validatedResult = routes.POST[x]?.response.parse(result);
|
|
102
102
|
res.json(validatedResult);
|
|
103
103
|
} catch (err) {
|
|
104
104
|
next(err);
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 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 getHeaders?: () => Promise<Record<string, string>> | Record<string, string>\n fetch?: FetchFunction\n validate?: boolean\n debug?: boolean\n}\n\nexport const createClient = <T extends 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 client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.query) {\n routes.GET[path]?.query?.parse(options.query)\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\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 json = await response.json()\n\n return routes.GET[path]?.result.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.query) {\n routes.POST[path]?.query?.parse(options.query)\n }\n }\n\n const queryString = options?.query\n ? \"?\" + new URLSearchParams(options.query).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\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 json = await response.json()\n\n return routes.POST[path]?.result.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\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 RouteHandlers<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 registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.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: routes.GET[x]?.query?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.result.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.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: routes.POST[x]?.body.parse(req.body),\n query: routes.POST[x]?.query?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.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 defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\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 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\">, \"response\">\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\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\" | \"response\">\n ) => Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\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\nexport const createClient = <T extends 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 client = {\n GET: {} as any,\n POST: {} as any,\n }\n\n client.GET = async (path: string, options?: any) => {\n if (validate && options?.queryParams) {\n routes.GET[path]?.queryParams?.parse(options.queryParams)\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n })\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 json = await response.json()\n\n return routes.GET[path]?.response.parse(json)\n }\n\n client.POST = async (path: string, body: any, options?: any) => {\n if (validate) {\n if (body) {\n routes.POST[path]?.body?.parse(body)\n }\n if (options?.queryParams) {\n routes.POST[path]?.queryParams?.parse(options.queryParams)\n }\n }\n\n const queryString = options?.queryParams\n ? \"?\" + new URLSearchParams(options.queryParams).toString()\n : \"\"\n\n const response = await customFetch(`${baseUrl}${path}${queryString}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await getHeaders()),\n },\n body: JSON.stringify(body),\n })\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 json = await response.json()\n\n return routes.POST[path]?.response.parse(json)\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\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 RouteHandlers<T extends RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: Omit<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"GET\"][K]>[\"response\"]\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: Omit<InferRouteConfig<T[\"POST\"][K]>, \"response\"> & {\n params: K extends string ? ExtractRouteParams<K> : unknown\n },\n ctx: TContext\n ) =>\n | Promise<InferRouteConfig<T[\"POST\"][K]>[\"response\"]>\n | InferRouteConfig<T[\"POST\"][K]>[\"response\"]\n }\n}\n\nexport const registerExpressRoutes = <T extends RouterConfig, TContext>(\n router: Router,\n routes: T,\n handlers: RouteHandlers<T, TContext> & {\n ctx?: (req: Request) => TContext\n schemaFilePath?: string\n }\n) => {\n router = Object.keys(routes.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 queryParams: routes.GET[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.GET[x]?.(data as any, ctx)\n const validatedResult = routes.GET[x]?.response.parse(result)\n\n res.json(validatedResult)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (handlers.schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(handlers.schemaFilePath!, \"utf8\"))\n )\n }\n\n router = Object.keys(routes.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: routes.POST[x]?.body.parse(req.body),\n queryParams: routes.POST[x]?.queryParams?.parse(req.query),\n }\n const result = await handlers.POST[x]?.(data as any, ctx)\n const validatedResult = routes.POST[x]?.response.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 queryParams?: z.ZodType\n response: 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 defineRoutes = <T extends RouterConfig>(routes: T): T =>\n routes\n"]}
|