@mandujs/core 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +4 -4
- package/README.md +653 -653
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +9 -0
- package/src/config/validate.ts +12 -0
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +46 -46
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +24 -1
- package/src/filling/deps.ts +238 -238
- package/src/filling/index.ts +2 -0
- package/src/filling/sse.test.ts +168 -0
- package/src/filling/sse.ts +162 -0
- package/src/generator/index.ts +3 -3
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -291
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/types.ts +348 -348
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +6 -1
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-scanner.ts +497 -497
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/escape.ts +44 -0
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +257 -0
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +16 -21
- package/src/runtime/streaming-ssr.ts +24 -33
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
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
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML 속성값 이스케이프
|
|
3
|
+
* XSS 방지를 위해 HTML 속성값에 들어갈 문자열을 안전하게 처리
|
|
4
|
+
*/
|
|
5
|
+
export function escapeHtmlAttr(value: string): string {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/&/g, "&")
|
|
8
|
+
.replace(/</g, "<")
|
|
9
|
+
.replace(/>/g, ">")
|
|
10
|
+
.replace(/"/g, """)
|
|
11
|
+
.replace(/'/g, "'");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Inline script의 JSON 데이터 이스케이프
|
|
16
|
+
* <script> 태그 내부의 JSON을 안전하게 처리
|
|
17
|
+
*/
|
|
18
|
+
export function escapeJsonForInlineScript(value: string): string {
|
|
19
|
+
return value
|
|
20
|
+
.replace(/</g, "\\u003c")
|
|
21
|
+
.replace(/>/g, "\\u003e")
|
|
22
|
+
.replace(/&/g, "\\u0026")
|
|
23
|
+
.replace(/'/g, "\\u0027")
|
|
24
|
+
.replace(/\u2028/g, "\\u2028")
|
|
25
|
+
.replace(/\u2029/g, "\\u2029");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* JavaScript 문자열 리터럴 이스케이프
|
|
30
|
+
* JS 코드 내부의 문자열 보간에 사용
|
|
31
|
+
*/
|
|
32
|
+
export function escapeJsString(value: string): string {
|
|
33
|
+
return value
|
|
34
|
+
.replace(/\\/g, "\\\\")
|
|
35
|
+
.replace(/\n/g, "\\n")
|
|
36
|
+
.replace(/\r/g, "\\r")
|
|
37
|
+
.replace(/"/g, "\\u0022")
|
|
38
|
+
.replace(/'/g, "\\u0027")
|
|
39
|
+
.replace(/</g, "\\u003c")
|
|
40
|
+
.replace(/>/g, "\\u003e")
|
|
41
|
+
.replace(/&/g, "\\u0026")
|
|
42
|
+
.replace(/\u2028/g, "\\u2028")
|
|
43
|
+
.replace(/\u2029/g, "\\u2029");
|
|
44
|
+
}
|