@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.
- package/package.json +3 -1
- package/src/brain/doctor/analyzer.ts +1 -0
- package/src/brain/doctor/patcher.ts +10 -6
- package/src/brain/doctor/reporter.ts +2 -2
- package/src/brain/types.ts +14 -10
- package/src/bundler/build.ts +17 -17
- package/src/bundler/dev.ts +1 -1
- package/src/client/island.ts +10 -9
- package/src/client/router.ts +1 -1
- package/src/config/mcp-ref.ts +6 -6
- package/src/config/metadata.test.ts +1 -1
- package/src/config/metadata.ts +36 -16
- package/src/config/symbols.ts +1 -1
- package/src/config/validate.ts +16 -0
- package/src/content/content.test.ts +3 -3
- package/src/content/loaders/file.ts +3 -0
- package/src/content/loaders/glob.ts +1 -0
- package/src/contract/client-safe.test.ts +1 -1
- package/src/contract/client.ts +18 -18
- package/src/contract/define.ts +29 -14
- package/src/contract/handler.ts +11 -11
- package/src/contract/normalize.test.ts +1 -1
- package/src/contract/normalize.ts +16 -10
- package/src/contract/registry.test.ts +1 -1
- package/src/contract/zod-utils.ts +155 -0
- package/src/devtools/client/catchers/network-proxy.ts +5 -1
- package/src/devtools/init.ts +2 -2
- package/src/devtools/server/source-context.ts +9 -3
- package/src/devtools/worker/redaction-worker.ts +12 -5
- package/src/error/index.ts +1 -1
- package/src/error/result.ts +14 -0
- package/src/filling/deps.ts +1 -1
- package/src/generator/templates.ts +2 -2
- package/src/guard/contract-guard.test.ts +1 -0
- package/src/guard/file-type.test.ts +1 -1
- package/src/guard/negotiation.ts +29 -1
- package/src/index.ts +1 -0
- package/src/intent/index.ts +28 -17
- package/src/island/index.ts +2 -2
- package/src/openapi/generator.ts +49 -31
- package/src/plugins/registry.ts +28 -18
- package/src/resource/__tests__/backward-compat.test.ts +2 -2
- package/src/resource/__tests__/edge-cases.test.ts +14 -13
- package/src/resource/__tests__/fixtures.ts +2 -2
- package/src/resource/__tests__/generator.test.ts +1 -1
- package/src/resource/__tests__/performance.test.ts +8 -6
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-routes.ts +29 -35
- package/src/runtime/logger.test.ts +3 -3
- package/src/runtime/logger.ts +1 -1
- package/src/runtime/server.ts +8 -6
- package/src/runtime/stable-selector.ts +1 -2
- package/src/runtime/streaming-ssr.ts +11 -2
- package/src/seo/index.ts +5 -0
- package/src/seo/integration/ssr.ts +2 -2
- package/src/seo/resolve/url.ts +7 -0
- package/src/seo/types.ts +13 -0
- package/src/spec/schema.ts +82 -54
- package/src/types/branded.ts +56 -0
- package/src/types/index.ts +1 -0
- package/src/utils/hasher.test.ts +6 -6
package/src/contract/define.ts
CHANGED
|
@@ -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
|
|
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<
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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:
|
|
458
|
+
return { type: 'string', enum: getZodEnumValues(schema) };
|
|
444
459
|
default:
|
|
445
460
|
return { type: 'object' };
|
|
446
461
|
}
|
package/src/contract/handler.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
303
|
-
return inner.default((schema
|
|
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
|
|
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
|
|
319
|
+
return z.array(applyCoercion(getZodArrayElementType(schema)!));
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
// 그 외는 원본 반환
|
|
@@ -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
|
-
|
|
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
|
// --------------------------------------------------------------------------
|
package/src/devtools/init.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface KitchenInstance {
|
|
|
30
30
|
/** DevTools 언마운트 및 정리 */
|
|
31
31
|
destroy: () => void;
|
|
32
32
|
/** 상태 관리자 접근 */
|
|
33
|
-
getState: () =>
|
|
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: () => ({}
|
|
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:
|
|
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:
|
|
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:
|
|
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: ${
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
215
|
+
workerSelf.postMessage(response);
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
218
|
|
package/src/error/index.ts
CHANGED
package/src/error/result.ts
CHANGED
|
@@ -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
|
*/
|
package/src/filling/deps.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}
|
package/src/guard/negotiation.ts
CHANGED
|
@@ -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 *`,
|