@mandujs/core 0.19.0 → 0.19.2

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.
Files changed (90) hide show
  1. package/README.ko.md +0 -14
  2. package/package.json +4 -1
  3. package/src/brain/architecture/analyzer.ts +4 -4
  4. package/src/brain/doctor/analyzer.ts +18 -14
  5. package/src/bundler/build.test.ts +127 -0
  6. package/src/bundler/build.ts +291 -113
  7. package/src/bundler/css.ts +20 -5
  8. package/src/bundler/dev.ts +55 -2
  9. package/src/bundler/prerender.ts +195 -0
  10. package/src/change/snapshot.ts +4 -23
  11. package/src/change/types.ts +2 -3
  12. package/src/client/Form.tsx +105 -0
  13. package/src/client/__tests__/use-sse.test.ts +153 -0
  14. package/src/client/hooks.ts +105 -6
  15. package/src/client/index.ts +35 -6
  16. package/src/client/router.ts +670 -433
  17. package/src/client/rpc.ts +140 -0
  18. package/src/client/runtime.ts +24 -21
  19. package/src/client/use-fetch.ts +239 -0
  20. package/src/client/use-head.ts +197 -0
  21. package/src/client/use-sse.ts +378 -0
  22. package/src/components/Image.tsx +162 -0
  23. package/src/config/mandu.ts +5 -0
  24. package/src/config/validate.ts +34 -0
  25. package/src/content/index.ts +5 -1
  26. package/src/devtools/client/catchers/error-catcher.ts +17 -0
  27. package/src/devtools/client/catchers/network-proxy.ts +390 -367
  28. package/src/devtools/client/components/kitchen-root.tsx +479 -467
  29. package/src/devtools/client/components/panel/diff-viewer.tsx +219 -0
  30. package/src/devtools/client/components/panel/guard-panel.tsx +374 -244
  31. package/src/devtools/client/components/panel/index.ts +45 -32
  32. package/src/devtools/client/components/panel/panel-container.tsx +332 -312
  33. package/src/devtools/client/components/panel/preview-panel.tsx +188 -0
  34. package/src/devtools/client/state-manager.ts +535 -478
  35. package/src/devtools/design-tokens.ts +265 -264
  36. package/src/devtools/types.ts +345 -319
  37. package/src/filling/filling.ts +336 -14
  38. package/src/filling/index.ts +5 -1
  39. package/src/filling/session.ts +216 -0
  40. package/src/filling/ws.ts +78 -0
  41. package/src/generator/generate.ts +2 -2
  42. package/src/guard/auto-correct.ts +0 -29
  43. package/src/guard/check.ts +14 -31
  44. package/src/guard/presets/index.ts +296 -294
  45. package/src/guard/rules.ts +15 -19
  46. package/src/guard/validator.ts +834 -834
  47. package/src/index.ts +5 -1
  48. package/src/island/index.ts +373 -304
  49. package/src/kitchen/api/contract-api.ts +225 -0
  50. package/src/kitchen/api/diff-parser.ts +108 -0
  51. package/src/kitchen/api/file-api.ts +273 -0
  52. package/src/kitchen/api/guard-api.ts +83 -0
  53. package/src/kitchen/api/guard-decisions.ts +100 -0
  54. package/src/kitchen/api/routes-api.ts +50 -0
  55. package/src/kitchen/index.ts +21 -0
  56. package/src/kitchen/kitchen-handler.ts +256 -0
  57. package/src/kitchen/kitchen-ui.ts +1732 -0
  58. package/src/kitchen/stream/activity-sse.ts +145 -0
  59. package/src/kitchen/stream/file-tailer.ts +99 -0
  60. package/src/middleware/compress.ts +62 -0
  61. package/src/middleware/cors.ts +47 -0
  62. package/src/middleware/index.ts +10 -0
  63. package/src/middleware/jwt.ts +134 -0
  64. package/src/middleware/logger.ts +58 -0
  65. package/src/middleware/timeout.ts +55 -0
  66. package/src/paths.ts +0 -4
  67. package/src/plugins/hooks.ts +64 -0
  68. package/src/plugins/index.ts +3 -0
  69. package/src/plugins/types.ts +5 -0
  70. package/src/report/build.ts +0 -6
  71. package/src/resource/__tests__/backward-compat.test.ts +0 -1
  72. package/src/router/fs-patterns.ts +11 -1
  73. package/src/router/fs-routes.ts +78 -14
  74. package/src/router/fs-scanner.ts +2 -2
  75. package/src/router/fs-types.ts +2 -1
  76. package/src/runtime/adapter-bun.ts +62 -0
  77. package/src/runtime/adapter.ts +47 -0
  78. package/src/runtime/cache.ts +310 -0
  79. package/src/runtime/handler.ts +65 -0
  80. package/src/runtime/image-handler.ts +195 -0
  81. package/src/runtime/index.ts +12 -0
  82. package/src/runtime/middleware.ts +263 -0
  83. package/src/runtime/server.ts +662 -83
  84. package/src/runtime/ssr.ts +55 -29
  85. package/src/runtime/streaming-ssr.ts +106 -82
  86. package/src/spec/index.ts +0 -1
  87. package/src/spec/schema.ts +1 -0
  88. package/src/testing/index.ts +144 -0
  89. package/src/watcher/watcher.ts +27 -1
  90. package/src/spec/lock.ts +0 -56
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Mandu Global Middleware
3
+ * 라우트 매칭 전에 실행되는 글로벌 미들웨어 시스템
4
+ *
5
+ * 프로젝트 루트의 middleware.ts 파일에서 자동 로드
6
+ */
7
+
8
+ import { CookieManager } from "../filling/context";
9
+
10
+ // ========== Types ==========
11
+
12
+ export interface MiddlewareContext {
13
+ /** 원본 Request */
14
+ request: Request;
15
+ /** 파싱된 URL */
16
+ url: URL;
17
+ /** Cookie 매니저 */
18
+ cookies: CookieManager;
19
+ /** matcher에서 추출된 파라미터 */
20
+ params: Record<string, string>;
21
+
22
+ /** 리다이렉트 응답 생성 */
23
+ redirect(url: string, status?: 301 | 302 | 307 | 308): Response;
24
+ /** JSON 응답 생성 */
25
+ json(data: unknown, status?: number): Response;
26
+ /** 내부 라우트 재작성 (URL 변경 없이 다른 라우트 처리) */
27
+ rewrite(url: string): Request;
28
+
29
+ /** 다음 핸들러에 데이터 전달 */
30
+ set(key: string, value: unknown): void;
31
+ /** 전달된 데이터 읽기 */
32
+ get<T>(key: string): T | undefined;
33
+ }
34
+
35
+ export type MiddlewareNext = () => Promise<Response>;
36
+
37
+ export type MiddlewareFn = (
38
+ ctx: MiddlewareContext,
39
+ next: MiddlewareNext
40
+ ) => Response | Promise<Response>;
41
+
42
+ export interface MiddlewareConfig {
43
+ /** 미들웨어를 적용할 경로 패턴 */
44
+ matcher?: string[];
45
+ /** 제외할 경로 패턴 */
46
+ exclude?: string[];
47
+ }
48
+
49
+ // ========== Implementation ==========
50
+
51
+ /**
52
+ * MiddlewareContext 생성
53
+ */
54
+ interface MiddlewareMatchResult {
55
+ matched: boolean;
56
+ params: Record<string, string>;
57
+ }
58
+
59
+ export interface InternalMiddlewareContext extends MiddlewareContext {
60
+ getRewrittenRequest(): Request | null;
61
+ }
62
+
63
+ export function createMiddlewareContext(
64
+ request: Request,
65
+ params: Record<string, string> = {}
66
+ ): InternalMiddlewareContext {
67
+ const url = new URL(request.url);
68
+ const cookies = new CookieManager(request);
69
+ const store = new Map<string, unknown>();
70
+ let rewrittenRequest: Request | null = null;
71
+
72
+ return {
73
+ request,
74
+ url,
75
+ cookies,
76
+ params,
77
+
78
+ redirect(target: string, status: 301 | 302 | 307 | 308 = 302): Response {
79
+ return Response.redirect(new URL(target, url.origin).href, status);
80
+ },
81
+
82
+ json(data: unknown, status: number = 200): Response {
83
+ return Response.json(data, { status });
84
+ },
85
+
86
+ rewrite(target: string): Request {
87
+ const rewriteUrl = new URL(target, url.origin);
88
+ rewrittenRequest = new Request(rewriteUrl.href, {
89
+ method: request.method,
90
+ headers: request.headers,
91
+ body: request.clone().body, // 원본 request body 소비 방지
92
+ });
93
+ return rewrittenRequest;
94
+ },
95
+
96
+ set(key: string, value: unknown): void {
97
+ store.set(key, value);
98
+ },
99
+
100
+ get<T>(key: string): T | undefined {
101
+ return store.get(key) as T | undefined;
102
+ },
103
+
104
+ getRewrittenRequest(): Request | null {
105
+ return rewrittenRequest;
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * 경로가 matcher 패턴과 일치하는지 확인
112
+ * :path* → 와일드카드 매칭
113
+ */
114
+ export function matchesMiddlewarePath(
115
+ pathname: string,
116
+ config: MiddlewareConfig | null
117
+ ): boolean {
118
+ return getMiddlewareMatch(pathname, config).matched;
119
+ }
120
+
121
+ export function getMiddlewareMatch(
122
+ pathname: string,
123
+ config: MiddlewareConfig | null
124
+ ): MiddlewareMatchResult {
125
+ // config 없으면 모든 경로에 적용
126
+ if (!config) return { matched: true, params: {} };
127
+
128
+ // exclude 패턴 먼저 확인
129
+ if (config.exclude) {
130
+ for (const pattern of config.exclude) {
131
+ if (matchPattern(pathname, pattern)) return { matched: false, params: {} };
132
+ }
133
+ }
134
+
135
+ // matcher가 없으면 모든 경로에 적용
136
+ if (!config.matcher || config.matcher.length === 0) return { matched: true, params: {} };
137
+
138
+ // matcher 패턴 중 하나라도 일치하면 적용
139
+ for (const pattern of config.matcher) {
140
+ const params = matchPatternWithParams(pathname, pattern);
141
+ if (params) {
142
+ return { matched: true, params };
143
+ }
144
+ }
145
+
146
+ return { matched: false, params: {} };
147
+ }
148
+
149
+ /**
150
+ * 단순 경로 패턴 매칭
151
+ * - /api/* → /api/ 하위 모든 경로
152
+ * - /dashboard/:path* → /dashboard/ 하위 모든 경로
153
+ * - /about → 정확히 /about
154
+ */
155
+ function matchPattern(pathname: string, pattern: string): boolean {
156
+ return matchPatternWithParams(pathname, pattern) !== null;
157
+ }
158
+
159
+ function matchPatternWithParams(pathname: string, pattern: string): Record<string, string> | null {
160
+ // 와일드카드 패턴: /api/* → /api, /api/anything 모두 매칭
161
+ if (pattern.endsWith("*") || pattern.endsWith(":path*")) {
162
+ const prefix = pattern.replace(/[:*]path\*$/, "").replace(/\*$/, "");
163
+ // prefix 자체 또는 prefix 하위 경로 매칭
164
+ const normalizedPrefix = prefix.replace(/\/$/, "");
165
+ // 정확히 prefix이거나, prefix/ 로 시작하는 경우만 매칭 (/api/* → /apikeys 매칭 방지)
166
+ if (pathname === normalizedPrefix || pathname.startsWith(normalizedPrefix + "/")) {
167
+ const wildcard = pathname.slice(normalizedPrefix.length).replace(/^\/+/, "");
168
+ if (pattern.endsWith(":path*")) {
169
+ return { path: wildcard };
170
+ }
171
+ return {};
172
+ }
173
+ return null;
174
+ }
175
+
176
+ const pathSegments = pathname.split("/").filter(Boolean);
177
+ const patternSegments = pattern.split("/").filter(Boolean);
178
+ if (pathSegments.length !== patternSegments.length) return null;
179
+
180
+ const params: Record<string, string> = {};
181
+ for (let i = 0; i < patternSegments.length; i++) {
182
+ const patternSegment = patternSegments[i];
183
+ const pathSegment = pathSegments[i];
184
+
185
+ if (patternSegment.startsWith(":")) {
186
+ params[patternSegment.slice(1)] = pathSegment;
187
+ continue;
188
+ }
189
+
190
+ if (patternSegment !== pathSegment) {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ return params;
196
+ }
197
+
198
+ /**
199
+ * middleware.ts 파일에서 미들웨어 로드 (async)
200
+ */
201
+ export async function loadMiddleware(
202
+ rootDir: string
203
+ ): Promise<{ fn: MiddlewareFn; config: MiddlewareConfig | null } | null> {
204
+ const possiblePaths = [
205
+ `${rootDir}/middleware.ts`,
206
+ `${rootDir}/middleware.js`,
207
+ ];
208
+
209
+ for (const mwPath of possiblePaths) {
210
+ try {
211
+ const file = Bun.file(mwPath);
212
+ if (await file.exists()) {
213
+ const mod = await import(mwPath);
214
+ return validateMiddlewareModule(mod);
215
+ }
216
+ } catch (error) {
217
+ console.warn(`[Mandu] middleware.ts 로드 실패:`, error);
218
+ }
219
+ }
220
+
221
+ return null;
222
+ }
223
+
224
+ /**
225
+ * middleware.ts 동기 로드 (서버 시작 시 사용 — 첫 요청부터 미들웨어 보장)
226
+ */
227
+ export function loadMiddlewareSync(
228
+ rootDir: string
229
+ ): { fn: MiddlewareFn; config: MiddlewareConfig | null } | null {
230
+ const fs = require("fs") as typeof import("fs");
231
+ const possiblePaths = [
232
+ `${rootDir}/middleware.ts`,
233
+ `${rootDir}/middleware.js`,
234
+ ];
235
+
236
+ for (const mwPath of possiblePaths) {
237
+ try {
238
+ if (fs.existsSync(mwPath)) {
239
+ // Bun에서 require()는 .ts도 동기 로드 가능
240
+ const mod = require(mwPath);
241
+ return validateMiddlewareModule(mod);
242
+ }
243
+ } catch (error) {
244
+ console.warn(`[Mandu] middleware.ts 로드 실패:`, error);
245
+ }
246
+ }
247
+
248
+ return null;
249
+ }
250
+
251
+ function validateMiddlewareModule(
252
+ mod: Record<string, unknown>
253
+ ): { fn: MiddlewareFn; config: MiddlewareConfig | null } | null {
254
+ const fn = mod.default as MiddlewareFn;
255
+ const config = (mod.config as MiddlewareConfig) ?? null;
256
+
257
+ if (typeof fn !== "function") {
258
+ console.warn(`[Mandu] middleware.ts의 default export가 함수가 아닙니다.`);
259
+ return null;
260
+ }
261
+
262
+ return { fn, config };
263
+ }