@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.
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -73
- package/src/bundler/dev.ts +21 -14
- package/src/client/globals.ts +44 -0
- package/src/client/index.ts +5 -4
- package/src/client/island.ts +8 -13
- package/src/client/router.ts +33 -41
- package/src/client/runtime.ts +23 -51
- package/src/client/window-state.ts +101 -0
- package/src/config/index.ts +1 -0
- package/src/config/mandu.ts +45 -9
- package/src/config/validate.ts +158 -0
- package/src/constants.ts +25 -0
- package/src/contract/client.ts +4 -3
- package/src/contract/define.ts +459 -0
- package/src/devtools/ai/context-builder.ts +375 -0
- package/src/devtools/ai/index.ts +25 -0
- package/src/devtools/ai/mcp-connector.ts +465 -0
- package/src/devtools/client/catchers/error-catcher.ts +327 -0
- package/src/devtools/client/catchers/index.ts +18 -0
- package/src/devtools/client/catchers/network-proxy.ts +363 -0
- package/src/devtools/client/components/index.ts +39 -0
- package/src/devtools/client/components/kitchen-root.tsx +362 -0
- package/src/devtools/client/components/mandu-character.tsx +241 -0
- package/src/devtools/client/components/overlay.tsx +368 -0
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -0
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -0
- package/src/devtools/client/components/panel/index.ts +32 -0
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -0
- package/src/devtools/client/components/panel/network-panel.tsx +292 -0
- package/src/devtools/client/components/panel/panel-container.tsx +259 -0
- package/src/devtools/client/filters/context-filters.ts +282 -0
- package/src/devtools/client/filters/index.ts +16 -0
- package/src/devtools/client/index.ts +63 -0
- package/src/devtools/client/persistence.ts +335 -0
- package/src/devtools/client/state-manager.ts +478 -0
- package/src/devtools/design-tokens.ts +263 -0
- package/src/devtools/hook/create-hook.ts +207 -0
- package/src/devtools/hook/index.ts +13 -0
- package/src/devtools/index.ts +439 -0
- package/src/devtools/init.ts +266 -0
- package/src/devtools/protocol.ts +237 -0
- package/src/devtools/server/index.ts +17 -0
- package/src/devtools/server/source-context.ts +444 -0
- package/src/devtools/types.ts +319 -0
- package/src/devtools/worker/index.ts +25 -0
- package/src/devtools/worker/redaction-worker.ts +222 -0
- package/src/devtools/worker/worker-manager.ts +409 -0
- package/src/error/formatter.ts +28 -24
- package/src/error/index.ts +13 -9
- package/src/error/result.ts +46 -0
- package/src/error/types.ts +6 -4
- package/src/filling/filling.ts +6 -5
- package/src/guard/check.ts +60 -56
- package/src/guard/types.ts +3 -1
- package/src/guard/watcher.ts +10 -1
- package/src/index.ts +81 -0
- package/src/intent/index.ts +310 -0
- package/src/island/index.ts +304 -0
- package/src/router/fs-patterns.ts +7 -0
- package/src/router/fs-routes.ts +20 -8
- package/src/router/fs-scanner.ts +117 -133
- package/src/runtime/server.ts +261 -201
- package/src/runtime/ssr.ts +5 -4
- package/src/runtime/streaming-ssr.ts +5 -4
- package/src/utils/bun.ts +8 -0
- 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
|
+
}
|
package/src/config/index.ts
CHANGED
package/src/config/mandu.ts
CHANGED
|
@@ -1,23 +1,62 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -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;
|
package/src/contract/client.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
290
|
+
timeout = TIMEOUTS.CLIENT_DEFAULT,
|
|
290
291
|
} = clientOptions;
|
|
291
292
|
|
|
292
293
|
// Build URL
|