@mandujs/core 0.41.2 → 0.43.0
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 +21 -4
- package/src/auth/__tests__/login.test.ts +420 -419
- package/src/auth/__tests__/reset.test.ts +296 -296
- package/src/brain/adapters/anthropic-oauth.ts +421 -420
- package/src/brain/adapters/index.ts +2 -1
- package/src/brain/adapters/ollama.ts +1 -1
- package/src/brain/adapters/openai-oauth.ts +534 -533
- package/src/brain/brain.ts +2 -1
- package/src/brain/redactor.ts +196 -196
- package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
- package/src/bundler/__tests__/cold-start.test.ts +504 -504
- package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
- package/src/bundler/__tests__/hdr.test.ts +1 -1
- package/src/bundler/analyzer.ts +958 -958
- package/src/bundler/build.ts +104 -14
- package/src/bundler/dev.ts +125 -0
- package/src/bundler/hmr-types.ts +1 -0
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
- package/src/bundler/plugins/index.ts +14 -0
- package/src/bundler/plugins/react-compiler-lint.ts +253 -0
- package/src/bundler/plugins/react-compiler.ts +162 -0
- package/src/bundler/types.ts +12 -0
- package/src/change/integrity.ts +2 -1
- package/src/client/index.ts +10 -0
- package/src/client/island.ts +38 -11
- package/src/client/router.ts +6 -1
- package/src/config/mandu.ts +57 -0
- package/src/config/validate.ts +42 -0
- package/src/content/collection.ts +844 -809
- package/src/content/content-layer.ts +316 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/digest.ts +133 -133
- package/src/content/generate-types.ts +168 -168
- package/src/content/index.ts +6 -1
- package/src/content/llms-txt.ts +277 -277
- package/src/contract/define.ts +474 -474
- package/src/contract/route-helpers.ts +2 -1
- package/src/contract/zod-utils.ts +158 -155
- package/src/db/index.ts +513 -513
- package/src/desktop/__tests__/smoke.test.ts +100 -100
- package/src/desktop/webview-fallback.ts +583 -583
- package/src/desktop/window.ts +3 -1
- package/src/dev-error-overlay/overlay-client.ts +300 -300
- package/src/devtools/ai/mcp-connector.ts +499 -498
- package/src/devtools/client/components/kitchen-root.tsx +7 -2
- package/src/email/resend.ts +163 -163
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/graph.ts +898 -898
- package/src/guard/index.ts +16 -0
- package/src/guard/statistics.ts +578 -578
- package/src/guard/tsgolint-bridge.ts +512 -0
- package/src/i18n/locale-resolver.ts +214 -214
- package/src/id/__tests__/id.test.ts +120 -120
- package/src/intent/index.ts +321 -321
- package/src/island/index.ts +39 -23
- package/src/kitchen/api/contract-api.ts +15 -8
- package/src/kitchen/kitchen-ui.ts +2137 -2137
- package/src/lockfile/index.ts +3 -2
- package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
- package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
- package/src/middleware/secure/index.ts +417 -417
- package/src/observability/event-bus.ts +2 -2
- package/src/observability/metrics.ts +334 -334
- package/src/observability/tracing.ts +694 -694
- package/src/openapi/generator.ts +1 -1
- package/src/perf/user-marks.ts +553 -553
- package/src/plugins/registry.ts +387 -387
- package/src/resource/ddl/diff.ts +392 -392
- package/src/resource/ddl/snapshot.ts +448 -447
- package/src/resource/generator-schema.ts +477 -476
- package/src/resource/parser.ts +4 -2
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.ts +422 -422
- package/src/runtime/fast-refresh-types.ts +126 -128
- package/src/runtime/image-handler.ts +206 -195
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +36 -19
- package/src/runtime/session-key.ts +328 -328
- package/src/scheduler/__tests__/scheduler.test.ts +514 -514
- package/src/seo/resolve/index.ts +353 -353
- package/src/spec/load.ts +1 -1
- package/src/testing/reporter.ts +676 -676
- package/src/testing/server.ts +196 -196
- package/src/testing/snapshot.ts +444 -444
- package/src/utils/__tests__/lru-cache.test.ts +186 -186
- package/src/utils/bun.ts +8 -8
package/src/runtime/security.ts
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime Security
|
|
3
|
-
*
|
|
4
|
-
* 동적 import 및 경로 접근 보안 검증
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import path from "path";
|
|
8
|
-
import type { Result } from "../error/result";
|
|
9
|
-
import { ok, err } from "../error/result";
|
|
10
|
-
import { SecurityError } from "../error/domains";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 허용된 import 경로 패턴
|
|
14
|
-
*/
|
|
15
|
-
const ALLOWED_IMPORT_PATTERNS = [
|
|
16
|
-
/^app\//, // app/ 디렉토리 (FS Routes)
|
|
17
|
-
/^src\/client\//, // 클라이언트 코드
|
|
18
|
-
/^src\/server\//, // 서버 코드
|
|
19
|
-
/^src\/shared\//, // 공유 코드
|
|
20
|
-
/^spec\//, // Spec 디렉토리 (레거시)
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 허용된 파일 확장자
|
|
25
|
-
*/
|
|
26
|
-
const ALLOWED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 차단된 경로 패턴
|
|
30
|
-
*/
|
|
31
|
-
const BLOCKED_PATTERNS = [
|
|
32
|
-
/node_modules/, // node_modules 직접 접근 차단
|
|
33
|
-
/\.env/, // 환경 변수 파일
|
|
34
|
-
/\.git/, // Git 디렉토리
|
|
35
|
-
/\.mandu\/.*\.json$/, // 설정 파일
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 동적 import 경로 검증
|
|
40
|
-
*
|
|
41
|
-
* @param rootDir 프로젝트 루트 디렉토리
|
|
42
|
-
* @param modulePath 상대 모듈 경로 (예: "app/layout.tsx")
|
|
43
|
-
* @returns 검증된 전체 경로 또는 에러
|
|
44
|
-
*/
|
|
45
|
-
export function validateImportPath(
|
|
46
|
-
rootDir: string,
|
|
47
|
-
modulePath: string
|
|
48
|
-
): Result<string> {
|
|
49
|
-
// 1. 경로 정규화
|
|
50
|
-
const normalized = path.posix.normalize(modulePath).replace(/\\/g, "/");
|
|
51
|
-
|
|
52
|
-
// 2. Path traversal 체크
|
|
53
|
-
if (normalized.includes("..")) {
|
|
54
|
-
return err(
|
|
55
|
-
new SecurityError(
|
|
56
|
-
"path_traversal",
|
|
57
|
-
`경로 탐색 공격 감지: ${modulePath}`,
|
|
58
|
-
modulePath
|
|
59
|
-
).toManduError()
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 3. 차단된 패턴 체크
|
|
64
|
-
for (const pattern of BLOCKED_PATTERNS) {
|
|
65
|
-
if (pattern.test(normalized)) {
|
|
66
|
-
return err(
|
|
67
|
-
new SecurityError(
|
|
68
|
-
"import_violation",
|
|
69
|
-
`차단된 경로 접근: ${modulePath}`,
|
|
70
|
-
modulePath
|
|
71
|
-
).toManduError()
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 4. 화이트리스트 검증
|
|
77
|
-
const isAllowed = ALLOWED_IMPORT_PATTERNS.some((pattern) =>
|
|
78
|
-
pattern.test(normalized)
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
if (!isAllowed) {
|
|
82
|
-
return err(
|
|
83
|
-
new SecurityError(
|
|
84
|
-
"import_violation",
|
|
85
|
-
`허용되지 않은 import 경로: ${modulePath}. 허용된 경로: app/, src/client/, src/server/, src/shared/, spec/`,
|
|
86
|
-
modulePath
|
|
87
|
-
).toManduError()
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 5. 확장자 검증 (있는 경우만)
|
|
92
|
-
const ext = path.extname(normalized);
|
|
93
|
-
if (ext && !ALLOWED_EXTENSIONS.includes(ext)) {
|
|
94
|
-
return err(
|
|
95
|
-
new SecurityError(
|
|
96
|
-
"import_violation",
|
|
97
|
-
`허용되지 않은 파일 확장자: ${ext}`,
|
|
98
|
-
modulePath
|
|
99
|
-
).toManduError()
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 6. 전체 경로 생성
|
|
104
|
-
const fullPath = path.join(rootDir, normalized);
|
|
105
|
-
|
|
106
|
-
// 7. 최종 경로가 rootDir 내에 있는지 확인
|
|
107
|
-
const resolvedPath = path.resolve(fullPath);
|
|
108
|
-
const resolvedRoot = path.resolve(rootDir);
|
|
109
|
-
|
|
110
|
-
if (!resolvedPath.startsWith(resolvedRoot + path.sep)) {
|
|
111
|
-
return err(
|
|
112
|
-
new SecurityError(
|
|
113
|
-
"path_traversal",
|
|
114
|
-
`루트 디렉토리 외부 접근 시도: ${modulePath}`,
|
|
115
|
-
modulePath
|
|
116
|
-
).toManduError()
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return ok(fullPath);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 안전한 동적 import
|
|
125
|
-
*
|
|
126
|
-
* @param rootDir 프로젝트 루트 디렉토리
|
|
127
|
-
* @param modulePath 상대 모듈 경로
|
|
128
|
-
* @returns 로드된 모듈 또는 null
|
|
129
|
-
*/
|
|
130
|
-
export async function safeImport<T = unknown>(
|
|
131
|
-
rootDir: string,
|
|
132
|
-
modulePath: string
|
|
133
|
-
): Promise<T | null> {
|
|
134
|
-
const validation = validateImportPath(rootDir, modulePath);
|
|
135
|
-
|
|
136
|
-
if (!validation.ok) {
|
|
137
|
-
console.error(`[Mandu Security] ${validation.error.message}`);
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const module = await import(validation.value);
|
|
143
|
-
return module as T;
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.error(`[Mandu] Failed to import: ${modulePath}`, error);
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 모듈 경로 검증 (boolean 반환)
|
|
152
|
-
*/
|
|
153
|
-
export function isValidImportPath(rootDir: string, modulePath: string): boolean {
|
|
154
|
-
return validateImportPath(rootDir, modulePath).ok;
|
|
155
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Security
|
|
3
|
+
*
|
|
4
|
+
* 동적 import 및 경로 접근 보안 검증
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from "path";
|
|
8
|
+
import type { Result } from "../error/result";
|
|
9
|
+
import { ok, err } from "../error/result";
|
|
10
|
+
import { SecurityError } from "../error/domains";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 허용된 import 경로 패턴
|
|
14
|
+
*/
|
|
15
|
+
const ALLOWED_IMPORT_PATTERNS = [
|
|
16
|
+
/^app\//, // app/ 디렉토리 (FS Routes)
|
|
17
|
+
/^src\/client\//, // 클라이언트 코드
|
|
18
|
+
/^src\/server\//, // 서버 코드
|
|
19
|
+
/^src\/shared\//, // 공유 코드
|
|
20
|
+
/^spec\//, // Spec 디렉토리 (레거시)
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 허용된 파일 확장자
|
|
25
|
+
*/
|
|
26
|
+
const ALLOWED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 차단된 경로 패턴
|
|
30
|
+
*/
|
|
31
|
+
const BLOCKED_PATTERNS = [
|
|
32
|
+
/node_modules/, // node_modules 직접 접근 차단
|
|
33
|
+
/\.env/, // 환경 변수 파일
|
|
34
|
+
/\.git/, // Git 디렉토리
|
|
35
|
+
/\.mandu\/.*\.json$/, // 설정 파일
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 동적 import 경로 검증
|
|
40
|
+
*
|
|
41
|
+
* @param rootDir 프로젝트 루트 디렉토리
|
|
42
|
+
* @param modulePath 상대 모듈 경로 (예: "app/layout.tsx")
|
|
43
|
+
* @returns 검증된 전체 경로 또는 에러
|
|
44
|
+
*/
|
|
45
|
+
export function validateImportPath(
|
|
46
|
+
rootDir: string,
|
|
47
|
+
modulePath: string
|
|
48
|
+
): Result<string> {
|
|
49
|
+
// 1. 경로 정규화
|
|
50
|
+
const normalized = path.posix.normalize(modulePath).replace(/\\/g, "/");
|
|
51
|
+
|
|
52
|
+
// 2. Path traversal 체크
|
|
53
|
+
if (normalized.includes("..")) {
|
|
54
|
+
return err(
|
|
55
|
+
new SecurityError(
|
|
56
|
+
"path_traversal",
|
|
57
|
+
`경로 탐색 공격 감지: ${modulePath}`,
|
|
58
|
+
modulePath
|
|
59
|
+
).toManduError()
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. 차단된 패턴 체크
|
|
64
|
+
for (const pattern of BLOCKED_PATTERNS) {
|
|
65
|
+
if (pattern.test(normalized)) {
|
|
66
|
+
return err(
|
|
67
|
+
new SecurityError(
|
|
68
|
+
"import_violation",
|
|
69
|
+
`차단된 경로 접근: ${modulePath}`,
|
|
70
|
+
modulePath
|
|
71
|
+
).toManduError()
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 4. 화이트리스트 검증
|
|
77
|
+
const isAllowed = ALLOWED_IMPORT_PATTERNS.some((pattern) =>
|
|
78
|
+
pattern.test(normalized)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!isAllowed) {
|
|
82
|
+
return err(
|
|
83
|
+
new SecurityError(
|
|
84
|
+
"import_violation",
|
|
85
|
+
`허용되지 않은 import 경로: ${modulePath}. 허용된 경로: app/, src/client/, src/server/, src/shared/, spec/`,
|
|
86
|
+
modulePath
|
|
87
|
+
).toManduError()
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 5. 확장자 검증 (있는 경우만)
|
|
92
|
+
const ext = path.extname(normalized);
|
|
93
|
+
if (ext && !ALLOWED_EXTENSIONS.includes(ext)) {
|
|
94
|
+
return err(
|
|
95
|
+
new SecurityError(
|
|
96
|
+
"import_violation",
|
|
97
|
+
`허용되지 않은 파일 확장자: ${ext}`,
|
|
98
|
+
modulePath
|
|
99
|
+
).toManduError()
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 6. 전체 경로 생성
|
|
104
|
+
const fullPath = path.join(rootDir, normalized);
|
|
105
|
+
|
|
106
|
+
// 7. 최종 경로가 rootDir 내에 있는지 확인
|
|
107
|
+
const resolvedPath = path.resolve(fullPath);
|
|
108
|
+
const resolvedRoot = path.resolve(rootDir);
|
|
109
|
+
|
|
110
|
+
if (!resolvedPath.startsWith(resolvedRoot + path.sep)) {
|
|
111
|
+
return err(
|
|
112
|
+
new SecurityError(
|
|
113
|
+
"path_traversal",
|
|
114
|
+
`루트 디렉토리 외부 접근 시도: ${modulePath}`,
|
|
115
|
+
modulePath
|
|
116
|
+
).toManduError()
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return ok(fullPath);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 안전한 동적 import
|
|
125
|
+
*
|
|
126
|
+
* @param rootDir 프로젝트 루트 디렉토리
|
|
127
|
+
* @param modulePath 상대 모듈 경로
|
|
128
|
+
* @returns 로드된 모듈 또는 null
|
|
129
|
+
*/
|
|
130
|
+
export async function safeImport<T = unknown>(
|
|
131
|
+
rootDir: string,
|
|
132
|
+
modulePath: string
|
|
133
|
+
): Promise<T | null> {
|
|
134
|
+
const validation = validateImportPath(rootDir, modulePath);
|
|
135
|
+
|
|
136
|
+
if (!validation.ok) {
|
|
137
|
+
console.error(`[Mandu Security] ${validation.error.message}`);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const module = await import(validation.value);
|
|
143
|
+
return module as T;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`[Mandu] Failed to import: ${modulePath}`, error);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 모듈 경로 검증 (boolean 반환)
|
|
152
|
+
*/
|
|
153
|
+
export function isValidImportPath(rootDir: string, modulePath: string): boolean {
|
|
154
|
+
return validateImportPath(rootDir, modulePath).ok;
|
|
155
|
+
}
|
package/src/runtime/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Server } from "bun";
|
|
1
|
+
import type { Server, ServerWebSocket } from "bun";
|
|
2
2
|
import type { RoutesManifest, RouteSpec, HydrationConfig, StaticParamSetSchema } from "../spec/schema";
|
|
3
3
|
import type { BundleManifest } from "../bundler/types";
|
|
4
4
|
import type { ManduFilling, RenderMode } from "../filling/filling";
|
|
@@ -77,8 +77,9 @@ import { collectPerfSnapshot } from "../perf/user-marks";
|
|
|
77
77
|
// `startServer()`; `runWithSpan` is used at the absolute TOP of the
|
|
78
78
|
// request handler so every downstream await (middleware, filling
|
|
79
79
|
// loader, SSR render) inherits the active span via AsyncLocalStorage.
|
|
80
|
+
import type {
|
|
81
|
+
Tracer} from "../observability/tracing";
|
|
80
82
|
import {
|
|
81
|
-
Tracer,
|
|
82
83
|
createTracerFromConfig,
|
|
83
84
|
runWithSpan,
|
|
84
85
|
setTracer,
|
|
@@ -4305,12 +4306,17 @@ function startBunServerWithFallback(options: {
|
|
|
4305
4306
|
const { port: startPort, hostname, fetch, websocket } = options;
|
|
4306
4307
|
let lastError: unknown = null;
|
|
4307
4308
|
|
|
4309
|
+
// `Parameters<typeof Bun.serve>[0]` captures the full `Serve.Options`
|
|
4310
|
+
// XOR union. We only ever use the hostname/port branch, so cast through
|
|
4311
|
+
// `unknown` at the call site to express "these are valid options we've
|
|
4312
|
+
// assembled dynamically" without widening the whole local to `any`.
|
|
4313
|
+
type BunServeOptions = Parameters<typeof Bun.serve>[0];
|
|
4308
4314
|
const serveOptions: Record<string, unknown> = { hostname, fetch, idleTimeout: 255 };
|
|
4309
4315
|
if (websocket) serveOptions.websocket = websocket;
|
|
4310
4316
|
|
|
4311
4317
|
// Port 0: let Bun/OS pick an available ephemeral port.
|
|
4312
4318
|
if (startPort === 0) {
|
|
4313
|
-
const server = Bun.serve({ port: 0, ...serveOptions } as
|
|
4319
|
+
const server = Bun.serve({ port: 0, ...serveOptions } as unknown as BunServeOptions);
|
|
4314
4320
|
return { server, port: server.port ?? 0, attempts: 0 };
|
|
4315
4321
|
}
|
|
4316
4322
|
|
|
@@ -4320,7 +4326,7 @@ function startBunServerWithFallback(options: {
|
|
|
4320
4326
|
continue;
|
|
4321
4327
|
}
|
|
4322
4328
|
try {
|
|
4323
|
-
const server = Bun.serve({ port: candidate, ...serveOptions } as
|
|
4329
|
+
const server = Bun.serve({ port: candidate, ...serveOptions } as unknown as BunServeOptions);
|
|
4324
4330
|
return { server, port: server.port ?? candidate, attempts: attempt };
|
|
4325
4331
|
} catch (error) {
|
|
4326
4332
|
if (!isPortInUseError(error)) {
|
|
@@ -4669,27 +4675,26 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
4669
4675
|
handleRequest,
|
|
4670
4676
|
});
|
|
4671
4677
|
|
|
4672
|
-
// WebSocket 핸들러 빌드 (등록된 WS 라우트가 있을 때만)
|
|
4678
|
+
// WebSocket 핸들러 빌드 (등록된 WS 라우트가 있을 때만).
|
|
4679
|
+
// Bun's `ServerWebSocket<T>` carries the upgrade payload in `ws.data`;
|
|
4680
|
+
// we set `T = WSUpgradeData` at upgrade time (see `bunServer.upgrade`
|
|
4681
|
+
// below) so downstream handlers get proper inference without casts.
|
|
4673
4682
|
const hasWsRoutes = registry.wsHandlers.size > 0;
|
|
4674
4683
|
const wsConfig = hasWsRoutes ? {
|
|
4675
|
-
open(ws:
|
|
4676
|
-
const
|
|
4677
|
-
const handlers = registry.wsHandlers.get(data.routeId);
|
|
4684
|
+
open(ws: ServerWebSocket<WSUpgradeData>) {
|
|
4685
|
+
const handlers = registry.wsHandlers.get(ws.data.routeId);
|
|
4678
4686
|
handlers?.open?.(wrapBunWebSocket(ws));
|
|
4679
4687
|
},
|
|
4680
|
-
message(ws:
|
|
4681
|
-
const
|
|
4682
|
-
const handlers = registry.wsHandlers.get(data.routeId);
|
|
4688
|
+
message(ws: ServerWebSocket<WSUpgradeData>, message: string | ArrayBuffer) {
|
|
4689
|
+
const handlers = registry.wsHandlers.get(ws.data.routeId);
|
|
4683
4690
|
handlers?.message?.(wrapBunWebSocket(ws), message);
|
|
4684
4691
|
},
|
|
4685
|
-
close(ws:
|
|
4686
|
-
const
|
|
4687
|
-
const handlers = registry.wsHandlers.get(data.routeId);
|
|
4692
|
+
close(ws: ServerWebSocket<WSUpgradeData>, code: number, reason: string) {
|
|
4693
|
+
const handlers = registry.wsHandlers.get(ws.data.routeId);
|
|
4688
4694
|
handlers?.close?.(wrapBunWebSocket(ws), code, reason);
|
|
4689
4695
|
},
|
|
4690
|
-
drain(ws:
|
|
4691
|
-
const
|
|
4692
|
-
const handlers = registry.wsHandlers.get(data.routeId);
|
|
4696
|
+
drain(ws: ServerWebSocket<WSUpgradeData>) {
|
|
4697
|
+
const handlers = registry.wsHandlers.get(ws.data.routeId);
|
|
4693
4698
|
handlers?.drain?.(wrapBunWebSocket(ws));
|
|
4694
4699
|
},
|
|
4695
4700
|
} : undefined;
|
|
@@ -4710,6 +4715,13 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
4710
4715
|
};
|
|
4711
4716
|
|
|
4712
4717
|
// fetch handler: WS upgrade 감지 추가
|
|
4718
|
+
//
|
|
4719
|
+
// Bun narrows `server.upgrade()` to require the matching WS data generic
|
|
4720
|
+
// (see `Server<T>` in bun-types/serve.d.ts:908). Our wsConfig is typed
|
|
4721
|
+
// against `WSUpgradeData`, so the server we receive at runtime is really
|
|
4722
|
+
// `Server<WSUpgradeData>`. The outer startBunServerWithFallback returns
|
|
4723
|
+
// `Server<undefined>` for back-compat; a single cast here threads the
|
|
4724
|
+
// correct generic through to `.upgrade()` without widening to `any`.
|
|
4713
4725
|
const wrappedFetch = hasWsRoutes
|
|
4714
4726
|
? async (req: Request, bunServer: Server<undefined>): Promise<Response | undefined> => {
|
|
4715
4727
|
// WebSocket upgrade 요청 감지
|
|
@@ -4717,7 +4729,8 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
4717
4729
|
const url = new URL(req.url);
|
|
4718
4730
|
const match = router.match(url.pathname);
|
|
4719
4731
|
if (match && registry.wsHandlers.has(match.route.id)) {
|
|
4720
|
-
const
|
|
4732
|
+
const wsServer = bunServer as unknown as Server<WSUpgradeData>;
|
|
4733
|
+
const upgraded = wsServer.upgrade(req, {
|
|
4721
4734
|
data: { routeId: match.route.id, params: match.params, id: newId() },
|
|
4722
4735
|
});
|
|
4723
4736
|
return upgraded ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
|
|
@@ -4736,7 +4749,11 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
4736
4749
|
const { server, port: actualPort, attempts } = startBunServerWithFallback({
|
|
4737
4750
|
port,
|
|
4738
4751
|
hostname,
|
|
4739
|
-
|
|
4752
|
+
// `wrappedFetch` has a `Server<undefined>` signature but the WS-enabled
|
|
4753
|
+
// branch internally treats it as `Server<WSUpgradeData>` (see wsServer
|
|
4754
|
+
// cast above). The fallback helper accepts the narrower type; a cast
|
|
4755
|
+
// through `unknown` threads the WS-aware fetch without `any`.
|
|
4756
|
+
fetch: wrappedFetch as unknown as Parameters<typeof startBunServerWithFallback>[0]["fetch"],
|
|
4740
4757
|
websocket: wsConfig,
|
|
4741
4758
|
});
|
|
4742
4759
|
|