@mandujs/core 0.18.21 → 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 (61) hide show
  1. package/package.json +3 -1
  2. package/src/brain/doctor/analyzer.ts +1 -0
  3. package/src/brain/doctor/patcher.ts +10 -6
  4. package/src/brain/doctor/reporter.ts +2 -2
  5. package/src/brain/types.ts +14 -10
  6. package/src/bundler/build.ts +17 -17
  7. package/src/bundler/dev.ts +1 -1
  8. package/src/client/island.ts +10 -9
  9. package/src/client/router.ts +1 -1
  10. package/src/config/mcp-ref.ts +6 -6
  11. package/src/config/metadata.test.ts +1 -1
  12. package/src/config/metadata.ts +36 -16
  13. package/src/config/symbols.ts +1 -1
  14. package/src/config/validate.ts +16 -0
  15. package/src/content/content.test.ts +3 -3
  16. package/src/content/loaders/file.ts +3 -0
  17. package/src/content/loaders/glob.ts +1 -0
  18. package/src/contract/client-safe.test.ts +1 -1
  19. package/src/contract/client.ts +18 -18
  20. package/src/contract/define.ts +29 -14
  21. package/src/contract/handler.ts +11 -11
  22. package/src/contract/normalize.test.ts +1 -1
  23. package/src/contract/normalize.ts +16 -10
  24. package/src/contract/registry.test.ts +1 -1
  25. package/src/contract/zod-utils.ts +155 -0
  26. package/src/devtools/client/catchers/network-proxy.ts +5 -1
  27. package/src/devtools/init.ts +2 -2
  28. package/src/devtools/server/source-context.ts +9 -3
  29. package/src/devtools/worker/redaction-worker.ts +12 -5
  30. package/src/error/index.ts +1 -1
  31. package/src/error/result.ts +14 -0
  32. package/src/filling/deps.ts +1 -1
  33. package/src/generator/templates.ts +2 -2
  34. package/src/guard/contract-guard.test.ts +1 -0
  35. package/src/guard/file-type.test.ts +1 -1
  36. package/src/guard/negotiation.ts +29 -1
  37. package/src/index.ts +1 -0
  38. package/src/intent/index.ts +28 -17
  39. package/src/island/index.ts +2 -2
  40. package/src/openapi/generator.ts +49 -31
  41. package/src/plugins/registry.ts +28 -18
  42. package/src/resource/__tests__/backward-compat.test.ts +2 -2
  43. package/src/resource/__tests__/edge-cases.test.ts +14 -13
  44. package/src/resource/__tests__/fixtures.ts +2 -2
  45. package/src/resource/__tests__/generator.test.ts +1 -1
  46. package/src/resource/__tests__/performance.test.ts +8 -6
  47. package/src/resource/schema.ts +1 -1
  48. package/src/router/fs-routes.ts +29 -35
  49. package/src/runtime/logger.test.ts +3 -3
  50. package/src/runtime/logger.ts +1 -1
  51. package/src/runtime/server.ts +8 -6
  52. package/src/runtime/stable-selector.ts +1 -2
  53. package/src/runtime/streaming-ssr.ts +11 -2
  54. package/src/seo/index.ts +5 -0
  55. package/src/seo/integration/ssr.ts +2 -2
  56. package/src/seo/resolve/url.ts +7 -0
  57. package/src/seo/types.ts +13 -0
  58. package/src/spec/schema.ts +82 -54
  59. package/src/types/branded.ts +56 -0
  60. package/src/types/index.ts +1 -0
  61. package/src/utils/hasher.test.ts +6 -6
@@ -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
 
@@ -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
  >,
@@ -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
  * 정규화 모드
@@ -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
+ }
@@ -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
  // --------------------------------------------------------------------------
@@ -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: () => {},
@@ -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));
@@ -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: {
@@ -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";
@@ -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
 
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export * from "./intent";
23
23
  export * from "./devtools";
24
24
  export * from "./paths";
25
25
  export * from "./resource";
26
+ export * from "./types";
26
27
 
27
28
  // ── Resolve export * ambiguities (TS2308) ──
28
29
  // When the same name is exported from multiple submodules via `export *`,