@mandujs/core 0.9.2 → 0.9.4
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.ko.md +200 -200
- package/README.md +200 -200
- package/package.json +52 -52
- package/src/bundler/build.ts +6 -0
- package/src/client/Link.tsx +209 -209
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +2 -1
- package/src/client/router.ts +387 -387
- package/src/client/serialize.ts +404 -404
- package/src/contract/client.test.ts +308 -0
- package/src/contract/client.ts +345 -0
- package/src/contract/handler.ts +270 -0
- package/src/contract/index.ts +137 -1
- package/src/contract/infer.test.ts +346 -0
- package/src/contract/types.ts +83 -0
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +438 -438
- package/src/filling/filling.ts +5 -1
- package/src/filling/index.ts +1 -1
- package/src/generator/index.ts +3 -3
- package/src/index.ts +75 -0
- package/src/report/index.ts +1 -1
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/index.ts +3 -3
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/ssr.ts +321 -321
- package/src/runtime/trace.ts +144 -144
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
package/src/filling/filling.ts
CHANGED
|
@@ -293,7 +293,11 @@ export class ManduFilling<TLoaderData = unknown> {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
/**
|
|
297
|
+
* Mandu Filling factory functions
|
|
298
|
+
* Note: These are also available via the main `Mandu` namespace
|
|
299
|
+
*/
|
|
300
|
+
export const ManduFillingFactory = {
|
|
297
301
|
filling<TLoaderData = unknown>(): ManduFilling<TLoaderData> {
|
|
298
302
|
return new ManduFilling<TLoaderData>();
|
|
299
303
|
},
|
package/src/filling/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
export { ManduContext, ValidationError, CookieManager } from "./context";
|
|
6
6
|
export type { CookieOptions } from "./context";
|
|
7
|
-
export { ManduFilling,
|
|
7
|
+
export { ManduFilling, ManduFillingFactory, LoaderTimeoutError } from "./filling";
|
|
8
8
|
export type { Handler, Guard, HttpMethod, Loader, LoaderOptions } from "./filling";
|
|
9
9
|
|
|
10
10
|
// Auth Guards
|
package/src/generator/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "./generate";
|
|
2
|
-
export * from "./templates";
|
|
3
|
-
export * from "./contract-glue";
|
|
1
|
+
export * from "./generate";
|
|
2
|
+
export * from "./templates";
|
|
3
|
+
export * from "./contract-glue";
|
package/src/index.ts
CHANGED
|
@@ -12,3 +12,78 @@ export * from "./contract";
|
|
|
12
12
|
export * from "./openapi";
|
|
13
13
|
export * from "./brain";
|
|
14
14
|
export * from "./watcher";
|
|
15
|
+
|
|
16
|
+
// Consolidated Mandu namespace
|
|
17
|
+
import { ManduFilling, ManduContext, ManduFillingFactory } from "./filling";
|
|
18
|
+
import { createContract, defineHandler, defineRoute, createClient, contractFetch } from "./contract";
|
|
19
|
+
import type { ContractDefinition, ContractInstance, ContractSchema } from "./contract";
|
|
20
|
+
import type { ContractHandlers, ClientOptions } from "./contract";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mandu - Unified Namespace
|
|
24
|
+
*
|
|
25
|
+
* 통합된 Mandu API 인터페이스
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { Mandu } from "@mandujs/core";
|
|
30
|
+
* import { z } from "zod";
|
|
31
|
+
*
|
|
32
|
+
* // Filling (Handler) API
|
|
33
|
+
* export default Mandu.filling()
|
|
34
|
+
* .get(async (ctx) => ctx.json({ message: "Hello" }));
|
|
35
|
+
*
|
|
36
|
+
* // Contract API
|
|
37
|
+
* const contract = Mandu.contract({
|
|
38
|
+
* request: { GET: { query: z.object({ id: z.string() }) } },
|
|
39
|
+
* response: { 200: z.object({ data: z.string() }) },
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Handler API (with type inference)
|
|
43
|
+
* const handlers = Mandu.handler(contract, {
|
|
44
|
+
* GET: (ctx) => ({ data: ctx.query.id }),
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Client API (type-safe fetch)
|
|
48
|
+
* const client = Mandu.client(contract, { baseUrl: "/api" });
|
|
49
|
+
* const result = await client.GET({ query: { id: "123" } });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const Mandu = {
|
|
53
|
+
// === Filling (Handler) API ===
|
|
54
|
+
/**
|
|
55
|
+
* Create a new filling (handler chain)
|
|
56
|
+
*/
|
|
57
|
+
filling: ManduFillingFactory.filling,
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a ManduContext from a Request
|
|
61
|
+
*/
|
|
62
|
+
context: ManduFillingFactory.context,
|
|
63
|
+
|
|
64
|
+
// === Contract API ===
|
|
65
|
+
/**
|
|
66
|
+
* Define a typed API contract
|
|
67
|
+
*/
|
|
68
|
+
contract: createContract,
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create typed handlers for a contract
|
|
72
|
+
*/
|
|
73
|
+
handler: defineHandler,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Define a complete route (contract + handler)
|
|
77
|
+
*/
|
|
78
|
+
route: defineRoute,
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a type-safe API client
|
|
82
|
+
*/
|
|
83
|
+
client: createClient,
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Make a type-safe fetch call
|
|
87
|
+
*/
|
|
88
|
+
fetch: contractFetch,
|
|
89
|
+
} as const;
|
package/src/report/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./build";
|
|
1
|
+
export * from "./build";
|
package/src/runtime/compose.ts
CHANGED
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu Middleware Compose 🔗
|
|
3
|
-
* Hono 스타일 미들웨어 조합 패턴
|
|
4
|
-
*
|
|
5
|
-
* @see https://github.com/honojs/hono/blob/main/src/compose.ts
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { ManduContext } from "../filling/context";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Next 함수 타입
|
|
12
|
-
*/
|
|
13
|
-
export type Next = () => Promise<void>;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 미들웨어 함수 타입
|
|
17
|
-
* - Response 반환: 체인 중단 (Guard 역할)
|
|
18
|
-
* - void 반환: 다음 미들웨어 실행
|
|
19
|
-
*/
|
|
20
|
-
export type Middleware = (
|
|
21
|
-
ctx: ManduContext,
|
|
22
|
-
next: Next
|
|
23
|
-
) => Response | void | Promise<Response | void>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 에러 핸들러 타입
|
|
27
|
-
*/
|
|
28
|
-
export type ErrorHandler = (
|
|
29
|
-
error: Error,
|
|
30
|
-
ctx: ManduContext
|
|
31
|
-
) => Response | Promise<Response>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* NotFound 핸들러 타입
|
|
35
|
-
*/
|
|
36
|
-
export type NotFoundHandler = (ctx: ManduContext) => Response | Promise<Response>;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 미들웨어 엔트리 (메타데이터 포함)
|
|
40
|
-
*/
|
|
41
|
-
export interface MiddlewareEntry {
|
|
42
|
-
fn: Middleware;
|
|
43
|
-
name?: string;
|
|
44
|
-
isAsync?: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Compose 옵션
|
|
49
|
-
*/
|
|
50
|
-
export interface ComposeOptions {
|
|
51
|
-
onError?: ErrorHandler;
|
|
52
|
-
onNotFound?: NotFoundHandler;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 미들웨어 함수들을 하나의 실행 함수로 조합
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const middleware = [
|
|
61
|
-
* { fn: async (ctx, next) => { console.log('before'); await next(); console.log('after'); } },
|
|
62
|
-
* { fn: async (ctx, next) => { return ctx.ok({ data: 'hello' }); } },
|
|
63
|
-
* ];
|
|
64
|
-
*
|
|
65
|
-
* const handler = compose(middleware, {
|
|
66
|
-
* onError: (err, ctx) => ctx.json({ error: err.message }, 500),
|
|
67
|
-
* onNotFound: (ctx) => ctx.notFound(),
|
|
68
|
-
* });
|
|
69
|
-
*
|
|
70
|
-
* const response = await handler(context);
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export function compose(
|
|
74
|
-
middleware: MiddlewareEntry[],
|
|
75
|
-
options: ComposeOptions = {}
|
|
76
|
-
): (ctx: ManduContext) => Promise<Response> {
|
|
77
|
-
const { onError, onNotFound } = options;
|
|
78
|
-
|
|
79
|
-
return async (ctx: ManduContext): Promise<Response> => {
|
|
80
|
-
let index = -1;
|
|
81
|
-
let finalResponse: Response | undefined;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 미들웨어 순차 실행
|
|
85
|
-
* @param i 현재 인덱스
|
|
86
|
-
*/
|
|
87
|
-
async function dispatch(i: number): Promise<void> {
|
|
88
|
-
// next() 이중 호출 방지
|
|
89
|
-
if (i <= index) {
|
|
90
|
-
throw new Error("next() called multiple times");
|
|
91
|
-
}
|
|
92
|
-
index = i;
|
|
93
|
-
|
|
94
|
-
const entry = middleware[i];
|
|
95
|
-
|
|
96
|
-
if (!entry) {
|
|
97
|
-
// 모든 미들웨어 통과 후 핸들러 없음
|
|
98
|
-
if (!finalResponse && onNotFound) {
|
|
99
|
-
finalResponse = await onNotFound(ctx);
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const result = await entry.fn(ctx, () => dispatch(i + 1));
|
|
106
|
-
|
|
107
|
-
// Response 반환 시 체인 중단
|
|
108
|
-
if (result instanceof Response) {
|
|
109
|
-
finalResponse = result;
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (err instanceof Error && onError) {
|
|
114
|
-
finalResponse = await onError(err, ctx);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
await dispatch(0);
|
|
122
|
-
|
|
123
|
-
// 응답이 없으면 404
|
|
124
|
-
if (!finalResponse) {
|
|
125
|
-
if (onNotFound) {
|
|
126
|
-
finalResponse = await onNotFound(ctx);
|
|
127
|
-
} else {
|
|
128
|
-
finalResponse = new Response("Not Found", { status: 404 });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return finalResponse;
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 미들웨어 배열 생성 헬퍼
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```typescript
|
|
141
|
-
* const mw = createMiddleware([
|
|
142
|
-
* authGuard,
|
|
143
|
-
* rateLimitGuard,
|
|
144
|
-
* mainHandler,
|
|
145
|
-
* ]);
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
export function createMiddleware(
|
|
149
|
-
fns: Middleware[]
|
|
150
|
-
): MiddlewareEntry[] {
|
|
151
|
-
return fns.map((fn, i) => ({
|
|
152
|
-
fn,
|
|
153
|
-
name: fn.name || `middleware_${i}`,
|
|
154
|
-
isAsync: fn.constructor.name === "AsyncFunction",
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* 미들웨어 체인 빌더
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```typescript
|
|
163
|
-
* const chain = new MiddlewareChain()
|
|
164
|
-
* .use(authGuard)
|
|
165
|
-
* .use(rateLimitGuard)
|
|
166
|
-
* .use(mainHandler)
|
|
167
|
-
* .onError((err, ctx) => ctx.json({ error: err.message }, 500))
|
|
168
|
-
* .build();
|
|
169
|
-
*
|
|
170
|
-
* const response = await chain(ctx);
|
|
171
|
-
* ```
|
|
172
|
-
*/
|
|
173
|
-
export class MiddlewareChain {
|
|
174
|
-
private middleware: MiddlewareEntry[] = [];
|
|
175
|
-
private errorHandler?: ErrorHandler;
|
|
176
|
-
private notFoundHandler?: NotFoundHandler;
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 미들웨어 추가
|
|
180
|
-
*/
|
|
181
|
-
use(fn: Middleware, name?: string): this {
|
|
182
|
-
this.middleware.push({
|
|
183
|
-
fn,
|
|
184
|
-
name: name || fn.name || `middleware_${this.middleware.length}`,
|
|
185
|
-
isAsync: fn.constructor.name === "AsyncFunction",
|
|
186
|
-
});
|
|
187
|
-
return this;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* 에러 핸들러 설정
|
|
192
|
-
*/
|
|
193
|
-
onError(handler: ErrorHandler): this {
|
|
194
|
-
this.errorHandler = handler;
|
|
195
|
-
return this;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* NotFound 핸들러 설정
|
|
200
|
-
*/
|
|
201
|
-
onNotFound(handler: NotFoundHandler): this {
|
|
202
|
-
this.notFoundHandler = handler;
|
|
203
|
-
return this;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 미들웨어 체인 빌드
|
|
208
|
-
*/
|
|
209
|
-
build(): (ctx: ManduContext) => Promise<Response> {
|
|
210
|
-
return compose(this.middleware, {
|
|
211
|
-
onError: this.errorHandler,
|
|
212
|
-
onNotFound: this.notFoundHandler,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* 미들웨어 목록 조회
|
|
218
|
-
*/
|
|
219
|
-
getMiddleware(): MiddlewareEntry[] {
|
|
220
|
-
return [...this.middleware];
|
|
221
|
-
}
|
|
222
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Middleware Compose 🔗
|
|
3
|
+
* Hono 스타일 미들웨어 조합 패턴
|
|
4
|
+
*
|
|
5
|
+
* @see https://github.com/honojs/hono/blob/main/src/compose.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ManduContext } from "../filling/context";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Next 함수 타입
|
|
12
|
+
*/
|
|
13
|
+
export type Next = () => Promise<void>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 미들웨어 함수 타입
|
|
17
|
+
* - Response 반환: 체인 중단 (Guard 역할)
|
|
18
|
+
* - void 반환: 다음 미들웨어 실행
|
|
19
|
+
*/
|
|
20
|
+
export type Middleware = (
|
|
21
|
+
ctx: ManduContext,
|
|
22
|
+
next: Next
|
|
23
|
+
) => Response | void | Promise<Response | void>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 에러 핸들러 타입
|
|
27
|
+
*/
|
|
28
|
+
export type ErrorHandler = (
|
|
29
|
+
error: Error,
|
|
30
|
+
ctx: ManduContext
|
|
31
|
+
) => Response | Promise<Response>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* NotFound 핸들러 타입
|
|
35
|
+
*/
|
|
36
|
+
export type NotFoundHandler = (ctx: ManduContext) => Response | Promise<Response>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 미들웨어 엔트리 (메타데이터 포함)
|
|
40
|
+
*/
|
|
41
|
+
export interface MiddlewareEntry {
|
|
42
|
+
fn: Middleware;
|
|
43
|
+
name?: string;
|
|
44
|
+
isAsync?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Compose 옵션
|
|
49
|
+
*/
|
|
50
|
+
export interface ComposeOptions {
|
|
51
|
+
onError?: ErrorHandler;
|
|
52
|
+
onNotFound?: NotFoundHandler;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 미들웨어 함수들을 하나의 실행 함수로 조합
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const middleware = [
|
|
61
|
+
* { fn: async (ctx, next) => { console.log('before'); await next(); console.log('after'); } },
|
|
62
|
+
* { fn: async (ctx, next) => { return ctx.ok({ data: 'hello' }); } },
|
|
63
|
+
* ];
|
|
64
|
+
*
|
|
65
|
+
* const handler = compose(middleware, {
|
|
66
|
+
* onError: (err, ctx) => ctx.json({ error: err.message }, 500),
|
|
67
|
+
* onNotFound: (ctx) => ctx.notFound(),
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* const response = await handler(context);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function compose(
|
|
74
|
+
middleware: MiddlewareEntry[],
|
|
75
|
+
options: ComposeOptions = {}
|
|
76
|
+
): (ctx: ManduContext) => Promise<Response> {
|
|
77
|
+
const { onError, onNotFound } = options;
|
|
78
|
+
|
|
79
|
+
return async (ctx: ManduContext): Promise<Response> => {
|
|
80
|
+
let index = -1;
|
|
81
|
+
let finalResponse: Response | undefined;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 미들웨어 순차 실행
|
|
85
|
+
* @param i 현재 인덱스
|
|
86
|
+
*/
|
|
87
|
+
async function dispatch(i: number): Promise<void> {
|
|
88
|
+
// next() 이중 호출 방지
|
|
89
|
+
if (i <= index) {
|
|
90
|
+
throw new Error("next() called multiple times");
|
|
91
|
+
}
|
|
92
|
+
index = i;
|
|
93
|
+
|
|
94
|
+
const entry = middleware[i];
|
|
95
|
+
|
|
96
|
+
if (!entry) {
|
|
97
|
+
// 모든 미들웨어 통과 후 핸들러 없음
|
|
98
|
+
if (!finalResponse && onNotFound) {
|
|
99
|
+
finalResponse = await onNotFound(ctx);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const result = await entry.fn(ctx, () => dispatch(i + 1));
|
|
106
|
+
|
|
107
|
+
// Response 반환 시 체인 중단
|
|
108
|
+
if (result instanceof Response) {
|
|
109
|
+
finalResponse = result;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof Error && onError) {
|
|
114
|
+
finalResponse = await onError(err, ctx);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await dispatch(0);
|
|
122
|
+
|
|
123
|
+
// 응답이 없으면 404
|
|
124
|
+
if (!finalResponse) {
|
|
125
|
+
if (onNotFound) {
|
|
126
|
+
finalResponse = await onNotFound(ctx);
|
|
127
|
+
} else {
|
|
128
|
+
finalResponse = new Response("Not Found", { status: 404 });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return finalResponse;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 미들웨어 배열 생성 헬퍼
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const mw = createMiddleware([
|
|
142
|
+
* authGuard,
|
|
143
|
+
* rateLimitGuard,
|
|
144
|
+
* mainHandler,
|
|
145
|
+
* ]);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function createMiddleware(
|
|
149
|
+
fns: Middleware[]
|
|
150
|
+
): MiddlewareEntry[] {
|
|
151
|
+
return fns.map((fn, i) => ({
|
|
152
|
+
fn,
|
|
153
|
+
name: fn.name || `middleware_${i}`,
|
|
154
|
+
isAsync: fn.constructor.name === "AsyncFunction",
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 미들웨어 체인 빌더
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const chain = new MiddlewareChain()
|
|
164
|
+
* .use(authGuard)
|
|
165
|
+
* .use(rateLimitGuard)
|
|
166
|
+
* .use(mainHandler)
|
|
167
|
+
* .onError((err, ctx) => ctx.json({ error: err.message }, 500))
|
|
168
|
+
* .build();
|
|
169
|
+
*
|
|
170
|
+
* const response = await chain(ctx);
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export class MiddlewareChain {
|
|
174
|
+
private middleware: MiddlewareEntry[] = [];
|
|
175
|
+
private errorHandler?: ErrorHandler;
|
|
176
|
+
private notFoundHandler?: NotFoundHandler;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 미들웨어 추가
|
|
180
|
+
*/
|
|
181
|
+
use(fn: Middleware, name?: string): this {
|
|
182
|
+
this.middleware.push({
|
|
183
|
+
fn,
|
|
184
|
+
name: name || fn.name || `middleware_${this.middleware.length}`,
|
|
185
|
+
isAsync: fn.constructor.name === "AsyncFunction",
|
|
186
|
+
});
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 에러 핸들러 설정
|
|
192
|
+
*/
|
|
193
|
+
onError(handler: ErrorHandler): this {
|
|
194
|
+
this.errorHandler = handler;
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* NotFound 핸들러 설정
|
|
200
|
+
*/
|
|
201
|
+
onNotFound(handler: NotFoundHandler): this {
|
|
202
|
+
this.notFoundHandler = handler;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 미들웨어 체인 빌드
|
|
208
|
+
*/
|
|
209
|
+
build(): (ctx: ManduContext) => Promise<Response> {
|
|
210
|
+
return compose(this.middleware, {
|
|
211
|
+
onError: this.errorHandler,
|
|
212
|
+
onNotFound: this.notFoundHandler,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 미들웨어 목록 조회
|
|
218
|
+
*/
|
|
219
|
+
getMiddleware(): MiddlewareEntry[] {
|
|
220
|
+
return [...this.middleware];
|
|
221
|
+
}
|
|
222
|
+
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -3,6 +3,6 @@ export * from "./router";
|
|
|
3
3
|
export * from "./server";
|
|
4
4
|
export * from "./cors";
|
|
5
5
|
export * from "./env";
|
|
6
|
-
export * from "./compose";
|
|
7
|
-
export * from "./lifecycle";
|
|
8
|
-
export * from "./trace";
|
|
6
|
+
export * from "./compose";
|
|
7
|
+
export * from "./lifecycle";
|
|
8
|
+
export * from "./trace";
|