@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
@@ -3,17 +3,20 @@
3
3
  * React hooks for client-side routing
4
4
  */
5
5
 
6
- import { useState, useEffect, useCallback, useSyncExternalStore } from "react";
6
+ import { useState, useEffect, useCallback, useMemo, useSyncExternalStore } from "react";
7
7
  import {
8
8
  subscribe,
9
9
  getRouterState,
10
10
  getCurrentRoute,
11
11
  getLoaderData,
12
+ getActionData,
12
13
  getNavigationState,
13
14
  navigate,
15
+ submitAction,
14
16
  type RouteInfo,
15
17
  type NavigationState,
16
18
  type NavigateOptions,
19
+ type ActionResult,
17
20
  } from "./router";
18
21
 
19
22
  /**
@@ -235,12 +238,18 @@ export function useRouter() {
235
238
  export function useMatch(pattern: string): boolean {
236
239
  const pathname = usePathname();
237
240
 
238
- // 간단한 패턴 매칭 (파라미터 고려)
239
- const regexStr = pattern
240
- .replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "[^/]+")
241
- .replace(/\//g, "\\/");
241
+ const regex = useMemo(() => {
242
+ // 파라미터를 플레이스홀더로 치환 → 나머지 특수문자 이스케이프 → 플레이스홀더 복원
243
+ const PLACEHOLDER = "\x00PARAM\x00";
244
+ const withPlaceholders = pattern.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, PLACEHOLDER);
245
+ const escaped = withPlaceholders.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
246
+ const regexStr = escaped.replace(
247
+ new RegExp(PLACEHOLDER.replace(/\x00/g, "\\x00"), "g"),
248
+ "[^/]+"
249
+ );
250
+ return new RegExp(`^${regexStr}$`);
251
+ }, [pattern]);
242
252
 
243
- const regex = new RegExp(`^${regexStr}$`);
244
253
  return regex.test(pathname);
245
254
  }
246
255
 
@@ -265,3 +274,93 @@ export function useGoForward(): () => void {
265
274
  }
266
275
  }, []);
267
276
  }
277
+
278
+ /**
279
+ * Action 데이터 접근
280
+ * filling.action()의 결과 데이터
281
+ *
282
+ * @example
283
+ * ```tsx
284
+ * const actionData = useActionData<{ created: boolean }>();
285
+ * if (actionData?.created) { ... }
286
+ * ```
287
+ */
288
+ export function useActionData<T = unknown>(): T | undefined {
289
+ const state = useRouterState();
290
+ return state.actionData as T | undefined;
291
+ }
292
+
293
+ /**
294
+ * Action 제출 함수
295
+ *
296
+ * @example
297
+ * ```tsx
298
+ * const submit = useSubmit();
299
+ * const handleCreate = () => submit("/api/todos", { title: "New" }, "create");
300
+ * ```
301
+ */
302
+ export function useSubmit(): (
303
+ url: string,
304
+ data: FormData | Record<string, unknown>,
305
+ actionName?: string,
306
+ method?: string
307
+ ) => Promise<ActionResult> {
308
+ return useCallback(
309
+ (url: string, data: FormData | Record<string, unknown>, actionName = "default", method = "POST") => {
310
+ return submitAction(url, data, actionName, method);
311
+ },
312
+ []
313
+ );
314
+ }
315
+
316
+ /**
317
+ * Mandu 통합 상태 훅
318
+ * 라우트, 데이터, 네비게이션, 액션 상태를 하나의 객체로 제공
319
+ *
320
+ * @example
321
+ * ```tsx
322
+ * const {
323
+ * url, params, routeId,
324
+ * loaderData, actionData,
325
+ * navigation,
326
+ * navigate, submit,
327
+ * } = useMandu();
328
+ * ```
329
+ */
330
+ export function useMandu() {
331
+ const state = useRouterState();
332
+ const navigateFn = useNavigate();
333
+ const submitFn = useSubmit();
334
+
335
+ // URL은 라우터 상태가 실제로 바뀔 때만 재생성한다.
336
+ // query-only navigation도 반영되도록 상태 전체를 의존성으로 사용한다.
337
+ const url = useMemo(
338
+ () => typeof window !== "undefined" ? new URL(window.location.href) : null,
339
+ [state]
340
+ );
341
+
342
+ return {
343
+ /** 현재 URL (브라우저) */
344
+ url,
345
+ /** URL 파라미터 */
346
+ params: (state.currentRoute?.params ?? {}) as Record<string, string>,
347
+ /** 현재 라우트 ID */
348
+ routeId: state.currentRoute?.id ?? "",
349
+ /** 라우트 패턴 */
350
+ pattern: state.currentRoute?.pattern ?? "",
351
+ /** Loader 데이터 */
352
+ loaderData: state.loaderData,
353
+ /** 마지막 Action 결과 */
354
+ actionData: state.actionData,
355
+ /** 네비게이션 상태 */
356
+ navigation: state.navigation,
357
+ /** 네비게이션 중 여부 */
358
+ isNavigating: state.navigation.state === "loading",
359
+ /** Action 제출 중 여부 */
360
+ isSubmitting: state.navigation.state === "submitting",
361
+ /** 프로그래매틱 네비게이션 */
362
+ navigate: navigateFn,
363
+ /** Action 제출 */
364
+ submit: submitFn,
365
+ };
366
+ }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Mandu Client Module 🏝️
3
- * 클라이언트 사이드 hydration 라우팅을 위한 API
2
+ * Mandu Client Module
3
+ * Client-side hydration and routing API
4
4
  *
5
5
  * @example
6
6
  * ```typescript
7
- * // Island 컴포넌트
7
+ * // Island component
8
8
  * import { Mandu } from "@mandujs/core/client";
9
9
  *
10
10
  * export default Mandu.island<TodosData>({
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * @example
17
17
  * ```typescript
18
- * // Client-side 라우팅
18
+ * // Client-side routing
19
19
  * import { Link, useRouter } from "@mandujs/core/client";
20
20
  *
21
21
  * function Nav() {
@@ -63,6 +63,15 @@ export {
63
63
  type HydrationPriority,
64
64
  } from "./runtime";
65
65
 
66
+ // SSE / ReadableStream API (microtask-starvation-safe)
67
+ export {
68
+ useSSE,
69
+ readStreamWithYield,
70
+ type UseSSEOptions,
71
+ type UseSSEReturn,
72
+ type ReadStreamOptions,
73
+ } from "./use-sse";
74
+
66
75
  // Client-side Router API
67
76
  export {
68
77
  navigate,
@@ -71,18 +80,35 @@ export {
71
80
  getRouterState,
72
81
  getCurrentRoute,
73
82
  getLoaderData,
83
+ getActionData,
74
84
  getNavigationState,
75
85
  initializeRouter,
76
86
  cleanupRouter,
87
+ submitAction,
88
+ setShouldRevalidate,
77
89
  type RouteInfo,
78
90
  type NavigationState,
79
91
  type RouterState,
80
92
  type NavigateOptions,
93
+ type ActionResult,
94
+ type ShouldRevalidateFunction,
81
95
  } from "./router";
82
96
 
83
97
  // Link Components
84
98
  export { Link, NavLink, type LinkProps, type NavLinkProps } from "./Link";
85
99
 
100
+ // Form Component (Progressive Enhancement)
101
+ export { Form, type FormProps, type FormState } from "./Form";
102
+
103
+ // RPC Client
104
+ export { createClient, RpcError, type RpcMethods, type RpcRequestOptions, type RpcClientOptions } from "./rpc";
105
+
106
+ // useFetch Composable
107
+ export { useFetch, type UseFetchOptions, type UseFetchReturn } from "./use-fetch";
108
+
109
+ // Head Management
110
+ export { useHead, useSeoMeta, resetSSRHead, getSSRHeadTags, type HeadConfig, type SeoMetaConfig } from "./use-head";
111
+
86
112
  // Stable interaction components
87
113
  export { ManduButton, ManduModalTrigger } from "./interaction";
88
114
 
@@ -100,9 +126,12 @@ export {
100
126
  useGoBack,
101
127
  useGoForward,
102
128
  useRouterState,
129
+ useActionData,
130
+ useSubmit,
131
+ useMandu,
103
132
  } from "./hooks";
104
133
 
105
- // Props Serialization (Fresh 스타일)
134
+ // Props Serialization (Fresh style)
106
135
  export {
107
136
  serializeProps,
108
137
  deserializeProps,
@@ -118,7 +147,7 @@ import { Link, NavLink } from "./Link";
118
147
 
119
148
  /**
120
149
  * Mandu Client namespace
121
- * v0.8.0: Hydration 자동으로 처리됨 (generateRuntimeSource에서 생성)
150
+ * v0.8.0: Hydration is handled automatically (generateRuntimeSource)
122
151
  * Note: Use `ManduClient` to avoid conflict with other Mandu exports
123
152
  */
124
153
  export const ManduClient = {