Skip to content

Commit cb9b00f

Browse files
committed
feat: Add module test file
1 parent a542b3e commit cb9b00f

File tree

9 files changed

+168
-83
lines changed

9 files changed

+168
-83
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { createModuleApi } from '@/api/lib/module';
2+
import { test } from '@/test/module';
23
import { OpenAPIHono } from '@hono/zod-openapi';
34

45
import { middlewareRoute } from './route';
56

67
export const middlewareModule = createModuleApi({
78
name: 'middleware',
89
plugin: 'core',
9-
routes: new OpenAPIHono().route('/', middlewareRoute),
10+
routes: new OpenAPIHono()
11+
.route('/', middlewareRoute)
12+
.route('/testNew', test.hono),
1013
});
1114

1215
export type MiddlewareTypes = typeof middlewareModule;
Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,8 @@
11
import { OpenAPIHono } from '@hono/zod-openapi';
2+
import { Env, Hono, Schema } from 'hono';
23
import { ClientRequest, hc } from 'hono/client';
3-
import { UnionToIntersection } from 'hono/utils/types';
4-
import { cookies, headers } from 'next/headers';
5-
6-
import { CONFIG } from './config';
7-
import { Env, Schema } from 'hono';
84
import { HonoBase } from 'hono/hono-base';
9-
10-
/**
11-
* Maps an API path string to a nested object structure for type-safe client usage.
12-
* Example: '/foo/bar' -> { foo: { bar: ... } }
13-
* This is intentionally shallow to avoid deep recursion for TS perf.
14-
*/
15-
type PathToChain<
16-
Path extends string,
17-
E extends Schema,
18-
Orig extends string = Path,
19-
> = Path extends `/${infer P}`
20-
? PathToChain<P, E, Path>
21-
: Path extends `${infer P}/${infer R}`
22-
? { [K in P]: PathToChain<R, E, Orig> }
23-
: Record<
24-
Path extends '' ? 'index' : Path,
25-
ClientRequest<E extends Record<string, unknown> ? E[Orig] : never>
26-
>;
27-
28-
/**
29-
* Type-safe client for a Hono API schema.
30-
*/
31-
export type Client<T> =
32-
T extends HonoBase<Env, infer S, string>
33-
? S extends Record<infer K, Schema>
34-
? K extends string
35-
? PathToChain<K, S>
36-
: never
37-
: never
38-
: never;
5+
import { UnionToIntersection } from 'hono/utils/types';
396

407
/**
418
* Create a type-safe client for a VitNode API module.
@@ -51,31 +18,4 @@ export async function fetcherNew<T extends OpenAPIHono>({
5118
module: string;
5219
options?: Omit<RequestInit, 'body'>;
5320
plugin: string;
54-
}): Promise<UnionToIntersection<Client<T>>> {
55-
const url = new URL(`/api/${plugin}/${module}`, CONFIG.backend.origin);
56-
const [nextInternalHeaders, cookie] = await Promise.all([
57-
headers(),
58-
cookies(),
59-
]);
60-
61-
const client = hc<T>(url.href, {
62-
fetch: async (input, requestInit) => {
63-
const headers = new Headers({
64-
'Content-Type': 'application/json',
65-
Cookie: cookie.toString(),
66-
['user-agent']: nextInternalHeaders.get('user-agent') ?? 'node',
67-
['x-forwarded-for']:
68-
nextInternalHeaders.get('x-forwarded-for') ?? '0.0.0.0',
69-
...options?.headers,
70-
});
71-
72-
return await fetch(input, {
73-
...requestInit,
74-
...options,
75-
headers,
76-
});
77-
},
78-
});
79-
80-
return client as unknown as UnionToIntersection<Client<T>>;
81-
}
21+
}) {}

packages/vitnode/src/lib/test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Env, Hono, Schema } from 'hono';
2+
import { HonoBase } from 'hono/hono-base';
3+
4+
type Client<T> =
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
T extends HonoBase<any, infer S, any>
7+
? S extends Record<infer K, Schema>
8+
? K extends string
9+
? string
10+
: never
11+
: never
12+
: never;
13+
14+
export function fetcher<T extends Hono<Env, Schema, string>>(): Client<T> {
15+
// const client = hc('https://example.com');
16+
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18+
return '' as any as Client<T>;
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// type PathToChain<
2+
// Path extends string,
3+
// E extends Schema,
4+
// Original extends string = Path,
5+
// > = Path extends `/${infer P}`
6+
// ? PathToChain<P, E, Original> // Pass Original consistently
7+
// : Path extends `${infer P}/${infer R}`
8+
// ? { [K in P]: PathToChain<R, E, Original> }
9+
// : Record<
10+
// Path extends '' ? 'index' : Path,
11+
// ClientRequest<E extends Record<string, unknown> ? E[Original] : never>
12+
// >;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
createRoute,
3+
OpenAPIHono,
4+
RouteConfig,
5+
RouteHandler,
6+
z,
7+
} from '@hono/zod-openapi';
8+
9+
export const withHandler = <R extends RouteConfig, H extends RouteHandler<R>>({
10+
route,
11+
handler,
12+
}: {
13+
handler: H;
14+
route: R;
15+
}) => ({ route, handler });
16+
17+
type Route<
18+
R extends RouteConfig = RouteConfig,
19+
H extends RouteHandler<R> = RouteHandler<R>,
20+
> = readonly { handler: H; route: R }[];
21+
22+
export interface BuildModuleType<T extends Route, Plugin extends string> {
23+
plugin: Plugin;
24+
routes: T;
25+
}
26+
27+
export function buildModule<
28+
const Routes extends Route,
29+
const P extends string,
30+
>({ routes, plugin }: { plugin: P; routes: Routes }) {
31+
const hono = new OpenAPIHono();
32+
33+
routes.forEach(({ handler, route }) => {
34+
hono.openapi(route, handler);
35+
});
36+
37+
return { routes, plugin, hono };
38+
}
39+
40+
export const test = buildModule({
41+
plugin: 'test_plugin',
42+
routes: [
43+
withHandler({
44+
route: createRoute({
45+
path: '/test34',
46+
method: 'get',
47+
responses: {
48+
200: {
49+
description: 'Success',
50+
content: {
51+
'application/json': {
52+
schema: z.object({ message: z.string() }),
53+
},
54+
},
55+
},
56+
},
57+
}),
58+
handler: c => c.json({ message: 'Success' }),
59+
}),
60+
withHandler({
61+
route: createRoute({
62+
path: '/test2',
63+
method: 'post',
64+
responses: {
65+
200: {
66+
description: 'Success 2',
67+
content: {
68+
'application/json': {
69+
schema: z.object({
70+
message: z.string(),
71+
}),
72+
},
73+
},
74+
},
75+
},
76+
}),
77+
handler: c => {
78+
return c.json({
79+
message: `Hello from ${c.req.path}`,
80+
});
81+
},
82+
}),
83+
],
84+
});
85+
86+
type Test = typeof test;
87+
88+
type FetcherParams<
89+
T extends { plugin: string; routes: Route },
90+
R extends T['routes'][number],
91+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
92+
> = R extends any
93+
? Pick<R['route'], 'method' | 'path'> & { plugin: T['plugin'] }
94+
: never;
95+
96+
function fetcher<T extends { plugin: string; routes: Route }>(
97+
params: FetcherParams<T, T['routes'][number]>,
98+
) {
99+
const { path, method, plugin } = params;
100+
}
101+
102+
export const testFetcher = () => {
103+
fetcher<Test>({ path: '/test34', method: 'get', plugin: 'test_plugin' });
104+
fetcher<Test>({ path: '/test2', method: 'post', plugin: 'test_plugin' });
105+
};
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { OpenAPIHono } from '@hono/zod-openapi';
22

3-
import { listCategories } from './routes/list.route';
3+
import { categoriesRoute } from './route';
44

5-
export const categoriesModule = new OpenAPIHono().route('/', listCategories);
5+
export const categoriesModule = new OpenAPIHono()
6+
.route('/categories', categoriesRoute)
7+
.route('/test', categoriesRoute);
8+
9+
export type CategoriesTypes = typeof categoriesModule;

plugins/blog/src/modules/categories/routes/list.route.ts renamed to plugins/blog/src/modules/categories/route.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import { OpenAPIHono } from '@hono/zod-openapi';
2-
import { z } from 'zod';
1+
import { OpenAPIHono, z } from '@hono/zod-openapi';
2+
import { createApiRoute } from 'vitnode/api/lib/route';
33

4-
export const listCategories = new OpenAPIHono().openapi(
5-
{
4+
export const categoriesRoute = new OpenAPIHono().openapi(
5+
createApiRoute({
66
method: 'get',
7-
description: 'Get categories',
87
path: '/',
8+
pluginConfig: {
9+
id: 'blog',
10+
name: 'Blog',
11+
},
912
responses: {
1013
200: {
1114
content: {
@@ -18,7 +21,7 @@ export const listCategories = new OpenAPIHono().openapi(
1821
description: 'Test',
1922
},
2023
},
21-
},
24+
}),
2225
c => {
2326
return c.json({
2427
test: 'test',
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { fetcher } from 'vitnode/lib/test';
2+
3+
import { CategoriesTypes } from './categories.module';
4+
5+
export const test = async () => {
6+
const client = fetcher<CategoriesTypes>();
7+
const response = await client.categories.$get();
8+
9+
return response;
10+
};

plugins/blog/src/test.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +0,0 @@
1-
import { fetcherNew } from 'vitnode/lib/fetcher-new';
2-
3-
export const test = async () => {
4-
const client = await fetcherNew({
5-
plugin: 'core',
6-
module: 'middleware',
7-
options: {
8-
cache: 'force-cache',
9-
},
10-
});
11-
};

0 commit comments

Comments
 (0)