@mandujs/core 0.9.41 → 0.9.42

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 (67) hide show
  1. package/package.json +1 -1
  2. package/src/bundler/build.ts +91 -73
  3. package/src/bundler/dev.ts +21 -14
  4. package/src/client/globals.ts +44 -0
  5. package/src/client/index.ts +5 -4
  6. package/src/client/island.ts +8 -13
  7. package/src/client/router.ts +33 -41
  8. package/src/client/runtime.ts +23 -51
  9. package/src/client/window-state.ts +101 -0
  10. package/src/config/index.ts +1 -0
  11. package/src/config/mandu.ts +45 -9
  12. package/src/config/validate.ts +158 -0
  13. package/src/constants.ts +25 -0
  14. package/src/contract/client.ts +4 -3
  15. package/src/contract/define.ts +459 -0
  16. package/src/devtools/ai/context-builder.ts +375 -0
  17. package/src/devtools/ai/index.ts +25 -0
  18. package/src/devtools/ai/mcp-connector.ts +465 -0
  19. package/src/devtools/client/catchers/error-catcher.ts +327 -0
  20. package/src/devtools/client/catchers/index.ts +18 -0
  21. package/src/devtools/client/catchers/network-proxy.ts +363 -0
  22. package/src/devtools/client/components/index.ts +39 -0
  23. package/src/devtools/client/components/kitchen-root.tsx +362 -0
  24. package/src/devtools/client/components/mandu-character.tsx +241 -0
  25. package/src/devtools/client/components/overlay.tsx +368 -0
  26. package/src/devtools/client/components/panel/errors-panel.tsx +259 -0
  27. package/src/devtools/client/components/panel/guard-panel.tsx +244 -0
  28. package/src/devtools/client/components/panel/index.ts +32 -0
  29. package/src/devtools/client/components/panel/islands-panel.tsx +304 -0
  30. package/src/devtools/client/components/panel/network-panel.tsx +292 -0
  31. package/src/devtools/client/components/panel/panel-container.tsx +259 -0
  32. package/src/devtools/client/filters/context-filters.ts +282 -0
  33. package/src/devtools/client/filters/index.ts +16 -0
  34. package/src/devtools/client/index.ts +63 -0
  35. package/src/devtools/client/persistence.ts +335 -0
  36. package/src/devtools/client/state-manager.ts +478 -0
  37. package/src/devtools/design-tokens.ts +263 -0
  38. package/src/devtools/hook/create-hook.ts +207 -0
  39. package/src/devtools/hook/index.ts +13 -0
  40. package/src/devtools/index.ts +439 -0
  41. package/src/devtools/init.ts +266 -0
  42. package/src/devtools/protocol.ts +237 -0
  43. package/src/devtools/server/index.ts +17 -0
  44. package/src/devtools/server/source-context.ts +444 -0
  45. package/src/devtools/types.ts +319 -0
  46. package/src/devtools/worker/index.ts +25 -0
  47. package/src/devtools/worker/redaction-worker.ts +222 -0
  48. package/src/devtools/worker/worker-manager.ts +409 -0
  49. package/src/error/formatter.ts +28 -24
  50. package/src/error/index.ts +13 -9
  51. package/src/error/result.ts +46 -0
  52. package/src/error/types.ts +6 -4
  53. package/src/filling/filling.ts +6 -5
  54. package/src/guard/check.ts +60 -56
  55. package/src/guard/types.ts +3 -1
  56. package/src/guard/watcher.ts +10 -1
  57. package/src/index.ts +81 -0
  58. package/src/intent/index.ts +310 -0
  59. package/src/island/index.ts +304 -0
  60. package/src/router/fs-patterns.ts +7 -0
  61. package/src/router/fs-routes.ts +20 -8
  62. package/src/router/fs-scanner.ts +117 -133
  63. package/src/runtime/server.ts +261 -201
  64. package/src/runtime/ssr.ts +5 -4
  65. package/src/runtime/streaming-ssr.ts +5 -4
  66. package/src/utils/bun.ts +8 -0
  67. package/src/utils/lru-cache.ts +75 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * 타입 안전 전역 상태 접근자
3
+ * window 객체 직접 접근 대신 이 모듈의 함수 사용
4
+ */
5
+ import type { Root } from "react-dom/client";
6
+ import type { RouterState } from "./router";
7
+
8
+ // ============================================
9
+ // 환경 체크
10
+ // ============================================
11
+
12
+ export function isBrowser(): boolean {
13
+ return typeof window !== "undefined";
14
+ }
15
+
16
+ // ============================================
17
+ // Router State
18
+ // ============================================
19
+
20
+ export function getRouterState(): RouterState | undefined {
21
+ if (!isBrowser()) return undefined;
22
+ return window.__MANDU_ROUTER_STATE__;
23
+ }
24
+
25
+ export function setRouterState(state: RouterState): void {
26
+ if (!isBrowser()) return;
27
+ window.__MANDU_ROUTER_STATE__ = state;
28
+ }
29
+
30
+ export function getRouterListeners(): Set<(state: RouterState) => void> {
31
+ if (!isBrowser()) return new Set();
32
+
33
+ if (!window.__MANDU_ROUTER_LISTENERS__) {
34
+ window.__MANDU_ROUTER_LISTENERS__ = new Set();
35
+ }
36
+ return window.__MANDU_ROUTER_LISTENERS__;
37
+ }
38
+
39
+ // ============================================
40
+ // Route & Data
41
+ // ============================================
42
+
43
+ export function getManduRoute():
44
+ | { id: string; pattern: string; params: Record<string, string> }
45
+ | undefined {
46
+ if (!isBrowser()) return undefined;
47
+ return window.__MANDU_ROUTE__;
48
+ }
49
+
50
+ export function getManduData():
51
+ | Record<string, { serverData: unknown; timestamp?: number }>
52
+ | undefined {
53
+ if (!isBrowser()) return undefined;
54
+ return window.__MANDU_DATA__;
55
+ }
56
+
57
+ export function getManduDataRaw(): string | undefined {
58
+ if (!isBrowser()) return undefined;
59
+ return window.__MANDU_DATA_RAW__;
60
+ }
61
+
62
+ /**
63
+ * 특정 라우트의 서버 데이터 조회 (타입 안전)
64
+ */
65
+ export function getServerData<T>(routeId: string): T | undefined {
66
+ const data = getManduData();
67
+ return data?.[routeId]?.serverData as T | undefined;
68
+ }
69
+
70
+ /**
71
+ * 서버 데이터 설정
72
+ */
73
+ export function setServerData(routeId: string, data: unknown): void {
74
+ if (!isBrowser()) return;
75
+
76
+ if (!window.__MANDU_DATA__) {
77
+ window.__MANDU_DATA__ = {};
78
+ }
79
+ window.__MANDU_DATA__[routeId] = { serverData: data };
80
+ }
81
+
82
+ // ============================================
83
+ // Hydration Roots
84
+ // ============================================
85
+
86
+ export function getHydratedRoots(): Map<string, Root> {
87
+ if (!isBrowser()) return new Map();
88
+
89
+ if (!window.__MANDU_ROOTS__) {
90
+ window.__MANDU_ROOTS__ = new Map();
91
+ }
92
+ return window.__MANDU_ROOTS__;
93
+ }
94
+
95
+ export function setHydratedRoot(id: string, root: Root): void {
96
+ getHydratedRoots().set(id, root);
97
+ }
98
+
99
+ export function removeHydratedRoot(id: string): boolean {
100
+ return getHydratedRoots().delete(id);
101
+ }
@@ -1 +1,2 @@
1
1
  export * from "./mandu";
2
+ export * from "./validate";
@@ -1,23 +1,62 @@
1
1
  import path from "path";
2
- import fs from "fs/promises";
2
+ import { readJsonFile } from "../utils/bun";
3
3
 
4
- export type GuardRuleSeverity = "error" | "warn" | "off";
4
+ export type GuardRuleSeverity = "error" | "warn" | "warning" | "off";
5
5
 
6
6
  export interface ManduConfig {
7
+ server?: {
8
+ port?: number;
9
+ hostname?: string;
10
+ cors?:
11
+ | boolean
12
+ | {
13
+ origin?: string | string[];
14
+ methods?: string[];
15
+ credentials?: boolean;
16
+ };
17
+ streaming?: boolean;
18
+ };
7
19
  guard?: {
20
+ preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic";
21
+ srcDir?: string;
22
+ exclude?: string[];
23
+ realtime?: boolean;
8
24
  rules?: Record<string, GuardRuleSeverity>;
9
25
  contractRequired?: GuardRuleSeverity;
10
26
  };
27
+ build?: {
28
+ outDir?: string;
29
+ minify?: boolean;
30
+ sourcemap?: boolean;
31
+ splitting?: boolean;
32
+ };
33
+ dev?: {
34
+ hmr?: boolean;
35
+ watchDirs?: string[];
36
+ };
37
+ fsRoutes?: {
38
+ routesDir?: string;
39
+ extensions?: string[];
40
+ exclude?: string[];
41
+ islandSuffix?: string;
42
+ legacyManifestPath?: string;
43
+ mergeWithLegacy?: boolean;
44
+ };
45
+ seo?: {
46
+ enabled?: boolean;
47
+ defaultTitle?: string;
48
+ titleTemplate?: string;
49
+ };
11
50
  }
12
51
 
13
- const CONFIG_FILES = [
52
+ export const CONFIG_FILES = [
14
53
  "mandu.config.ts",
15
54
  "mandu.config.js",
16
55
  "mandu.config.json",
17
56
  path.join(".mandu", "guard.json"),
18
57
  ];
19
58
 
20
- function coerceConfig(raw: unknown, source: string): ManduConfig {
59
+ export function coerceConfig(raw: unknown, source: string): ManduConfig {
21
60
  if (!raw || typeof raw !== "object") return {};
22
61
 
23
62
  // .mandu/guard.json can be guard-only
@@ -31,16 +70,13 @@ function coerceConfig(raw: unknown, source: string): ManduConfig {
31
70
  export async function loadManduConfig(rootDir: string): Promise<ManduConfig> {
32
71
  for (const fileName of CONFIG_FILES) {
33
72
  const filePath = path.join(rootDir, fileName);
34
- try {
35
- await fs.access(filePath);
36
- } catch {
73
+ if (!(await Bun.file(filePath).exists())) {
37
74
  continue;
38
75
  }
39
76
 
40
77
  if (fileName.endsWith(".json")) {
41
78
  try {
42
- const content = await Bun.file(filePath).text();
43
- const parsed = JSON.parse(content);
79
+ const parsed = await readJsonFile(filePath);
44
80
  return coerceConfig(parsed, fileName);
45
81
  } catch {
46
82
  return {};
@@ -0,0 +1,158 @@
1
+ import { z, ZodError } from "zod";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import { CONFIG_FILES, coerceConfig } from "./mandu";
5
+ import { readJsonFile } from "../utils/bun";
6
+
7
+ /**
8
+ * Mandu 설정 스키마
9
+ */
10
+ export const ManduConfigSchema = z
11
+ .object({
12
+ server: z
13
+ .object({
14
+ port: z.number().min(1).max(65535).default(3000),
15
+ hostname: z.string().default("localhost"),
16
+ cors: z
17
+ .union([
18
+ z.boolean(),
19
+ z.object({
20
+ origin: z.union([z.string(), z.array(z.string())]).optional(),
21
+ methods: z.array(z.string()).optional(),
22
+ credentials: z.boolean().optional(),
23
+ }),
24
+ ])
25
+ .default(false),
26
+ streaming: z.boolean().default(false),
27
+ })
28
+ .default({}),
29
+
30
+ guard: z
31
+ .object({
32
+ preset: z.enum(["mandu", "fsd", "clean", "hexagonal", "atomic"]).default("mandu"),
33
+ srcDir: z.string().default("src"),
34
+ exclude: z.array(z.string()).default([]),
35
+ realtime: z.boolean().default(true),
36
+ rules: z.record(z.enum(["error", "warn", "warning", "off"])).optional(),
37
+ })
38
+ .default({}),
39
+
40
+ build: z
41
+ .object({
42
+ outDir: z.string().default(".mandu"),
43
+ minify: z.boolean().default(true),
44
+ sourcemap: z.boolean().default(false),
45
+ splitting: z.boolean().default(false),
46
+ })
47
+ .default({}),
48
+
49
+ dev: z
50
+ .object({
51
+ hmr: z.boolean().default(true),
52
+ watchDirs: z.array(z.string()).default([]),
53
+ })
54
+ .default({}),
55
+
56
+ fsRoutes: z
57
+ .object({
58
+ routesDir: z.string().default("app"),
59
+ extensions: z.array(z.string()).default([".tsx", ".ts", ".jsx", ".js"]),
60
+ exclude: z.array(z.string()).default([]),
61
+ islandSuffix: z.string().default(".island"),
62
+ legacyManifestPath: z.string().optional(),
63
+ mergeWithLegacy: z.boolean().default(true),
64
+ })
65
+ .default({}),
66
+
67
+ seo: z
68
+ .object({
69
+ enabled: z.boolean().default(true),
70
+ defaultTitle: z.string().optional(),
71
+ titleTemplate: z.string().optional(),
72
+ })
73
+ .default({}),
74
+ })
75
+ .passthrough();
76
+
77
+ export type ValidatedManduConfig = z.infer<typeof ManduConfigSchema>;
78
+
79
+ /**
80
+ * 검증 결과
81
+ */
82
+ export interface ValidationResult {
83
+ valid: boolean;
84
+ config?: ValidatedManduConfig;
85
+ errors?: Array<{
86
+ path: string;
87
+ message: string;
88
+ }>;
89
+ source?: string;
90
+ }
91
+
92
+ /**
93
+ * 설정 파일 검증
94
+ */
95
+ export async function validateConfig(rootDir: string): Promise<ValidationResult> {
96
+ for (const fileName of CONFIG_FILES) {
97
+ const filePath = path.join(rootDir, fileName);
98
+ if (!(await Bun.file(filePath).exists())) {
99
+ continue;
100
+ }
101
+
102
+ try {
103
+ let raw: unknown;
104
+ if (fileName.endsWith(".json")) {
105
+ raw = await readJsonFile(filePath);
106
+ } else {
107
+ const module = await import(pathToFileURL(filePath).href);
108
+ raw = module?.default ?? module;
109
+ }
110
+
111
+ const config = ManduConfigSchema.parse(coerceConfig(raw ?? {}, fileName));
112
+ return { valid: true, config, source: fileName };
113
+ } catch (error) {
114
+ if (error instanceof ZodError) {
115
+ const errors = error.errors.map((e) => ({
116
+ path: e.path.join("."),
117
+ message: e.message,
118
+ }));
119
+ return { valid: false, errors, source: fileName };
120
+ }
121
+
122
+ return {
123
+ valid: false,
124
+ errors: [
125
+ {
126
+ path: "",
127
+ message: `Failed to load config: ${
128
+ error instanceof Error ? error.message : String(error)
129
+ }`,
130
+ },
131
+ ],
132
+ source: fileName,
133
+ };
134
+ }
135
+ }
136
+
137
+ // 설정 파일 없음 - 기본값 사용
138
+ return { valid: true, config: ManduConfigSchema.parse({}) };
139
+ }
140
+
141
+ /**
142
+ * CLI용 검증 및 리포트
143
+ */
144
+ export async function validateAndReport(rootDir: string): Promise<ValidatedManduConfig | null> {
145
+ const result = await validateConfig(rootDir);
146
+
147
+ if (!result.valid) {
148
+ console.error(`\n❌ Invalid config${result.source ? ` (${result.source})` : ""}:\n`);
149
+ for (const error of result.errors || []) {
150
+ const location = error.path ? ` ${error.path}: ` : " ";
151
+ console.error(`${location}${error.message}`);
152
+ }
153
+ console.error("");
154
+ return null;
155
+ }
156
+
157
+ return result.config!;
158
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Mandu core constants
3
+ * Centralized defaults for timeouts, limits, and client behavior.
4
+ */
5
+
6
+ export const TIMEOUTS = {
7
+ LOADER_DEFAULT: 5000,
8
+ CLIENT_DEFAULT: 30000,
9
+ WATCHER_DEBOUNCE: 100,
10
+ HMR_RECONNECT_DELAY: 1000,
11
+ HMR_MAX_RECONNECT: 10,
12
+ } as const;
13
+
14
+ export const PORTS = {
15
+ HMR_OFFSET: 1,
16
+ } as const;
17
+
18
+ export const HYDRATION = {
19
+ DEFAULT_PRIORITY: "visible" as const,
20
+ } as const;
21
+
22
+ export const LIMITS = {
23
+ ROUTER_PATTERN_CACHE: 200,
24
+ ROUTER_PREFETCH_CACHE: 500,
25
+ } as const;
@@ -7,13 +7,14 @@
7
7
  * - 타입 안전 fetch 호출
8
8
  */
9
9
 
10
- import type { z } from "zod";
10
+ import type { z } from "zod";
11
11
  import type {
12
12
  ContractSchema,
13
13
  ContractMethod,
14
14
  MethodRequestSchema,
15
15
  } from "./schema";
16
16
  import type { InferResponseSchema } from "./types";
17
+ import { TIMEOUTS } from "../constants";
17
18
 
18
19
  /**
19
20
  * Client options for making requests
@@ -176,7 +177,7 @@ export function createClient<T extends ContractSchema>(
176
177
  baseUrl,
177
178
  headers: defaultHeaders = {},
178
179
  fetch: customFetch = fetch,
179
- timeout = 30000,
180
+ timeout = TIMEOUTS.CLIENT_DEFAULT,
180
181
  } = options;
181
182
 
182
183
  const methods: ContractMethod[] = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -286,7 +287,7 @@ export async function contractFetch<
286
287
  const {
287
288
  headers: defaultHeaders = {},
288
289
  fetch: customFetch = fetch,
289
- timeout = 30000,
290
+ timeout = TIMEOUTS.CLIENT_DEFAULT,
290
291
  } = clientOptions;
291
292
 
292
293
  // Build URL