@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.
- package/package.json +3 -1
- package/src/brain/architecture/analyzer.ts +3 -5
- package/src/brain/architecture/types.ts +4 -4
- package/src/brain/doctor/analyzer.ts +1 -0
- package/src/brain/doctor/index.ts +1 -1
- package/src/brain/doctor/patcher.ts +10 -6
- package/src/brain/doctor/reporter.ts +4 -4
- package/src/brain/types.ts +14 -10
- package/src/bundler/build.ts +17 -17
- package/src/bundler/css.ts +3 -2
- 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 +17 -1
- 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.test.ts +2 -1
- package/src/contract/client.ts +18 -18
- package/src/contract/define.ts +32 -17
- package/src/contract/handler.ts +11 -11
- package/src/contract/index.ts +2 -5
- package/src/contract/infer.test.ts +2 -1
- package/src/contract/normalize.test.ts +1 -1
- package/src/contract/normalize.ts +17 -11
- package/src/contract/registry.test.ts +1 -1
- package/src/contract/zod-utils.ts +155 -0
- package/src/devtools/client/catchers/error-catcher.ts +3 -3
- package/src/devtools/client/catchers/network-proxy.ts +5 -1
- package/src/devtools/client/components/kitchen-root.tsx +2 -2
- package/src/devtools/client/components/panel/guard-panel.tsx +3 -3
- package/src/devtools/client/state-manager.ts +9 -9
- package/src/devtools/index.ts +8 -8
- package/src/devtools/init.ts +2 -2
- package/src/devtools/protocol.ts +4 -4
- package/src/devtools/server/source-context.ts +9 -3
- package/src/devtools/types.ts +5 -5
- 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 +5 -2
- package/src/filling/filling.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/index.ts +1 -1
- package/src/guard/negotiation.ts +29 -1
- package/src/guard/presets/index.ts +3 -0
- package/src/guard/semantic-slots.ts +4 -4
- package/src/index.ts +10 -1
- package/src/intent/index.ts +28 -17
- package/src/island/index.ts +8 -8
- package/src/openapi/generator.ts +49 -31
- package/src/plugins/index.ts +1 -1
- package/src/plugins/registry.ts +28 -18
- package/src/plugins/types.ts +2 -2
- 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 +34 -40
- package/src/router/fs-types.ts +2 -2
- package/src/router/index.ts +1 -1
- package/src/runtime/boundary.tsx +4 -4
- package/src/runtime/logger.test.ts +3 -3
- package/src/runtime/logger.ts +1 -1
- package/src/runtime/server.ts +18 -16
- package/src/runtime/ssr.ts +1 -1
- package/src/runtime/stable-selector.ts +1 -2
- package/src/runtime/streaming-ssr.ts +15 -6
- package/src/seo/index.ts +5 -0
- package/src/seo/integration/ssr.ts +4 -4
- package/src/seo/render/basic.ts +12 -4
- package/src/seo/render/opengraph.ts +12 -6
- package/src/seo/render/twitter.ts +3 -2
- package/src/seo/resolve/url.ts +7 -0
- package/src/seo/types.ts +13 -0
- package/src/spec/schema.ts +89 -61
- package/src/types/branded.ts +56 -0
- package/src/types/index.ts +1 -0
- package/src/utils/hasher.test.ts +6 -6
- package/src/utils/hasher.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/watcher/watcher.ts +2 -2
package/src/contract/client.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
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
|
|
|
@@ -126,19 +135,19 @@ export function isContract<T extends ContractDefinition>(
|
|
|
126
135
|
|
|
127
136
|
/** Contract에서 Input 타입 추출 */
|
|
128
137
|
export type ContractInput<
|
|
129
|
-
C extends
|
|
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
|
|
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
|
|
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
|
|
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
|
>,
|
package/src/contract/index.ts
CHANGED
|
@@ -204,8 +204,5 @@ export const ManduContract = {
|
|
|
204
204
|
fetch: contractFetch,
|
|
205
205
|
} as const;
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 전역 에러를 캐치하여 DevTools로 전달
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { NormalizedError, ErrorType,
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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().
|
|
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 {
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
307
|
+
addDevToolsGuardViolation(violation: DevToolsGuardViolation): void {
|
|
308
308
|
const guardViolations = [violation, ...this.state.guardViolations];
|
|
309
309
|
|
|
310
310
|
// 최대 개수 제한
|
|
311
|
-
if (guardViolations.length > this.
|
|
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
|
-
|
|
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.
|
|
398
|
+
this.addDevToolsGuardViolation(event.data as DevToolsGuardViolation);
|
|
399
399
|
break;
|
|
400
400
|
|
|
401
401
|
case 'guard:clear':
|
|
402
|
-
this.
|
|
402
|
+
this.clearDevToolsGuardViolations((event.data as { ruleId?: string }).ruleId);
|
|
403
403
|
break;
|
|
404
404
|
|
|
405
405
|
case 'hmr:update':
|