@mandujs/core 0.18.20 → 0.18.22

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 (91) hide show
  1. package/package.json +3 -1
  2. package/src/brain/architecture/analyzer.ts +3 -5
  3. package/src/brain/architecture/types.ts +4 -4
  4. package/src/brain/doctor/analyzer.ts +1 -0
  5. package/src/brain/doctor/index.ts +1 -1
  6. package/src/brain/doctor/patcher.ts +10 -6
  7. package/src/brain/doctor/reporter.ts +4 -4
  8. package/src/brain/types.ts +14 -10
  9. package/src/bundler/build.ts +17 -17
  10. package/src/bundler/css.ts +3 -2
  11. package/src/bundler/dev.ts +1 -1
  12. package/src/client/island.ts +10 -9
  13. package/src/client/router.ts +1 -1
  14. package/src/config/mcp-ref.ts +6 -6
  15. package/src/config/metadata.test.ts +1 -1
  16. package/src/config/metadata.ts +36 -16
  17. package/src/config/symbols.ts +1 -1
  18. package/src/config/validate.ts +17 -1
  19. package/src/content/content.test.ts +3 -3
  20. package/src/content/loaders/file.ts +3 -0
  21. package/src/content/loaders/glob.ts +1 -0
  22. package/src/contract/client-safe.test.ts +1 -1
  23. package/src/contract/client.test.ts +2 -1
  24. package/src/contract/client.ts +18 -18
  25. package/src/contract/define.ts +32 -17
  26. package/src/contract/handler.ts +11 -11
  27. package/src/contract/index.ts +2 -5
  28. package/src/contract/infer.test.ts +2 -1
  29. package/src/contract/normalize.test.ts +1 -1
  30. package/src/contract/normalize.ts +17 -11
  31. package/src/contract/registry.test.ts +1 -1
  32. package/src/contract/zod-utils.ts +155 -0
  33. package/src/devtools/client/catchers/error-catcher.ts +3 -3
  34. package/src/devtools/client/catchers/network-proxy.ts +5 -1
  35. package/src/devtools/client/components/kitchen-root.tsx +2 -2
  36. package/src/devtools/client/components/panel/guard-panel.tsx +3 -3
  37. package/src/devtools/client/state-manager.ts +9 -9
  38. package/src/devtools/index.ts +8 -8
  39. package/src/devtools/init.ts +2 -2
  40. package/src/devtools/protocol.ts +4 -4
  41. package/src/devtools/server/source-context.ts +9 -3
  42. package/src/devtools/types.ts +5 -5
  43. package/src/devtools/worker/redaction-worker.ts +12 -5
  44. package/src/error/index.ts +1 -1
  45. package/src/error/result.ts +14 -0
  46. package/src/filling/deps.ts +5 -2
  47. package/src/filling/filling.ts +1 -1
  48. package/src/generator/templates.ts +2 -2
  49. package/src/guard/contract-guard.test.ts +1 -0
  50. package/src/guard/file-type.test.ts +1 -1
  51. package/src/guard/index.ts +1 -1
  52. package/src/guard/negotiation.ts +29 -1
  53. package/src/guard/presets/index.ts +3 -0
  54. package/src/guard/semantic-slots.ts +4 -4
  55. package/src/index.ts +10 -1
  56. package/src/intent/index.ts +28 -17
  57. package/src/island/index.ts +8 -8
  58. package/src/openapi/generator.ts +49 -31
  59. package/src/plugins/index.ts +1 -1
  60. package/src/plugins/registry.ts +28 -18
  61. package/src/plugins/types.ts +2 -2
  62. package/src/resource/__tests__/backward-compat.test.ts +2 -2
  63. package/src/resource/__tests__/edge-cases.test.ts +14 -13
  64. package/src/resource/__tests__/fixtures.ts +2 -2
  65. package/src/resource/__tests__/generator.test.ts +1 -1
  66. package/src/resource/__tests__/performance.test.ts +8 -6
  67. package/src/resource/schema.ts +1 -1
  68. package/src/router/fs-routes.ts +34 -40
  69. package/src/router/fs-types.ts +2 -2
  70. package/src/router/index.ts +1 -1
  71. package/src/runtime/boundary.tsx +4 -4
  72. package/src/runtime/logger.test.ts +3 -3
  73. package/src/runtime/logger.ts +1 -1
  74. package/src/runtime/server.ts +18 -16
  75. package/src/runtime/ssr.ts +1 -1
  76. package/src/runtime/stable-selector.ts +1 -2
  77. package/src/runtime/streaming-ssr.ts +15 -6
  78. package/src/seo/index.ts +5 -0
  79. package/src/seo/integration/ssr.ts +4 -4
  80. package/src/seo/render/basic.ts +12 -4
  81. package/src/seo/render/opengraph.ts +12 -6
  82. package/src/seo/render/twitter.ts +3 -2
  83. package/src/seo/resolve/url.ts +7 -0
  84. package/src/seo/types.ts +13 -0
  85. package/src/spec/schema.ts +89 -61
  86. package/src/types/branded.ts +56 -0
  87. package/src/types/index.ts +1 -0
  88. package/src/utils/hasher.test.ts +6 -6
  89. package/src/utils/hasher.ts +2 -2
  90. package/src/utils/index.ts +1 -1
  91. package/src/watcher/watcher.ts +2 -2
@@ -7,14 +7,14 @@
7
7
  * - 타입 안전 fetch 호출
8
8
  */
9
9
 
10
- import type { z } from "zod";
11
- import type {
12
- ContractSchema,
13
- ContractMethod,
14
- MethodRequestSchema,
15
- } from "./schema";
16
- import type { InferResponseSchema } from "./types";
17
- import { TIMEOUTS } from "../constants";
10
+ import type { z } from "zod";
11
+ import type {
12
+ ContractSchema,
13
+ ContractMethod,
14
+ MethodRequestSchema,
15
+ } from "./schema";
16
+ import type { InferResponseSchema } from "./types";
17
+ import { TIMEOUTS } from "../constants";
18
18
 
19
19
  /**
20
20
  * Client options for making requests
@@ -74,12 +74,12 @@ type InferRequestOptions<T extends MethodRequestSchema | undefined> =
74
74
  /**
75
75
  * Infer success response from contract
76
76
  */
77
- type InferSuccessResponse<TResponse extends ContractSchema["response"]> =
78
- InferResponseSchema<TResponse[200]> extends never
79
- ? InferResponseSchema<TResponse[201]> extends never
80
- ? unknown
81
- : InferResponseSchema<TResponse[201]>
82
- : InferResponseSchema<TResponse[200]>;
77
+ type InferSuccessResponse<TResponse extends ContractSchema["response"]> =
78
+ InferResponseSchema<TResponse[200]> extends never
79
+ ? InferResponseSchema<TResponse[201]> extends never
80
+ ? unknown
81
+ : InferResponseSchema<TResponse[201]>
82
+ : InferResponseSchema<TResponse[200]>;
83
83
 
84
84
  /**
85
85
  * Contract client method
@@ -95,7 +95,7 @@ export type ContractClientMethod<
95
95
  * Contract client interface
96
96
  */
97
97
  export type ContractClient<T extends ContractSchema> = {
98
- [M in Extract<keyof T["request"], ContractMethod>]: ContractClientMethod<
98
+ [M in ContractMethod as M extends keyof T["request"] ? M : never]: ContractClientMethod<
99
99
  T["request"][M] extends MethodRequestSchema ? T["request"][M] : undefined,
100
100
  T["response"]
101
101
  >;
@@ -177,7 +177,7 @@ export function createClient<T extends ContractSchema>(
177
177
  baseUrl,
178
178
  headers: defaultHeaders = {},
179
179
  fetch: customFetch = fetch,
180
- timeout = TIMEOUTS.CLIENT_DEFAULT,
180
+ timeout = TIMEOUTS.CLIENT_DEFAULT,
181
181
  } = options;
182
182
 
183
183
  const methods: ContractMethod[] = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -264,7 +264,7 @@ export function createClient<T extends ContractSchema>(
264
264
  */
265
265
  export async function contractFetch<
266
266
  T extends ContractSchema,
267
- M extends Extract<keyof T["request"], ContractMethod>,
267
+ M extends ContractMethod & keyof T["request"],
268
268
  >(
269
269
  _contract: T,
270
270
  method: M,
@@ -287,7 +287,7 @@ export async function contractFetch<
287
287
  const {
288
288
  headers: defaultHeaders = {},
289
289
  fetch: customFetch = fetch,
290
- timeout = TIMEOUTS.CLIENT_DEFAULT,
290
+ timeout = TIMEOUTS.CLIENT_DEFAULT,
291
291
  } = clientOptions;
292
292
 
293
293
  // Build URL
@@ -28,6 +28,15 @@
28
28
  */
29
29
 
30
30
  import { z, type ZodType, type ZodObject, type ZodRawShape } from 'zod';
31
+ import {
32
+ getZodTypeName,
33
+ getZodInnerType,
34
+ getZodArrayElementType,
35
+ getZodObjectShape,
36
+ getZodEnumValues,
37
+ getZodChecks,
38
+ isZodRequired,
39
+ } from './zod-utils';
31
40
 
32
41
  // ============================================================================
33
42
  // Types
@@ -58,7 +67,7 @@ export interface EndpointDefinition<
58
67
  tags?: string[];
59
68
  }
60
69
 
61
- export type ContractDefinition = Record<string, EndpointDefinition<any, any, any>>;
70
+ export type ContractDefinition = Record<string, EndpointDefinition>;
62
71
 
63
72
  // ============================================================================
64
73
  // Contract Metadata
@@ -116,7 +125,7 @@ export function isContract<T extends ContractDefinition>(
116
125
  return (
117
126
  typeof value === 'object' &&
118
127
  value !== null &&
119
- (value as ContractMeta<any>).__contract === true
128
+ (value as ContractMeta<ContractDefinition>).__contract === true
120
129
  );
121
130
  }
122
131
 
@@ -126,19 +135,19 @@ export function isContract<T extends ContractDefinition>(
126
135
 
127
136
  /** Contract에서 Input 타입 추출 */
128
137
  export type ContractInput<
129
- C extends Contract<any>,
138
+ C extends ContractMeta<ContractDefinition>,
130
139
  K extends keyof C['__endpoints']
131
140
  > = C['__endpoints'][K]['input'] extends ZodType<infer T> ? T : never;
132
141
 
133
142
  /** Contract에서 Output 타입 추출 */
134
143
  export type ContractOutput<
135
- C extends Contract<any>,
144
+ C extends ContractMeta<ContractDefinition>,
136
145
  K extends keyof C['__endpoints']
137
146
  > = C['__endpoints'][K]['output'] extends ZodType<infer T> ? T : never;
138
147
 
139
148
  /** Contract에서 Params 타입 추출 */
140
149
  export type ContractParams<
141
- C extends Contract<any>,
150
+ C extends ContractMeta<ContractDefinition>,
142
151
  K extends keyof C['__endpoints']
143
152
  > = C['__endpoints'][K]['params'] extends ZodType<infer T> ? T : never;
144
153
 
@@ -412,8 +421,7 @@ function generateSchemas<T extends ContractDefinition>(
412
421
  }
413
422
 
414
423
  function zodToOpenAPI(schema: ZodType): Record<string, unknown> {
415
- const def = (schema as any)._def;
416
- const typeName = def.typeName;
424
+ const typeName = getZodTypeName(schema);
417
425
 
418
426
  switch (typeName) {
419
427
  case 'ZodString':
@@ -422,25 +430,32 @@ function zodToOpenAPI(schema: ZodType): Record<string, unknown> {
422
430
  return { type: 'number' };
423
431
  case 'ZodBoolean':
424
432
  return { type: 'boolean' };
425
- case 'ZodArray':
426
- return { type: 'array', items: zodToOpenAPI(def.type) };
433
+ case 'ZodArray': {
434
+ const elementType = getZodArrayElementType(schema);
435
+ return { type: 'array', items: elementType ? zodToOpenAPI(elementType) : {} };
436
+ }
427
437
  case 'ZodObject': {
428
438
  const properties: Record<string, unknown> = {};
429
439
  const required: string[] = [];
430
- for (const [key, value] of Object.entries(def.shape())) {
431
- properties[key] = zodToOpenAPI(value as ZodType);
432
- if (!(value as any).isOptional?.()) {
440
+ const shape = getZodObjectShape(schema) ?? {};
441
+ for (const [key, value] of Object.entries(shape)) {
442
+ properties[key] = zodToOpenAPI(value);
443
+ if (isZodRequired(value)) {
433
444
  required.push(key);
434
445
  }
435
446
  }
436
447
  return { type: 'object', properties, required };
437
448
  }
438
- case 'ZodOptional':
439
- return zodToOpenAPI(def.innerType);
440
- case 'ZodNullable':
441
- return { ...zodToOpenAPI(def.innerType), nullable: true };
449
+ case 'ZodOptional': {
450
+ const inner = getZodInnerType(schema);
451
+ return inner ? zodToOpenAPI(inner) : {};
452
+ }
453
+ case 'ZodNullable': {
454
+ const inner = getZodInnerType(schema);
455
+ return { ...(inner ? zodToOpenAPI(inner) : {}), nullable: true };
456
+ }
442
457
  case 'ZodEnum':
443
- return { type: 'string', enum: def.values };
458
+ return { type: 'string', enum: getZodEnumValues(schema) };
444
459
  default:
445
460
  return { type: 'object' };
446
461
  }
@@ -5,13 +5,13 @@
5
5
  * Elysia 패턴 채택: Contract → Handler 타입 자동 추론
6
6
  */
7
7
 
8
- import type { z } from "zod";
9
- import type {
10
- ContractSchema,
11
- ContractMethod,
12
- MethodRequestSchema,
13
- } from "./schema";
14
- import type { InferResponseSchema } from "./types";
8
+ import type { z } from "zod";
9
+ import type {
10
+ ContractSchema,
11
+ ContractMethod,
12
+ MethodRequestSchema,
13
+ } from "./schema";
14
+ import type { InferResponseSchema } from "./types";
15
15
 
16
16
  /**
17
17
  * Typed request context for a handler
@@ -62,9 +62,9 @@ export type HandlerFn<TContext, TResponse> = (
62
62
  /**
63
63
  * Infer response type union from contract response schema
64
64
  */
65
- type InferResponseUnion<TResponse extends ContractSchema["response"]> = {
66
- [K in keyof TResponse]: InferResponseSchema<TResponse[K]>;
67
- }[keyof TResponse];
65
+ type InferResponseUnion<TResponse extends ContractSchema["response"]> = {
66
+ [K in keyof TResponse]: InferResponseSchema<TResponse[K]>;
67
+ }[keyof TResponse];
68
68
 
69
69
  /**
70
70
  * Handler definition for all methods in a contract
@@ -96,7 +96,7 @@ type InferResponseUnion<TResponse extends ContractSchema["response"]> = {
96
96
  * ```
97
97
  */
98
98
  export type ContractHandlers<T extends ContractSchema> = {
99
- [M in Extract<keyof T["request"], ContractMethod>]?: HandlerFn<
99
+ [M in ContractMethod as M extends keyof T["request"] ? M : never]?: HandlerFn<
100
100
  InferContextFromMethod<
101
101
  T["request"][M] extends MethodRequestSchema ? T["request"][M] : undefined
102
102
  >,
@@ -204,8 +204,5 @@ export const ManduContract = {
204
204
  fetch: contractFetch,
205
205
  } as const;
206
206
 
207
- /**
208
- * Alias for backward compatibility within contract module
209
- * 외부에서는 메인 index.ts의 Mandu를 사용하세요
210
- */
211
- export const Mandu = ManduContract;
207
+ // Note: The unified `Mandu` namespace is defined in the main index.ts.
208
+ // ManduContract is available for direct import from this module.
@@ -9,7 +9,8 @@
9
9
 
10
10
  import { describe, it, expect } from "bun:test";
11
11
  import { z } from "zod";
12
- import { Mandu, type InferContract, type InferQuery, type InferBody, type InferResponse } from "./index";
12
+ import { Mandu } from "../index";
13
+ import type { InferContract, InferQuery, InferBody, InferResponse } from "./index";
13
14
 
14
15
  // === Test Schemas ===
15
16
  const UserSchema = z.object({
@@ -42,7 +42,7 @@ describe("normalizeData", () => {
42
42
  const input = { name: "Kim", age: 25, admin: true };
43
43
  const result = normalizeData(schema, input, { mode: "passthrough" });
44
44
 
45
- expect(result).toEqual({ name: "Kim", age: 25, admin: true });
45
+ expect(result as Record<string, unknown>).toEqual({ name: "Kim", age: 25, admin: true });
46
46
  });
47
47
 
48
48
  test("기본 모드는 strip", () => {
@@ -21,6 +21,12 @@
21
21
  */
22
22
 
23
23
  import { z, type ZodTypeAny, type ZodObject, type ZodRawShape } from "zod";
24
+ import {
25
+ getZodChecks,
26
+ getZodInnerType,
27
+ getZodArrayElementType,
28
+ getZodDefaultValue,
29
+ } from "./zod-utils";
24
30
 
25
31
  /**
26
32
  * 정규화 모드
@@ -146,7 +152,7 @@ export function normalizeSchema<T extends ZodTypeAny>(
146
152
  return schema;
147
153
  }
148
154
 
149
- return applyNormalizeMode(schema, opts.mode) as T;
155
+ return applyNormalizeMode(schema, opts.mode) as unknown as T;
150
156
  }
151
157
 
152
158
  /**
@@ -249,18 +255,18 @@ function applyCoercion(schema: ZodTypeAny): ZodTypeAny {
249
255
  if (schema instanceof z.ZodNumber) {
250
256
  let coerced = z.coerce.number();
251
257
  // 기존 체크 유지 (min, max 등)
252
- const checks = (schema as any)._def.checks || [];
258
+ const checks = getZodChecks(schema);
253
259
  for (const check of checks) {
254
260
  switch (check.kind) {
255
261
  case "min":
256
262
  coerced = check.inclusive
257
- ? coerced.gte(check.value)
258
- : coerced.gt(check.value);
263
+ ? coerced.gte(check.value!)
264
+ : coerced.gt(check.value!);
259
265
  break;
260
266
  case "max":
261
267
  coerced = check.inclusive
262
- ? coerced.lte(check.value)
263
- : coerced.lt(check.value);
268
+ ? coerced.lte(check.value!)
269
+ : coerced.lt(check.value!);
264
270
  break;
265
271
  case "int":
266
272
  coerced = coerced.int();
@@ -294,23 +300,23 @@ function applyCoercion(schema: ZodTypeAny): ZodTypeAny {
294
300
 
295
301
  // ZodOptional → 내부 스키마에 coercion 적용
296
302
  if (schema instanceof z.ZodOptional) {
297
- return applyCoercion((schema as any)._def.innerType).optional();
303
+ return applyCoercion(getZodInnerType(schema)!).optional();
298
304
  }
299
305
 
300
306
  // ZodDefault → 내부 스키마에 coercion 적용
301
307
  if (schema instanceof z.ZodDefault) {
302
- const inner = applyCoercion((schema as any)._def.innerType);
303
- return inner.default((schema as any)._def.defaultValue());
308
+ const inner = applyCoercion(getZodInnerType(schema)!);
309
+ return inner.default(getZodDefaultValue(schema));
304
310
  }
305
311
 
306
312
  // ZodNullable → 내부 스키마에 coercion 적용
307
313
  if (schema instanceof z.ZodNullable) {
308
- return applyCoercion((schema as any)._def.innerType).nullable();
314
+ return applyCoercion(getZodInnerType(schema)!).nullable();
309
315
  }
310
316
 
311
317
  // ZodArray → 배열 요소에 coercion 적용 (쿼리스트링 배열)
312
318
  if (schema instanceof z.ZodArray) {
313
- return z.array(applyCoercion((schema as any)._def.type));
319
+ return z.array(applyCoercion(getZodArrayElementType(schema)!));
314
320
  }
315
321
 
316
322
  // 그 외는 원본 반환
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect } from "bun:test";
2
2
  import { diffContractRegistry, type ContractRegistry } from "./registry";
3
3
 
4
4
  describe("Contract registry diff", () => {
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Zod Internal Access Utilities
3
+ *
4
+ * Zod 스키마의 내부 `_def` 속성에 접근하는 로직을 중앙화합니다.
5
+ * `as any` 캐스팅은 이 파일 내부에만 존재하며,
6
+ * 외부에는 타입 안전한 API만 노출합니다.
7
+ */
8
+
9
+ import type { ZodTypeAny } from "zod";
10
+
11
+ // ============================================================================
12
+ // Internal _def access (as any is confined here)
13
+ // ============================================================================
14
+
15
+ interface ZodDef {
16
+ typeName: string;
17
+ innerType?: ZodTypeAny;
18
+ type?: ZodTypeAny;
19
+ schema?: ZodTypeAny;
20
+ shape?: () => Record<string, ZodTypeAny>;
21
+ checks?: ReadonlyArray<{ kind: string; value?: number; inclusive?: boolean; regex?: RegExp }>;
22
+ values?: readonly unknown[];
23
+ options?: readonly ZodTypeAny[];
24
+ value?: unknown;
25
+ defaultValue?: () => unknown;
26
+ description?: string;
27
+ }
28
+
29
+ /**
30
+ * Zod 스키마의 내부 _def 속성을 안전하게 추출합니다.
31
+ */
32
+ function getDef(schema: ZodTypeAny): ZodDef {
33
+ return (schema as any)._def;
34
+ }
35
+
36
+ // ============================================================================
37
+ // Type Name
38
+ // ============================================================================
39
+
40
+ /** Zod 스키마의 typeName을 반환합니다. */
41
+ export function getZodTypeName(schema: ZodTypeAny): string {
42
+ return getDef(schema).typeName;
43
+ }
44
+
45
+ // ============================================================================
46
+ // Inner Type Access
47
+ // ============================================================================
48
+
49
+ /**
50
+ * ZodOptional, ZodNullable, ZodDefault 등 래퍼 스키마의 내부 타입을 반환합니다.
51
+ * `_def.innerType`에 해당합니다.
52
+ */
53
+ export function getZodInnerType(schema: ZodTypeAny): ZodTypeAny | undefined {
54
+ return getDef(schema).innerType;
55
+ }
56
+
57
+ /**
58
+ * ZodArray의 요소 타입을 반환합니다.
59
+ * `_def.type`에 해당합니다.
60
+ */
61
+ export function getZodArrayElementType(schema: ZodTypeAny): ZodTypeAny | undefined {
62
+ return getDef(schema).type;
63
+ }
64
+
65
+ /**
66
+ * ZodEffects의 내부 스키마를 반환합니다.
67
+ * `_def.schema`에 해당합니다.
68
+ */
69
+ export function getZodEffectsSchema(schema: ZodTypeAny): ZodTypeAny | undefined {
70
+ return getDef(schema).schema;
71
+ }
72
+
73
+ // ============================================================================
74
+ // Object Shape
75
+ // ============================================================================
76
+
77
+ /**
78
+ * ZodObject의 shape를 반환합니다.
79
+ * `_def.shape()`에 해당합니다.
80
+ */
81
+ export function getZodObjectShape(schema: ZodTypeAny): Record<string, ZodTypeAny> | undefined {
82
+ const def = getDef(schema);
83
+ return typeof def.shape === "function" ? def.shape() : undefined;
84
+ }
85
+
86
+ // ============================================================================
87
+ // Checks (validation rules)
88
+ // ============================================================================
89
+
90
+ interface ZodCheck {
91
+ kind: string;
92
+ value?: number;
93
+ inclusive?: boolean;
94
+ regex?: RegExp;
95
+ }
96
+
97
+ /**
98
+ * ZodString, ZodNumber 등의 validation checks를 반환합니다.
99
+ * `_def.checks`에 해당합니다.
100
+ */
101
+ export function getZodChecks(schema: ZodTypeAny): readonly ZodCheck[] {
102
+ return getDef(schema).checks ?? [];
103
+ }
104
+
105
+ // ============================================================================
106
+ // Enum / Literal / Union
107
+ // ============================================================================
108
+
109
+ /** ZodEnum의 values를 반환합니다. */
110
+ export function getZodEnumValues(schema: ZodTypeAny): readonly unknown[] | undefined {
111
+ return getDef(schema).values;
112
+ }
113
+
114
+ /** ZodUnion의 options를 반환합니다. */
115
+ export function getZodUnionOptions(schema: ZodTypeAny): readonly ZodTypeAny[] | undefined {
116
+ return getDef(schema).options;
117
+ }
118
+
119
+ /** ZodLiteral의 value를 반환합니다. */
120
+ export function getZodLiteralValue(schema: ZodTypeAny): unknown {
121
+ return getDef(schema).value;
122
+ }
123
+
124
+ // ============================================================================
125
+ // Default Value
126
+ // ============================================================================
127
+
128
+ /**
129
+ * ZodDefault의 기본값을 반환합니다.
130
+ * `_def.defaultValue()`에 해당합니다.
131
+ */
132
+ export function getZodDefaultValue(schema: ZodTypeAny): unknown {
133
+ const fn = getDef(schema).defaultValue;
134
+ return typeof fn === "function" ? fn() : undefined;
135
+ }
136
+
137
+ // ============================================================================
138
+ // Type Predicates
139
+ // ============================================================================
140
+
141
+ /** 스키마가 ZodOptional인지 확인합니다. */
142
+ export function isZodOptional(schema: ZodTypeAny): boolean {
143
+ return getZodTypeName(schema) === "ZodOptional";
144
+ }
145
+
146
+ /** 스키마가 ZodDefault인지 확인합니다. */
147
+ export function isZodDefault(schema: ZodTypeAny): boolean {
148
+ return getZodTypeName(schema) === "ZodDefault";
149
+ }
150
+
151
+ /** 필드가 required인지 확인합니다 (Optional/Default가 아닌 경우). */
152
+ export function isZodRequired(schema: ZodTypeAny): boolean {
153
+ const typeName = getZodTypeName(schema);
154
+ return typeName !== "ZodOptional" && typeName !== "ZodDefault";
155
+ }
@@ -5,7 +5,7 @@
5
5
  * 전역 에러를 캐치하여 DevTools로 전달
6
6
  */
7
7
 
8
- import type { NormalizedError, ErrorType, Severity } from '../../types';
8
+ import type { NormalizedError, ErrorType, DevToolsSeverity } from '../../types';
9
9
  import { getOrCreateHook } from '../../hook';
10
10
  import { createErrorEvent } from '../../protocol';
11
11
 
@@ -69,7 +69,7 @@ function normalizeError(
69
69
  return {
70
70
  id: generateErrorId(),
71
71
  type,
72
- severity: determineSeverity(type, error),
72
+ severity: determineDevToolsSeverity(type, error),
73
73
  message: isError ? error.message : String(error),
74
74
  stack: isError ? error.stack : undefined,
75
75
  timestamp: Date.now(),
@@ -78,7 +78,7 @@ function normalizeError(
78
78
  };
79
79
  }
80
80
 
81
- function determineSeverity(type: ErrorType, _error: unknown): Severity {
81
+ function determineDevToolsSeverity(type: ErrorType, _error: unknown): DevToolsSeverity {
82
82
  switch (type) {
83
83
  case 'runtime':
84
84
  case 'unhandled':
@@ -158,7 +158,7 @@ export class NetworkProxy {
158
158
  const self = this;
159
159
  const originalFetch = this.originalFetch;
160
160
 
161
- (window as any).fetch = async function(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
161
+ const interceptedFetch = async function(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
162
162
  const url = typeof input === 'string'
163
163
  ? input
164
164
  : input instanceof URL
@@ -227,6 +227,10 @@ export class NetworkProxy {
227
227
  throw error;
228
228
  }
229
229
  };
230
+
231
+ // Bun's fetch type includes `preconnect`, use Object.assign to preserve it
232
+ Object.assign(interceptedFetch, { preconnect: window.fetch.preconnect });
233
+ window.fetch = interceptedFetch as typeof fetch;
230
234
  }
231
235
 
232
236
  // --------------------------------------------------------------------------
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React, { useEffect, useState, useCallback, useMemo } from 'react';
9
9
  import { createRoot, type Root } from 'react-dom/client';
10
- import type { NormalizedError, DevToolsConfig, IslandSnapshot, NetworkRequest, GuardViolation } from '../../types';
10
+ import type { NormalizedError, DevToolsConfig, IslandSnapshot, NetworkRequest, DevToolsGuardViolation } from '../../types';
11
11
  import { generateCSSVariables, testIds, zIndex } from '../../design-tokens';
12
12
  import { getStateManager, type KitchenState } from '../state-manager';
13
13
  import { getOrCreateHook } from '../../hook';
@@ -241,7 +241,7 @@ function KitchenApp({ config }: KitchenAppProps): React.ReactElement | null {
241
241
  }, []);
242
242
 
243
243
  const handleClearGuard = useCallback(() => {
244
- getStateManager().clearGuardViolations();
244
+ getStateManager().clearDevToolsGuardViolations();
245
245
  }, []);
246
246
 
247
247
  const handleRestart = useCallback(async () => {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React, { useMemo } from 'react';
7
- import type { GuardViolation } from '../../../types';
7
+ import type { DevToolsGuardViolation } from '../../../types';
8
8
  import { colors, typography, spacing, borderRadius, animation } from '../../../design-tokens';
9
9
 
10
10
  // ============================================================================
@@ -130,7 +130,7 @@ const severityStyles: Record<string, { border: string; bg: string; color: string
130
130
  // ============================================================================
131
131
 
132
132
  export interface GuardPanelProps {
133
- violations: GuardViolation[];
133
+ violations: DevToolsGuardViolation[];
134
134
  onClear: () => void;
135
135
  }
136
136
 
@@ -141,7 +141,7 @@ export interface GuardPanelProps {
141
141
  export function GuardPanel({ violations, onClear }: GuardPanelProps): React.ReactElement {
142
142
  // Group by rule
143
143
  const groupedByRule = useMemo(() => {
144
- const groups = new Map<string, GuardViolation[]>();
144
+ const groups = new Map<string, DevToolsGuardViolation[]>();
145
145
  for (const v of violations) {
146
146
  const existing = groups.get(v.ruleId) ?? [];
147
147
  groups.set(v.ruleId, [...existing, v]);
@@ -10,7 +10,7 @@ import type {
10
10
  NormalizedError,
11
11
  IslandSnapshot,
12
12
  NetworkRequest,
13
- GuardViolation,
13
+ DevToolsGuardViolation,
14
14
  DevToolsConfig,
15
15
  ManduState,
16
16
  } from '../types';
@@ -30,7 +30,7 @@ export interface KitchenState {
30
30
  errors: NormalizedError[];
31
31
  islands: Map<string, IslandSnapshot>;
32
32
  networkRequests: Map<string, NetworkRequest>;
33
- guardViolations: GuardViolation[];
33
+ guardViolations: DevToolsGuardViolation[];
34
34
 
35
35
  // Mandu Character State
36
36
  manduState: ManduState;
@@ -78,7 +78,7 @@ export class KitchenStateManager {
78
78
  private listeners: Set<StateListener> = new Set();
79
79
  private maxErrors = 100;
80
80
  private maxNetworkRequests = 200;
81
- private maxGuardViolations = 50;
81
+ private maxDevToolsGuardViolations = 50;
82
82
 
83
83
  constructor(config?: Partial<DevToolsConfig>) {
84
84
  this.state = createInitialState(config);
@@ -104,7 +104,7 @@ export class KitchenStateManager {
104
104
  return Array.from(this.state.networkRequests.values());
105
105
  }
106
106
 
107
- getGuardViolations(): GuardViolation[] {
107
+ getDevToolsGuardViolations(): DevToolsGuardViolation[] {
108
108
  return [...this.state.guardViolations];
109
109
  }
110
110
 
@@ -304,11 +304,11 @@ export class KitchenStateManager {
304
304
  // Guard Actions
305
305
  // --------------------------------------------------------------------------
306
306
 
307
- addGuardViolation(violation: GuardViolation): void {
307
+ addDevToolsGuardViolation(violation: DevToolsGuardViolation): void {
308
308
  const guardViolations = [violation, ...this.state.guardViolations];
309
309
 
310
310
  // 최대 개수 제한
311
- if (guardViolations.length > this.maxGuardViolations) {
311
+ if (guardViolations.length > this.maxDevToolsGuardViolations) {
312
312
  guardViolations.pop();
313
313
  }
314
314
 
@@ -321,7 +321,7 @@ export class KitchenStateManager {
321
321
  this.setState({ guardViolations, manduState });
322
322
  }
323
323
 
324
- clearGuardViolations(ruleId?: string): void {
324
+ clearDevToolsGuardViolations(ruleId?: string): void {
325
325
  if (ruleId) {
326
326
  const guardViolations = this.state.guardViolations.filter(
327
327
  (v) => v.ruleId !== ruleId
@@ -395,11 +395,11 @@ export class KitchenStateManager {
395
395
  break;
396
396
 
397
397
  case 'guard:violation':
398
- this.addGuardViolation(event.data as GuardViolation);
398
+ this.addDevToolsGuardViolation(event.data as DevToolsGuardViolation);
399
399
  break;
400
400
 
401
401
  case 'guard:clear':
402
- this.clearGuardViolations((event.data as { ruleId?: string }).ruleId);
402
+ this.clearDevToolsGuardViolations((event.data as { ruleId?: string }).ruleId);
403
403
  break;
404
404
 
405
405
  case 'hmr:update':