@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
@@ -16,11 +16,11 @@ export type {
16
16
 
17
17
  // Error
18
18
  ErrorType,
19
- Severity,
19
+ DevToolsSeverity,
20
20
  NormalizedError,
21
21
 
22
22
  // Island
23
- HydrationStrategy,
23
+ DevToolsHydrationStrategy,
24
24
  IslandStatus,
25
25
  IslandSnapshot,
26
26
 
@@ -29,7 +29,7 @@ export type {
29
29
  NetworkBodyPolicy,
30
30
 
31
31
  // Guard
32
- GuardViolation,
32
+ DevToolsGuardViolation,
33
33
 
34
34
  // AI Context
35
35
  CodeContextInfo,
@@ -170,7 +170,7 @@ export {
170
170
  createIslandHydrateEndEvent,
171
171
  createNetworkRequestEvent,
172
172
  createNetworkResponseEvent,
173
- createGuardViolationEvent,
173
+ createDevToolsGuardViolationEvent,
174
174
  createHmrUpdateEvent,
175
175
  createHmrErrorEvent,
176
176
 
@@ -199,12 +199,12 @@ export {
199
199
  // ============================================================================
200
200
 
201
201
  import { getOrCreateHook } from './hook';
202
- import type { NormalizedError, GuardViolation } from './types';
202
+ import type { NormalizedError, DevToolsGuardViolation } from './types';
203
203
  import {
204
204
  createErrorEvent,
205
205
  createHmrUpdateEvent,
206
206
  createHmrErrorEvent,
207
- createGuardViolationEvent,
207
+ createDevToolsGuardViolationEvent,
208
208
  } from './protocol';
209
209
 
210
210
  /**
@@ -261,10 +261,10 @@ export function notifyHmrError(message: string, stack?: string): void {
261
261
  * Guard 위반 리포트 (간편 API)
262
262
  */
263
263
  export function reportGuardViolation(
264
- violation: Omit<GuardViolation, 'id' | 'timestamp'>
264
+ violation: Omit<DevToolsGuardViolation, 'id' | 'timestamp'>
265
265
  ): void {
266
266
  const hook = getOrCreateHook();
267
- hook.emit(createGuardViolationEvent(violation));
267
+ hook.emit(createDevToolsGuardViolationEvent(violation));
268
268
  }
269
269
 
270
270
  // ============================================================================
@@ -30,7 +30,7 @@ export interface KitchenInstance {
30
30
  /** DevTools 언마운트 및 정리 */
31
31
  destroy: () => void;
32
32
  /** 상태 관리자 접근 */
33
- getState: () => any;
33
+ getState: () => Record<string, unknown>;
34
34
  /** 에러 리포트 */
35
35
  reportError: (error: Error | string) => void;
36
36
  /** DevTools 열기 */
@@ -213,7 +213,7 @@ function createInstance(): KitchenInstance {
213
213
  function createNoopInstance(): KitchenInstance {
214
214
  return {
215
215
  destroy: () => {},
216
- getState: () => ({} as any),
216
+ getState: () => ({}),
217
217
  reportError: () => {},
218
218
  open: () => {},
219
219
  close: () => {},
@@ -8,7 +8,7 @@ import type {
8
8
  NormalizedError,
9
9
  IslandSnapshot,
10
10
  NetworkRequest,
11
- GuardViolation,
11
+ DevToolsGuardViolation,
12
12
  } from './types';
13
13
 
14
14
  // ============================================================================
@@ -33,7 +33,7 @@ export type KitchenEvents =
33
33
  | KitchenEvent<'network:error', { id: string; error: string }>
34
34
 
35
35
  // Guard events
36
- | KitchenEvent<'guard:violation', GuardViolation>
36
+ | KitchenEvent<'guard:violation', DevToolsGuardViolation>
37
37
  | KitchenEvent<'guard:clear', { ruleId?: string }>
38
38
 
39
39
  // HMR events
@@ -144,8 +144,8 @@ export function createNetworkResponseEvent(
144
144
  };
145
145
  }
146
146
 
147
- export function createGuardViolationEvent(
148
- violation: Omit<GuardViolation, 'id' | 'timestamp'>
147
+ export function createDevToolsGuardViolationEvent(
148
+ violation: Omit<DevToolsGuardViolation, 'id' | 'timestamp'>
149
149
  ): KitchenEvents {
150
150
  return {
151
151
  type: 'guard:violation',
@@ -235,8 +235,14 @@ export interface SourcemapParseResult {
235
235
  /**
236
236
  * 간단한 Sourcemap 파서 (Base64 VLQ 디코딩)
237
237
  */
238
+ interface RawSourcemap {
239
+ mappings: string;
240
+ sources: string[];
241
+ names?: string[];
242
+ }
243
+
238
244
  export class SourcemapParser {
239
- private sourcemap: any;
245
+ private sourcemap: RawSourcemap;
240
246
 
241
247
  constructor(sourcemapContent: string) {
242
248
  try {
@@ -405,7 +411,7 @@ export function createViteMiddleware(projectRoot: string) {
405
411
  const provider = new SourceContextProvider({ projectRoot });
406
412
  const handler = provider.createHandler();
407
413
 
408
- return async (req: any, res: any, next: () => void) => {
414
+ return async (req: { url?: string }, res: { setHeader: (key: string, value: string) => void; statusCode: number; end: (data: string) => void }, next: () => void) => {
409
415
  // __mandu_source__ 엔드포인트만 처리
410
416
  if (!req.url?.startsWith('/api/__mandu_source__')) {
411
417
  return next();
@@ -435,7 +441,7 @@ export function manduSourceContextPlugin(options?: Partial<SourceContextProvider
435
441
  return {
436
442
  name: 'mandu-source-context',
437
443
 
438
- configureServer(server: any) {
444
+ configureServer(server: { config: { root?: string }; middlewares: { use: (middleware: ReturnType<typeof createViteMiddleware>) => void } }) {
439
445
  const projectRoot = options?.projectRoot ?? server.config.root ?? process.cwd();
440
446
 
441
447
  server.middlewares.use(createViteMiddleware(projectRoot));
@@ -18,12 +18,12 @@ export interface KitchenEvent<T extends string = string, D = unknown> {
18
18
  // ============================================================================
19
19
 
20
20
  export type ErrorType = 'runtime' | 'unhandled' | 'react' | 'network' | 'hmr' | 'guard';
21
- export type Severity = 'critical' | 'error' | 'warning' | 'info';
21
+ export type DevToolsSeverity = 'critical' | 'error' | 'warning' | 'info';
22
22
 
23
23
  export interface NormalizedError {
24
24
  id: string;
25
25
  type: ErrorType;
26
- severity: Severity;
26
+ severity: DevToolsSeverity;
27
27
  message: string;
28
28
  stack?: string;
29
29
  source?: string;
@@ -39,13 +39,13 @@ export interface NormalizedError {
39
39
  // Island Types
40
40
  // ============================================================================
41
41
 
42
- export type HydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'never';
42
+ export type DevToolsHydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'never';
43
43
  export type IslandStatus = 'ssr' | 'pending' | 'hydrating' | 'hydrated' | 'error';
44
44
 
45
45
  export interface IslandSnapshot {
46
46
  id: string;
47
47
  name: string;
48
- strategy: HydrationStrategy;
48
+ strategy: DevToolsHydrationStrategy;
49
49
  status: IslandStatus;
50
50
  ssrRenderTime?: number;
51
51
  hydrateStartTime?: number;
@@ -89,7 +89,7 @@ export interface NetworkBodyPolicy {
89
89
  // Guard Types
90
90
  // ============================================================================
91
91
 
92
- export interface GuardViolation {
92
+ export interface DevToolsGuardViolation {
93
93
  id: string;
94
94
  ruleId: string;
95
95
  ruleName: string;
@@ -185,12 +185,14 @@ function handleMessage(request: WorkerRequest): WorkerResponse {
185
185
  };
186
186
  }
187
187
 
188
- default:
188
+ default: {
189
+ const unknownType: string = request.type;
189
190
  return {
190
191
  id: request.id,
191
192
  success: false,
192
- error: `Unknown request type: ${(request as any).type}`,
193
+ error: `Unknown request type: ${unknownType}`,
193
194
  };
195
+ }
194
196
  }
195
197
  } catch (error) {
196
198
  return {
@@ -202,10 +204,15 @@ function handleMessage(request: WorkerRequest): WorkerResponse {
202
204
  }
203
205
 
204
206
  // Worker 컨텍스트에서만 실행
205
- if (typeof self !== 'undefined' && typeof (self as any).postMessage === 'function') {
206
- self.onmessage = (event: MessageEvent<WorkerRequest>) => {
207
+ interface WorkerSelf {
208
+ postMessage: (message: WorkerResponse) => void;
209
+ onmessage: ((event: MessageEvent<WorkerRequest>) => void) | null;
210
+ }
211
+ const workerSelf = typeof self !== 'undefined' ? (self as unknown as WorkerSelf) : undefined;
212
+ if (workerSelf && typeof workerSelf.postMessage === 'function') {
213
+ workerSelf.onmessage = (event: MessageEvent<WorkerRequest>) => {
207
214
  const response = handleMessage(event.data);
208
- (self as any).postMessage(response);
215
+ workerSelf.postMessage(response);
209
216
  };
210
217
  }
211
218
 
@@ -38,4 +38,4 @@ export {
38
38
 
39
39
  // Result helpers
40
40
  export type { Result } from "./result";
41
- export { ok, err, statusFromError, errorToResponse } from "./result";
41
+ export { ok, err, isOk, isErr, statusFromError, errorToResponse } from "./result";
@@ -12,6 +12,20 @@ export type Result<T> =
12
12
  export const ok = <T>(value: T): Result<T> => ({ ok: true, value });
13
13
  export const err = (error: ManduError): Result<never> => ({ ok: false, error });
14
14
 
15
+ /**
16
+ * Result가 성공인지 검사하는 type guard
17
+ */
18
+ export function isOk<T>(result: Result<T>): result is { ok: true; value: T } {
19
+ return result.ok === true;
20
+ }
21
+
22
+ /**
23
+ * Result가 실패인지 검사하는 type guard
24
+ */
25
+ export function isErr<T>(result: Result<T>): result is { ok: false; error: ManduError } {
26
+ return result.ok === false;
27
+ }
28
+
15
29
  /**
16
30
  * ManduError -> HTTP status 매핑
17
31
  */
@@ -163,7 +163,7 @@ export function createMockDeps(overrides: Partial<FillingDeps> = {}): FillingDep
163
163
 
164
164
  return {
165
165
  db: {
166
- query: async () => [] as any,
166
+ query: async () => [] as never,
167
167
  transaction: async (fn) => fn(),
168
168
  },
169
169
  cache: {
@@ -171,7 +171,10 @@ export function createMockDeps(overrides: Partial<FillingDeps> = {}): FillingDep
171
171
  set: asyncNoop,
172
172
  delete: asyncNoop,
173
173
  },
174
- fetch: async () => new Response(),
174
+ fetch: Object.assign(
175
+ async (): Promise<Response> => new Response(),
176
+ { preconnect: (_url: string) => {} }
177
+ ) as typeof fetch,
175
178
  logger: {
176
179
  debug: noop,
177
180
  info: noop,
@@ -361,7 +361,7 @@ export class ManduFilling<TLoaderData = unknown> {
361
361
  if (error instanceof ValidationError) {
362
362
  return ctx.json({ errorType: "LOGIC_ERROR", code: ErrorCode.SLOT_VALIDATION_ERROR, message: "Validation failed", summary: "입력 검증 실패 - 요청 데이터 확인 필요", fix: { file: routeContext ? `spec/slots/${routeContext.routeId}.slot.ts` : "spec/slots/", suggestion: "요청 데이터가 스키마와 일치하는지 확인하세요" }, route: routeContext, errors: error.errors, timestamp: new Date().toISOString() }, 400);
363
363
  }
364
- const classifier = new ErrorClassifier(null, routeContext);
364
+ const classifier = new ErrorClassifier(null, routeContext ? { id: routeContext.routeId, pattern: routeContext.pattern } : undefined);
365
365
  const manduError = classifier.classify(error);
366
366
  console.error(`[Mandu] ${manduError.errorType}:`, manduError.message);
367
367
  const response = formatErrorResponse(manduError, { isDev: process.env.NODE_ENV !== "production" });
@@ -304,7 +304,7 @@ interface Props {
304
304
  }
305
305
 
306
306
  function ${pageName}Page({ params, loaderData }: Props): React.ReactElement {
307
- const serverData = (loaderData || {}) as any;
307
+ const serverData = (loaderData || {}) as Record<string, unknown>;
308
308
  const setupResult = islandModule.definition.setup(serverData);
309
309
  return islandModule.definition.render(setupResult) as React.ReactElement;
310
310
  }
@@ -333,7 +333,7 @@ interface Props {
333
333
  }
334
334
 
335
335
  export default function ${pageName}Page({ params, loaderData }: Props): React.ReactElement {
336
- const serverData = (loaderData || {}) as any;
336
+ const serverData = (loaderData || {}) as Record<string, unknown>;
337
337
  const setupResult = islandModule.definition.setup(serverData);
338
338
  return islandModule.definition.render(setupResult) as React.ReactElement;
339
339
  }
@@ -78,6 +78,7 @@ describe("Contract Guard", () => {
78
78
  pattern: "/",
79
79
  kind: "page",
80
80
  module: "generated/routes/home.ts",
81
+ componentModule: "generated/routes/home.tsx",
81
82
  slotModule: "spec/slots/home.slot.ts",
82
83
  // No contractModule - but that's fine for pages
83
84
  },
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect } from "bun:test";
2
2
  import { validateFileAnalysis } from "./validator";
3
3
  import { fsdPreset } from "./presets/fsd";
4
4
  import type { FileAnalysis, GuardConfig } from "./types";
@@ -272,7 +272,7 @@ export {
272
272
  type SlotMetadata,
273
273
  type CustomRule,
274
274
  type ConstraintViolation,
275
- type SlotValidationResult,
275
+ type SemanticSlotValidationResult,
276
276
  } from "./semantic-slots";
277
277
 
278
278
  // ═══════════════════════════════════════════════════════════════════════════
@@ -988,16 +988,44 @@ export interface ${toPascalCase(name)}Dto {
988
988
  export interface ${toPascalCase(name)}ResponseDto {
989
989
  // TODO: Define response DTO fields
990
990
  }
991
+ `;
992
+
993
+ case "controller":
994
+ return `/**
995
+ * ${purpose}
996
+ *
997
+ * Controller - 요청/응답 처리
998
+ */
999
+
1000
+ export class ${toPascalCase(name)}Controller {
1001
+ // TODO: Implement controller methods
1002
+ }
1003
+ `;
1004
+
1005
+ case "hook":
1006
+ return `/**
1007
+ * ${purpose}
1008
+ *
1009
+ * Custom Hook
1010
+ */
1011
+
1012
+ export function use${toPascalCase(name)}() {
1013
+ // TODO: Implement hook logic
1014
+ }
991
1015
  `;
992
1016
 
993
1017
  case "util":
994
- default:
995
1018
  return `/**
996
1019
  * ${purpose}
997
1020
  */
998
1021
 
999
1022
  // TODO: Implement utility functions
1000
1023
  `;
1024
+
1025
+ default: {
1026
+ const _exhaustive: never = template;
1027
+ throw new Error(`Unhandled file template: ${_exhaustive}`);
1028
+ }
1001
1029
  }
1002
1030
  }
1003
1031
 
@@ -11,6 +11,9 @@ import { hexagonalPreset, HEXAGONAL_HIERARCHY } from "./hexagonal";
11
11
  import { atomicPreset, ATOMIC_HIERARCHY } from "./atomic";
12
12
  import { cqrsPreset, CQRS_HIERARCHY } from "./cqrs";
13
13
 
14
+ // Re-export types
15
+ export type { GuardPreset, PresetDefinition } from "../types";
16
+
14
17
  // Re-export
15
18
  export { fsdPreset, FSD_HIERARCHY } from "./fsd";
16
19
  export { cleanPreset, CLEAN_HIERARCHY } from "./clean";
@@ -144,7 +144,7 @@ export interface ConstraintViolation {
144
144
  /**
145
145
  * 슬롯 검증 결과
146
146
  */
147
- export interface SlotValidationResult {
147
+ export interface SemanticSlotValidationResult {
148
148
  /** 유효 여부 */
149
149
  valid: boolean;
150
150
 
@@ -461,7 +461,7 @@ export async function validateSlotConstraints(
461
461
  filePath: string,
462
462
  constraints: SlotConstraints,
463
463
  rootDir?: string
464
- ): Promise<SlotValidationResult> {
464
+ ): Promise<SemanticSlotValidationResult> {
465
465
  const violations: ConstraintViolation[] = [];
466
466
  const suggestions: string[] = [];
467
467
 
@@ -727,9 +727,9 @@ export async function validateSlots(
727
727
  totalSlots: number;
728
728
  validSlots: number;
729
729
  invalidSlots: number;
730
- results: SlotValidationResult[];
730
+ results: SemanticSlotValidationResult[];
731
731
  }> {
732
- const results: SlotValidationResult[] = [];
732
+ const results: SemanticSlotValidationResult[] = [];
733
733
 
734
734
  for (const filePath of slotFiles) {
735
735
  // 파일에서 메타데이터 추출 시도
package/src/index.ts CHANGED
@@ -23,12 +23,21 @@ export * from "./intent";
23
23
  export * from "./devtools";
24
24
  export * from "./paths";
25
25
  export * from "./resource";
26
+ export * from "./types";
27
+
28
+ // ── Resolve export * ambiguities (TS2308) ──
29
+ // When the same name is exported from multiple submodules via `export *`,
30
+ // TypeScript considers them ambiguous. Explicit re-exports resolve this.
31
+ export { formatViolation } from "./guard";
32
+ export { type HttpMethod } from "./filling";
33
+ export { type GuardViolation } from "./guard";
34
+ export { type Severity } from "./guard";
26
35
 
27
36
  // Consolidated Mandu namespace
28
37
  import { ManduFilling, ManduContext, ManduFillingFactory, createSSEConnection } from "./filling";
29
38
  import { createContract, defineHandler, defineRoute, createClient, contractFetch, createClientContract, querySchema, bodySchema, apiError } from "./contract";
30
39
  import { defineContract, generateAllFromContract, generateOpenAPISpec } from "./contract/define";
31
- import { island, isIsland, type IslandComponent, type HydrationStrategy } from "./island";
40
+ import { island, isIsland, type IslandComponent, type IslandHydrationStrategy } from "./island";
32
41
  import { intent, isIntent, getIntentDocs, generateOpenAPIFromIntent } from "./intent";
33
42
  import { initializeHook, reportError, ManduDevTools, getStateManager } from "./devtools";
34
43
  import type { ContractDefinition, ContractInstance, ContractSchema } from "./contract";
@@ -25,6 +25,8 @@
25
25
  import { z, type ZodType } from 'zod';
26
26
  import { ManduFillingFactory, type ManduFilling } from '../filling/filling';
27
27
  import type { ManduContext } from '../filling/context';
28
+ import { getZodTypeName, getZodObjectShape, getZodArrayElementType } from '../contract/zod-utils';
29
+ import type { ZodTypeAny } from 'zod';
28
30
 
29
31
  // ============================================================================
30
32
  // Types
@@ -51,7 +53,7 @@ export interface IntentDefinition<TInput = unknown, TOutput = unknown> {
51
53
  guard?: (ctx: ManduContext) => Response | void | Promise<Response | void>;
52
54
  }
53
55
 
54
- export type IntentMap = Record<string, IntentDefinition<any, any>>;
56
+ export type IntentMap = Record<string, IntentDefinition<unknown, unknown>>;
55
57
 
56
58
  export interface IntentMeta {
57
59
  __intent: true;
@@ -86,7 +88,7 @@ export function intent(intents: IntentMap): ManduFilling & IntentMeta {
86
88
  const docs: IntentDocumentation[] = [];
87
89
 
88
90
  // 메서드별로 핸들러 그룹화
89
- const methodHandlers: Record<HttpMethod, IntentDefinition<any, any>[]> = {
91
+ const methodHandlers: Record<HttpMethod, IntentDefinition<unknown, unknown>[]> = {
90
92
  GET: [],
91
93
  POST: [],
92
94
  PUT: [],
@@ -115,12 +117,13 @@ export function intent(intents: IntentMap): ManduFilling & IntentMeta {
115
117
  }
116
118
 
117
119
  // 각 메서드에 대해 핸들러 등록
118
- const registerMethod = (method: HttpMethod, handlers: IntentDefinition<any, any>[]) => {
120
+ const registerMethod = (method: HttpMethod, handlers: IntentDefinition<unknown, unknown>[]) => {
119
121
  if (handlers.length === 0) return;
120
122
 
121
123
  const methodLower = method.toLowerCase() as Lowercase<HttpMethod>;
124
+ const fillingWithMethods = filling as ManduFilling & Record<Lowercase<HttpMethod>, (handler: (ctx: ManduContext) => Response | Promise<Response>) => ManduFilling>;
122
125
 
123
- (filling as any)[methodLower](async (ctx: ManduContext) => {
126
+ fillingWithMethods[methodLower](async (ctx: ManduContext) => {
124
127
  // 경로 매칭 (path가 있는 경우)
125
128
  for (const def of handlers) {
126
129
  if (def.path && !matchPath(ctx.url, def.path)) {
@@ -135,11 +138,15 @@ export function intent(intents: IntentMap): ManduFilling & IntentMeta {
135
138
  }
136
139
  }
137
140
 
138
- // Input 검증
141
+ // Input 검증 (ctx.body() throws ValidationError on schema mismatch)
139
142
  if (def.input && ['POST', 'PUT', 'PATCH'].includes(method)) {
140
- const bodyResult = await ctx.body(def.input);
141
- if (!bodyResult.success) {
142
- return ctx.error('Validation failed', bodyResult.error);
143
+ try {
144
+ await ctx.body(def.input);
145
+ } catch (validationError) {
146
+ return ctx.error(
147
+ 'Validation failed',
148
+ validationError instanceof Error ? validationError : new Error(String(validationError))
149
+ );
143
150
  }
144
151
  }
145
152
 
@@ -247,26 +254,30 @@ export function generateOpenAPIFromIntent(
247
254
  */
248
255
  function zodToJsonSchema(schema: ZodType<unknown>): Record<string, unknown> {
249
256
  // 실제 구현은 zod-to-json-schema 라이브러리 사용 권장
250
- const def = (schema as any)._def;
257
+ const typeName = getZodTypeName(schema as ZodTypeAny);
251
258
 
252
- if (def.typeName === 'ZodString') {
259
+ if (typeName === 'ZodString') {
253
260
  return { type: 'string' };
254
261
  }
255
- if (def.typeName === 'ZodNumber') {
262
+ if (typeName === 'ZodNumber') {
256
263
  return { type: 'number' };
257
264
  }
258
- if (def.typeName === 'ZodBoolean') {
265
+ if (typeName === 'ZodBoolean') {
259
266
  return { type: 'boolean' };
260
267
  }
261
- if (def.typeName === 'ZodObject') {
268
+ if (typeName === 'ZodObject') {
262
269
  const properties: Record<string, unknown> = {};
263
- for (const [key, value] of Object.entries(def.shape())) {
264
- properties[key] = zodToJsonSchema(value as ZodType<unknown>);
270
+ const shape = getZodObjectShape(schema as ZodTypeAny);
271
+ if (shape) {
272
+ for (const [key, value] of Object.entries(shape)) {
273
+ properties[key] = zodToJsonSchema(value as ZodType<unknown>);
274
+ }
265
275
  }
266
276
  return { type: 'object', properties };
267
277
  }
268
- if (def.typeName === 'ZodArray') {
269
- return { type: 'array', items: zodToJsonSchema(def.type) };
278
+ if (typeName === 'ZodArray') {
279
+ const elementType = getZodArrayElementType(schema as ZodTypeAny);
280
+ return { type: 'array', items: elementType ? zodToJsonSchema(elementType as ZodType<unknown>) : {} };
270
281
  }
271
282
 
272
283
  return { type: 'unknown' };
@@ -20,7 +20,7 @@ import { z } from 'zod';
20
20
  // ============================================================================
21
21
 
22
22
  /** 하이드레이션 타이밍 */
23
- export type HydrationStrategy =
23
+ export type IslandHydrationStrategy =
24
24
  | 'load' // 페이지 로드 즉시
25
25
  | 'idle' // requestIdleCallback
26
26
  | 'visible' // IntersectionObserver
@@ -30,7 +30,7 @@ export type HydrationStrategy =
30
30
  /** Island 옵션 */
31
31
  export interface IslandOptions<P = unknown> {
32
32
  /** 하이드레이션 전략 */
33
- hydrate: HydrationStrategy;
33
+ hydrate: IslandHydrationStrategy;
34
34
  /** 미디어 쿼리 (hydrate: 'media' 일 때) */
35
35
  media?: string;
36
36
  /** SSR 폴백 컴포넌트 */
@@ -44,7 +44,7 @@ export interface IslandOptions<P = unknown> {
44
44
  /** Island 컴포넌트 메타데이터 */
45
45
  export interface IslandMeta {
46
46
  __island: true;
47
- __hydrate: HydrationStrategy;
47
+ __hydrate: IslandHydrationStrategy;
48
48
  __media?: string;
49
49
  __fallback?: ReactNode;
50
50
  __name: string;
@@ -95,7 +95,7 @@ export function getAllIslands(): Map<string, IslandComponent<any>> {
95
95
  * });
96
96
  */
97
97
  export function island<P extends Record<string, unknown>>(
98
- strategy: HydrationStrategy,
98
+ strategy: IslandHydrationStrategy,
99
99
  Component: ComponentType<P>
100
100
  ): IslandComponent<P>;
101
101
 
@@ -105,7 +105,7 @@ export function island<P extends Record<string, unknown>>(
105
105
  ): IslandComponent<P>;
106
106
 
107
107
  export function island<P extends Record<string, unknown>>(
108
- strategyOrOptions: HydrationStrategy | IslandOptions<P>,
108
+ strategyOrOptions: IslandHydrationStrategy | IslandOptions<P>,
109
109
  Component: ComponentType<P>
110
110
  ): IslandComponent<P> {
111
111
  const options: IslandOptions<P> = typeof strategyOrOptions === 'string'
@@ -133,10 +133,10 @@ export function island<P extends Record<string, unknown>>(
133
133
  // isIsland() - Island 컴포넌트 체크
134
134
  // ============================================================================
135
135
 
136
- export function isIsland(component: unknown): component is IslandComponent<unknown> {
136
+ export function isIsland(component: unknown): component is IslandComponent<any> {
137
137
  return (
138
138
  typeof component === 'function' &&
139
- (component as IslandComponent<unknown>).__island === true
139
+ (component as IslandComponent<any>).__island === true
140
140
  );
141
141
  }
142
142
 
@@ -194,7 +194,7 @@ export function deserializeIslandProps(json: string): Record<string, unknown> {
194
194
  export interface IslandPlaceholderProps {
195
195
  name: string;
196
196
  props: Record<string, unknown>;
197
- hydrate: HydrationStrategy;
197
+ hydrate: IslandHydrationStrategy;
198
198
  media?: string;
199
199
  fallback?: ReactNode;
200
200
  }