@jokio/rpc 0.5.4 → 0.6.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/dist/index.d.mts +21 -14
- package/dist/index.d.ts +21 -14
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -14,10 +14,22 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
14
14
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never;
|
|
15
15
|
};
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
|
+
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
18
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
19
|
+
} : Rest extends `${string}/:${string}` ? {
|
|
20
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
21
|
+
} : {
|
|
22
|
+
[K in Param]: string;
|
|
23
|
+
} : T extends `${infer _Start}:${infer Param}` ? {
|
|
24
|
+
[K in Param]: string;
|
|
25
|
+
} : Record<string, never>;
|
|
17
26
|
|
|
27
|
+
type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
|
|
28
|
+
params?: K extends string ? ExtractRouteParams<K> : unknown;
|
|
29
|
+
};
|
|
18
30
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?:
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body"
|
|
31
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T["GET"][K]>, "body">, K>) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
32
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: ClientOptions<Omit<InferRouteConfig<T["POST"][K]>, "body">, K>) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
33
|
};
|
|
22
34
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
35
|
type CreateClientOptions = {
|
|
@@ -29,21 +41,16 @@ type CreateClientOptions = {
|
|
|
29
41
|
};
|
|
30
42
|
declare const createClient: <T extends RouterConfig>(routes: T, options: CreateClientOptions) => RouterClient<T>;
|
|
31
43
|
|
|
32
|
-
type
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} : Record<string, never>;
|
|
44
|
+
type MaybePromise<T> = Promise<T> | T;
|
|
45
|
+
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
46
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
47
|
+
};
|
|
37
48
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
49
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data:
|
|
40
|
-
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
50
|
+
[K in keyof T["GET"]]: (data: HandlerData<Omit<InferRouteConfig<T["GET"][K]>, "body">, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
42
51
|
};
|
|
43
52
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data:
|
|
45
|
-
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
53
|
+
[K in keyof T["POST"]]: (data: HandlerData<InferRouteConfig<T["POST"][K]>, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
47
54
|
};
|
|
48
55
|
};
|
|
49
56
|
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
|
@@ -52,4 +59,4 @@ declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router:
|
|
|
52
59
|
validation?: boolean;
|
|
53
60
|
}) => Router;
|
|
54
61
|
|
|
55
|
-
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
|
62
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,22 @@ type InferRouteConfig<T extends RouteConfig | Omit<RouteConfig, "body">> = {
|
|
|
14
14
|
[K in keyof T]: T[K] extends z.ZodType ? z.infer<T[K]> : never;
|
|
15
15
|
};
|
|
16
16
|
declare const defineRoutes: <T extends RouterConfig>(routes: T) => T;
|
|
17
|
+
type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Rest extends `:${string}` ? {
|
|
18
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
19
|
+
} : Rest extends `${string}/:${string}` ? {
|
|
20
|
+
[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string;
|
|
21
|
+
} : {
|
|
22
|
+
[K in Param]: string;
|
|
23
|
+
} : T extends `${infer _Start}:${infer Param}` ? {
|
|
24
|
+
[K in Param]: string;
|
|
25
|
+
} : Record<string, never>;
|
|
17
26
|
|
|
27
|
+
type ClientOptions<TConfig, K> = Omit<TConfig, "response"> & {
|
|
28
|
+
params?: K extends string ? ExtractRouteParams<K> : unknown;
|
|
29
|
+
};
|
|
18
30
|
type RouterClient<T extends RouterConfig> = {
|
|
19
|
-
GET: <K extends keyof T["GET"]>(path: K, options?:
|
|
20
|
-
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: Omit<InferRouteConfig<T["POST"][K]>, "body"
|
|
31
|
+
GET: <K extends keyof T["GET"]>(path: K, options?: ClientOptions<Omit<InferRouteConfig<T["GET"][K]>, "body">, K>) => Promise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
32
|
+
POST: <K extends keyof T["POST"]>(path: K, body: InferRouteConfig<T["POST"][K]>["body"], options?: ClientOptions<Omit<InferRouteConfig<T["POST"][K]>, "body">, K>) => Promise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
21
33
|
};
|
|
22
34
|
type FetchFunction = (url: string, options: RequestInit) => Promise<Response>;
|
|
23
35
|
type CreateClientOptions = {
|
|
@@ -29,21 +41,16 @@ type CreateClientOptions = {
|
|
|
29
41
|
};
|
|
30
42
|
declare const createClient: <T extends RouterConfig>(routes: T, options: CreateClientOptions) => RouterClient<T>;
|
|
31
43
|
|
|
32
|
-
type
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} : Record<string, never>;
|
|
44
|
+
type MaybePromise<T> = Promise<T> | T;
|
|
45
|
+
type HandlerData<TConfig, K> = Omit<TConfig, "response"> & {
|
|
46
|
+
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
47
|
+
};
|
|
37
48
|
type RouteHandlers<T extends RouterConfig, TContext> = {
|
|
38
49
|
GET: {
|
|
39
|
-
[K in keyof T["GET"]]: (data:
|
|
40
|
-
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
41
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["GET"][K]>["response"]> | InferRouteConfig<T["GET"][K]>["response"];
|
|
50
|
+
[K in keyof T["GET"]]: (data: HandlerData<Omit<InferRouteConfig<T["GET"][K]>, "body">, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T["GET"][K]>["response"]>;
|
|
42
51
|
};
|
|
43
52
|
POST: {
|
|
44
|
-
[K in keyof T["POST"]]: (data:
|
|
45
|
-
params: K extends string ? ExtractRouteParams<K> : unknown;
|
|
46
|
-
}, ctx: TContext) => Promise<InferRouteConfig<T["POST"][K]>["response"]> | InferRouteConfig<T["POST"][K]>["response"];
|
|
53
|
+
[K in keyof T["POST"]]: (data: HandlerData<InferRouteConfig<T["POST"][K]>, K>, ctx: TContext) => MaybePromise<InferRouteConfig<T["POST"][K]>["response"]>;
|
|
47
54
|
};
|
|
48
55
|
};
|
|
49
56
|
declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router: Router, routes: T, handlers: RouteHandlers<T, TContext> & {
|
|
@@ -52,4 +59,4 @@ declare const registerExpressRoutes: <T extends RouterConfig, TContext>(router:
|
|
|
52
59
|
validation?: boolean;
|
|
53
60
|
}) => Router;
|
|
54
61
|
|
|
55
|
-
export { type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
|
62
|
+
export { type ExtractRouteParams, type InferRouteConfig, type RouteConfig, type RouteHandlers, type RouterClient, type RouterConfig, createClient, defineRoutes, registerExpressRoutes };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
4
|
|
|
5
5
|
// src/client.ts
|
|
6
|
+
var replacePathParams = (path, params) => {
|
|
7
|
+
const paramNames = /* @__PURE__ */ new Set();
|
|
8
|
+
const paramPattern = /:([^/]+)/g;
|
|
9
|
+
let match;
|
|
10
|
+
while ((match = paramPattern.exec(path)) !== null) {
|
|
11
|
+
paramNames.add(match[1]);
|
|
12
|
+
}
|
|
13
|
+
for (const paramName of paramNames) {
|
|
14
|
+
if (!(paramName in params)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Missing required parameter: "${paramName}" for path "${path}"`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return path.replace(/:([^/]+)/g, (_, paramName) => {
|
|
21
|
+
return String(params[paramName]);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
6
24
|
var createClient = (routes, options) => {
|
|
7
25
|
const {
|
|
8
26
|
baseUrl,
|
|
@@ -19,7 +37,8 @@ var createClient = (routes, options) => {
|
|
|
19
37
|
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
20
38
|
}
|
|
21
39
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
22
|
-
const
|
|
40
|
+
const finalPath = replacePathParams(path, options2.params ?? {});
|
|
41
|
+
const response = await customFetch(`${baseUrl}${finalPath}${queryString}`, {
|
|
23
42
|
method: "GET",
|
|
24
43
|
headers: {
|
|
25
44
|
"Content-Type": "application/json",
|
|
@@ -50,7 +69,8 @@ var createClient = (routes, options) => {
|
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
53
|
-
const
|
|
72
|
+
const finalPath = replacePathParams(path, options2.params ?? {});
|
|
73
|
+
const response = await customFetch(`${baseUrl}${finalPath}${queryString}`, {
|
|
54
74
|
method: "POST",
|
|
55
75
|
headers: {
|
|
56
76
|
"Content-Type": "application/json",
|
|
@@ -75,7 +95,7 @@ var createClient = (routes, options) => {
|
|
|
75
95
|
return client;
|
|
76
96
|
};
|
|
77
97
|
var registerExpressRoutes = (router, routes, handlers) => {
|
|
78
|
-
const { validation =
|
|
98
|
+
const { validation = false, schemaFilePath } = handlers;
|
|
79
99
|
router = Object.keys(routes.GET).reduce(
|
|
80
100
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
81
101
|
try {
|
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":";;;;;AAwBO,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,MAAA,GAAS;AAAA,IACb,KAAK,EAAC;AAAA,IACN,MAAM;AAAC,GACT;AAEA,EAAA,MAAA,CAAO,GAAA,GAAM,OAAO,IAAA,EAAcA,QAAAA,KAAkB;AAClD,IAAA,IAAI,QAAA,IAAYA,UAAS,WAAA,EAAa;AACpC,MAAA,MAAA,CAAO,IAAI,IAAI,CAAA,EAAG,WAAA,EAAa,KAAA,CAAMA,SAAQ,WAAW,CAAA;AAAA,IAC1D;AAEA,IAAA,MAAM,WAAA,GAAcA,QAAAA,EAAS,WAAA,GACzB,GAAA,GAAM,IAAI,gBAAgBA,QAAAA,CAAQ,WAAW,CAAA,CAAE,QAAA,EAAS,GACxD,EAAA;AAEJ,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,CAAA,EAAG,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI;AAAA,MACpE,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,MAAM,UAAA;AAAW;AACvB,KACD,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AAEvC,MAAA,IAAIA,SAAQ,KAAA,EAAO;AACjB,QAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,MACrB;AAEA,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,IAAI,OAAO,GAAA,CAAI,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,MAAA,EAAQ;AAC7C,MAAA,MAAM,SAAS,IAAA,EAAK;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,QAAA,GAAW,OAAO,GAAA,CAAI,IAAI,GAAG,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAAA,EAC7D,CAAA;AAEA,EAAA,MAAA,CAAO,IAAA,GAAO,OAAO,IAAA,EAAc,IAAA,EAAWA,QAAAA,KAAkB;AAC9D,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,EAAM,MAAM,IAAI,CAAA;AAAA,MACrC;AACA,MAAA,IAAIA,UAAS,WAAA,EAAa;AACxB,QAAA,MAAA,CAAO,KAAK,IAAI,CAAA,EAAG,WAAA,EAAa,KAAA,CAAMA,SAAQ,WAAW,CAAA;AAAA,MAC3D;AAAA,IACF;AAEA,IAAA,MAAM,WAAA,GAAcA,QAAAA,EAAS,WAAA,GACzB,GAAA,GAAM,IAAI,gBAAgBA,QAAAA,CAAQ,WAAW,CAAA,CAAE,QAAA,EAAS,GACxD,EAAA;AAEJ,IAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,CAAA,EAAG,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI;AAAA,MACpE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,MAAM,UAAA;AAAW,OACvB;AAAA,MACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI;AAAA,KAC1B,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,KAAA,GAAa,MAAM,QAAA,CAAS,IAAA,EAAK;AAEvC,MAAA,IAAIA,SAAQ,KAAA,EAAO;AACjB,QAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,MACrB;AAEA,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,IAAI,OAAO,IAAA,CAAK,IAAI,CAAA,CAAE,QAAA,CAAS,SAAS,MAAA,EAAQ;AAC9C,MAAA,MAAM,SAAS,IAAA,EAAK;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,QAAA,GAAW,OAAO,IAAA,CAAK,IAAI,GAAG,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAAA,EAC9D,CAAA;AAEA,EAAA,OAAO,MAAA;AACT;ACrFO,IAAM,qBAAA,GAAwB,CACnC,MAAA,EACA,MAAA,EACA,QAAA,KAKG;AACH,EAAA,MAAM,EAAE,UAAA,GAAa,IAAA,EAAM,cAAA,EAAe,GAAI,QAAA;AAE9C,EAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA,CAAE,MAAA;AAAA,IAC/B,CAAC,GAAG,CAAA,KACF,CAAA,CAAE,IAAI,CAAA,EAAG,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AACjC,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAO,QAAA,CAAS,GAAA,GAAM,GAAG,KAAK,EAAC;AAErC,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,QAAQ,GAAA,CAAI,MAAA;AAAA,UACZ,WAAA,EAAa,OAAO,GAAA,CAAI,CAAC,GAAG,WAAA,EAAa,KAAA,CAAM,IAAI,KAAK;AAAA,SAC1D;AACA,QAAA,MAAM,SAAS,MAAM,QAAA,CAAS,IAAI,CAAC,CAAA,GAAI,MAAa,GAAG,CAAA;AAEvD,QAAA,GAAA,CAAI,IAAA,CAAK,UAAA,GAAa,MAAA,CAAO,GAAA,CAAI,CAAC,GAAG,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,GAAI,MAAM,CAAA;AAAA,MACtE,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,GAAG,CAAA;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AAEA,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAAA,MAAI,WAAA;AAAA,MAAa,OAAO,CAAA,EAAG,GAAA,KACzC,GAAA,CACG,WAAA,CAAY,YAAY,CAAA,CACxB,IAAA,CAAK,MAAMC,iBAAA,CAAS,cAAA,EAAiB,MAAM,CAAC;AAAA,KACjD;AAAA,EACF;AAEA,EAAA,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA;AAAA,IAChC,CAAC,GAAG,CAAA,KACF,CAAA,CAAE,KAAK,CAAA,EAAG,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AAClC,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAO,QAAA,CAAS,GAAA,GAAM,GAAG,KAAK,EAAC;AAErC,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,QAAQ,GAAA,CAAI,MAAA;AAAA,UACZ,IAAA,EAAM,OAAO,IAAA,CAAK,CAAC,GAAG,IAAA,CAAK,KAAA,CAAM,IAAI,IAAI,CAAA;AAAA,UACzC,WAAA,EAAa,OAAO,IAAA,CAAK,CAAC,GAAG,WAAA,EAAa,KAAA,CAAM,IAAI,KAAK;AAAA,SAC3D;AACA,QAAA,MAAM,SAAS,MAAM,QAAA,CAAS,KAAK,CAAC,CAAA,GAAI,MAAa,GAAG,CAAA;AAExD,QAAA,GAAA,CAAI,IAAA,CAAK,UAAA,GAAa,MAAA,CAAO,IAAA,CAAK,CAAC,GAAG,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,GAAI,MAAM,CAAA;AAAA,MACvE,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,GAAG,CAAA;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AAEA,EAAA,OAAO,MAAA;AACT;;;AC9EO,IAAM,YAAA,GAAe,CAAyB,MAAA,KACnD","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 if (routes.GET[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.GET[path]?.response.parse(json) : 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 if (routes.POST[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.POST[path]?.response.parse(json) : 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 validation?: boolean\n }\n) => {\n const { validation = true, schemaFilePath } = handlers\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\n res.json(validation ? routes.GET[x]?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(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\n res.json(validation ? routes.POST[x]?.response.parse(result) : result)\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"]}
|
|
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 {\n ExtractRouteParams,\n InferRouteConfig,\n 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 RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\">, K>\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\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 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 finalPath = replacePathParams(path, options.params ?? {})\n\n const response = await customFetch(`${baseUrl}${finalPath}${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 if (routes.GET[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.GET[path]?.response.parse(json) : 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 finalPath = replacePathParams(path, options.params ?? {})\n\n const response = await customFetch(`${baseUrl}${finalPath}${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 if (routes.POST[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.POST[path]?.response.parse(json) : json\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type {\n ExtractRouteParams,\n InferRouteConfig,\n 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 RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: HandlerData<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: HandlerData<InferRouteConfig<T[\"POST\"][K]>, K>,\n ctx: TContext\n ) => MaybePromise<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 validation?: boolean\n }\n) => {\n const { validation = false, schemaFilePath } = handlers\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\n res.json(validation ? routes.GET[x]?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(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\n res.json(validation ? routes.POST[x]?.response.parse(result) : result)\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 => 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
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
|
+
var replacePathParams = (path, params) => {
|
|
5
|
+
const paramNames = /* @__PURE__ */ new Set();
|
|
6
|
+
const paramPattern = /:([^/]+)/g;
|
|
7
|
+
let match;
|
|
8
|
+
while ((match = paramPattern.exec(path)) !== null) {
|
|
9
|
+
paramNames.add(match[1]);
|
|
10
|
+
}
|
|
11
|
+
for (const paramName of paramNames) {
|
|
12
|
+
if (!(paramName in params)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Missing required parameter: "${paramName}" for path "${path}"`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return path.replace(/:([^/]+)/g, (_, paramName) => {
|
|
19
|
+
return String(params[paramName]);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
4
22
|
var createClient = (routes, options) => {
|
|
5
23
|
const {
|
|
6
24
|
baseUrl,
|
|
@@ -17,7 +35,8 @@ var createClient = (routes, options) => {
|
|
|
17
35
|
routes.GET[path]?.queryParams?.parse(options2.queryParams);
|
|
18
36
|
}
|
|
19
37
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
20
|
-
const
|
|
38
|
+
const finalPath = replacePathParams(path, options2.params ?? {});
|
|
39
|
+
const response = await customFetch(`${baseUrl}${finalPath}${queryString}`, {
|
|
21
40
|
method: "GET",
|
|
22
41
|
headers: {
|
|
23
42
|
"Content-Type": "application/json",
|
|
@@ -48,7 +67,8 @@ var createClient = (routes, options) => {
|
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
const queryString = options2?.queryParams ? "?" + new URLSearchParams(options2.queryParams).toString() : "";
|
|
51
|
-
const
|
|
70
|
+
const finalPath = replacePathParams(path, options2.params ?? {});
|
|
71
|
+
const response = await customFetch(`${baseUrl}${finalPath}${queryString}`, {
|
|
52
72
|
method: "POST",
|
|
53
73
|
headers: {
|
|
54
74
|
"Content-Type": "application/json",
|
|
@@ -73,7 +93,7 @@ var createClient = (routes, options) => {
|
|
|
73
93
|
return client;
|
|
74
94
|
};
|
|
75
95
|
var registerExpressRoutes = (router, routes, handlers) => {
|
|
76
|
-
const { validation =
|
|
96
|
+
const { validation = false, schemaFilePath } = handlers;
|
|
77
97
|
router = Object.keys(routes.GET).reduce(
|
|
78
98
|
(r, x) => r.get(x, async (req, res, next) => {
|
|
79
99
|
try {
|
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\">, \"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 if (routes.GET[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.GET[path]?.response.parse(json) : 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 if (routes.POST[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.POST[path]?.response.parse(json) : 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 validation?: boolean\n }\n) => {\n const { validation = true, schemaFilePath } = handlers\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\n res.json(validation ? routes.GET[x]?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(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\n res.json(validation ? routes.POST[x]?.response.parse(result) : result)\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"]}
|
|
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 {\n ExtractRouteParams,\n InferRouteConfig,\n 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 RouterConfig> = {\n GET: <K extends keyof T[\"GET\"]>(\n path: K,\n options?: ClientOptions<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, K>\n ) => Promise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n\n POST: <K extends keyof T[\"POST\"]>(\n path: K,\n body: InferRouteConfig<T[\"POST\"][K]>[\"body\"],\n options?: ClientOptions<Omit<InferRouteConfig<T[\"POST\"][K]>, \"body\">, K>\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\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 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 finalPath = replacePathParams(path, options.params ?? {})\n\n const response = await customFetch(`${baseUrl}${finalPath}${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 if (routes.GET[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.GET[path]?.response.parse(json) : 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 finalPath = replacePathParams(path, options.params ?? {})\n\n const response = await customFetch(`${baseUrl}${finalPath}${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 if (routes.POST[path].response.type === \"void\") {\n await response.text()\n return\n }\n\n const json = await response.json()\n\n return validate ? routes.POST[path]?.response.parse(json) : json\n }\n\n return client\n}\n","import type { Request, Router } from \"express\"\nimport { readFile } from \"node:fs/promises\"\nimport type {\n ExtractRouteParams,\n InferRouteConfig,\n 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 RouterConfig, TContext> = {\n GET: {\n [K in keyof T[\"GET\"]]: (\n data: HandlerData<Omit<InferRouteConfig<T[\"GET\"][K]>, \"body\">, K>,\n ctx: TContext\n ) => MaybePromise<InferRouteConfig<T[\"GET\"][K]>[\"response\"]>\n }\n POST: {\n [K in keyof T[\"POST\"]]: (\n data: HandlerData<InferRouteConfig<T[\"POST\"][K]>, K>,\n ctx: TContext\n ) => MaybePromise<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 validation?: boolean\n }\n) => {\n const { validation = false, schemaFilePath } = handlers\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\n res.json(validation ? routes.GET[x]?.response.parse(result) : result)\n } catch (err) {\n next(err)\n }\n }),\n router\n )\n\n if (schemaFilePath) {\n router = router.get(\"/__schema\", async (_, res) =>\n res\n .contentType(\"text/plain\")\n .send(await readFile(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\n res.json(validation ? routes.POST[x]?.response.parse(result) : result)\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 => 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"]}
|