mega-framework 0.1.5 → 0.1.7
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/bin/mega-ws-hub.js +2 -2
- package/package.json +32 -8
- package/sample/crud/.env +156 -8
- package/sample/crud/.env.example +153 -28
- package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
- package/sample/crud/.mega/journal/snapshot.json +261 -0
- package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
- package/sample/crud/apps/main/controllers/web-controller.js +7 -5
- package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
- package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
- package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
- package/sample/crud/apps/main/models/log-partition-model.js +105 -0
- package/sample/crud/apps/main/models/note-model.js +79 -0
- package/sample/crud/apps/main/models/user-level-model.js +24 -0
- package/sample/crud/apps/main/models/user-model.js +146 -0
- package/sample/crud/apps/main/models/user-type-model.js +21 -0
- package/sample/crud/apps/main/models/wallet-model.js +24 -0
- package/sample/crud/apps/main/routes/users.js +55 -10
- package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
- package/sample/crud/apps/main/services/auth-service.js +39 -24
- package/sample/crud/apps/main/services/log-partition-service.js +101 -0
- package/sample/crud/apps/main/services/note-service.js +6 -6
- package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
- package/sample/crud/apps/main/services/user-service.js +62 -21
- package/sample/crud/apps/main/views/auth/login.ejs +6 -6
- package/sample/crud/apps/main/views/auth/register.ejs +46 -5
- package/sample/crud/apps/main/views/users/edit.ejs +42 -5
- package/sample/crud/apps/main/views/users/list.ejs +6 -2
- package/sample/crud/apps/main/views/users/new.ejs +56 -4
- package/sample/crud/docs/log_partition_design.mm.md +23 -0
- package/sample/crud/mega.config.js +63 -3
- package/sample/crud/package.json +3 -3
- package/sample/crud/scripts/start-ws-hub.sh +2 -2
- package/sample/simple/package.json +2 -2
- package/src/adapters/adapter-manager.js +2 -1
- package/src/adapters/adapter-options.js +30 -0
- package/src/adapters/maria-adapter.js +26 -3
- package/src/adapters/mega-db-adapter.js +7 -1
- package/src/adapters/mongo-adapter.js +19 -1
- package/src/adapters/postgres-adapter.js +25 -2
- package/src/adapters/sqlite-adapter.js +20 -1
- package/src/cli/commands/new.js +13 -3
- package/src/cli/commands/scaffold.js +137 -33
- package/src/cli/generators/index.js +82 -2
- package/src/cli/index.js +478 -104
- package/src/core/ajv-mapper.js +27 -2
- package/src/core/boot.js +485 -237
- package/src/core/cluster-metrics.js +13 -4
- package/src/core/config-validator.js +25 -0
- package/src/core/ctx-builder.js +6 -2
- package/src/core/envelope.js +112 -12
- package/src/core/hub-link.js +65 -4
- package/src/core/i18n.js +11 -1
- package/src/core/index.js +6 -2
- package/src/core/mega-app.js +223 -481
- package/src/core/mega-cluster.js +54 -13
- package/src/core/mega-server.js +40 -9
- package/src/core/migration/dialect-registry.js +107 -0
- package/src/core/migration/dialects/README.md +62 -0
- package/src/core/migration/dialects/maria.js +496 -0
- package/src/core/migration/dialects/mongo.js +824 -0
- package/src/core/migration/dialects/postgres.js +563 -0
- package/src/core/migration/dialects/sqlite.js +476 -0
- package/src/core/migration/differ.js +456 -0
- package/src/core/migration/generate.js +508 -0
- package/src/core/migration/journal.js +167 -0
- package/src/core/migration/model-scan.js +84 -0
- package/src/core/migration/mongo-migration-db.js +97 -0
- package/src/core/migration/schema-builder.js +400 -0
- package/src/core/migration/schema-validator.js +315 -0
- package/src/core/migration-lock.js +205 -0
- package/src/core/migration-runner.js +166 -38
- package/src/core/multipart.js +28 -5
- package/src/core/pipeline.js +129 -0
- package/src/core/router.js +70 -65
- package/src/core/scope-registry.js +0 -1
- package/src/core/security.js +67 -9
- package/src/core/workers-manager.js +12 -1
- package/src/core/ws-cluster.js +10 -3
- package/src/core/ws-message.js +48 -4
- package/src/core/ws-presence.js +624 -0
- package/src/core/ws-roster.js +4 -1
- package/src/core/ws-upgrade.js +118 -12
- package/src/index.js +1 -1
- package/src/lib/hub-protocol.js +29 -0
- package/src/lib/mega-health.js +25 -4
- package/src/lib/mega-job-queue.js +98 -21
- package/src/lib/mega-job.js +29 -0
- package/src/lib/mega-logger.js +1 -1
- package/src/lib/mega-metrics.js +3 -12
- package/src/lib/mega-plugin.js +34 -3
- package/src/lib/mega-schedule.js +40 -22
- package/src/lib/mega-shutdown.js +162 -49
- package/src/lib/mega-tracing.js +66 -19
- package/src/lib/mega-worker.js +5 -1
- package/src/lib/otel-resource.js +36 -0
- package/src/{cli → lib}/ws-hub.js +51 -8
- package/src/models/crud-sql-builder.js +133 -0
- package/src/models/mega-model.js +82 -2
- package/src/models/model-crud.js +483 -0
- package/src/models/mongo-crud.js +285 -0
- package/templates/model/code-mongo.tpl +35 -0
- package/templates/model/code.tpl +15 -1
- package/templates/model/test-mongo.tpl +38 -0
- package/templates/model/test.tpl +4 -0
- package/types/adapters/adapter-manager.d.ts +95 -0
- package/types/adapters/adapter-options.d.ts +91 -0
- package/types/adapters/file-adapter.d.ts +94 -0
- package/types/adapters/file-session-adapter.d.ts +101 -0
- package/types/adapters/index.d.ts +20 -0
- package/types/adapters/maria-adapter.d.ts +115 -0
- package/types/adapters/mega-adapter.d.ts +215 -0
- package/types/adapters/mega-bus-adapter.d.ts +45 -0
- package/types/adapters/mega-cache-adapter.d.ts +47 -0
- package/types/adapters/mega-db-adapter.d.ts +47 -0
- package/types/adapters/mega-lock-adapter.d.ts +62 -0
- package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
- package/types/adapters/mega-session-adapter.d.ts +32 -0
- package/types/adapters/mongo-adapter.d.ts +139 -0
- package/types/adapters/nats-adapter.d.ts +108 -0
- package/types/adapters/postgres-adapter.d.ts +139 -0
- package/types/adapters/redis-adapter.d.ts +70 -0
- package/types/adapters/redis-session-adapter.d.ts +82 -0
- package/types/adapters/redlock-adapter.d.ts +149 -0
- package/types/adapters/registry.d.ts +46 -0
- package/types/adapters/sqlite-adapter.d.ts +106 -0
- package/types/auth/index.d.ts +24 -0
- package/types/cli/commands/console-cmd.d.ts +37 -0
- package/types/cli/commands/new.d.ts +16 -0
- package/types/cli/commands/routes.d.ts +36 -0
- package/types/cli/commands/scaffold.d.ts +78 -0
- package/types/cli/commands/test-cmd.d.ts +14 -0
- package/types/cli/generators/index.d.ts +112 -0
- package/types/cli/index.d.ts +249 -0
- package/types/cli/template-engine.d.ts +40 -0
- package/types/core/ajv-mapper.d.ts +27 -0
- package/types/core/boot.d.ts +233 -0
- package/types/core/cluster-metrics.d.ts +52 -0
- package/types/core/config-loader.d.ts +13 -0
- package/types/core/config-validator.d.ts +30 -0
- package/types/core/ctx-builder.d.ts +80 -0
- package/types/core/envelope.d.ts +79 -0
- package/types/core/error-mapper.d.ts +17 -0
- package/types/core/formbody.d.ts +41 -0
- package/types/core/hub-link.d.ts +264 -0
- package/types/core/i18n.d.ts +178 -0
- package/types/core/index.d.ts +28 -0
- package/types/core/mega-app.d.ts +529 -0
- package/types/core/mega-cluster.d.ts +104 -0
- package/types/core/mega-server.d.ts +91 -0
- package/types/core/mega-service.d.ts +31 -0
- package/types/core/migration/dialect-registry.d.ts +22 -0
- package/types/core/migration/dialects/maria.d.ts +99 -0
- package/types/core/migration/dialects/mongo.d.ts +89 -0
- package/types/core/migration/dialects/postgres.d.ts +117 -0
- package/types/core/migration/dialects/sqlite.d.ts +111 -0
- package/types/core/migration/differ.d.ts +47 -0
- package/types/core/migration/generate.d.ts +56 -0
- package/types/core/migration/journal.d.ts +52 -0
- package/types/core/migration/model-scan.d.ts +19 -0
- package/types/core/migration/mongo-migration-db.d.ts +7 -0
- package/types/core/migration/schema-builder.d.ts +197 -0
- package/types/core/migration/schema-validator.d.ts +20 -0
- package/types/core/migration-lock.d.ts +33 -0
- package/types/core/migration-runner.d.ts +101 -0
- package/types/core/multipart.d.ts +86 -0
- package/types/core/openapi.d.ts +62 -0
- package/types/core/pipeline.d.ts +92 -0
- package/types/core/router.d.ts +159 -0
- package/types/core/routes-loader.d.ts +21 -0
- package/types/core/scope-registry.d.ts +14 -0
- package/types/core/security.d.ts +77 -0
- package/types/core/services-loader.d.ts +27 -0
- package/types/core/session-cleanup-schedule.d.ts +19 -0
- package/types/core/session-store.d.ts +18 -0
- package/types/core/session.d.ts +77 -0
- package/types/core/static-assets.d.ts +73 -0
- package/types/core/template.d.ts +106 -0
- package/types/core/workers-manager.d.ts +79 -0
- package/types/core/ws-cluster.d.ts +208 -0
- package/types/core/ws-compression.d.ts +112 -0
- package/types/core/ws-controller.d.ts +65 -0
- package/types/core/ws-message.d.ts +106 -0
- package/types/core/ws-presence.d.ts +273 -0
- package/types/core/ws-roster.d.ts +96 -0
- package/types/core/ws-upgrade.d.ts +231 -0
- package/types/errors/config-error.d.ts +10 -0
- package/types/errors/http-errors.d.ts +120 -0
- package/types/errors/index.d.ts +3 -0
- package/types/errors/mega-error.d.ts +32 -0
- package/types/index.d.ts +39 -0
- package/types/lib/asp/config.d.ts +49 -0
- package/types/lib/asp/crypto.d.ts +43 -0
- package/types/lib/asp/errors.d.ts +30 -0
- package/types/lib/asp/nonce-cache.d.ts +52 -0
- package/types/lib/asp/plugin.d.ts +30 -0
- package/types/lib/asp/ws-terminator.d.ts +45 -0
- package/types/lib/env-mapper.d.ts +14 -0
- package/types/lib/hub-protocol.d.ts +106 -0
- package/types/lib/index.d.ts +22 -0
- package/types/lib/logger/telegram-core.d.ts +104 -0
- package/types/lib/logger/telegram-transport.d.ts +45 -0
- package/types/lib/mega-brute-force.d.ts +66 -0
- package/types/lib/mega-circuit-breaker.d.ts +241 -0
- package/types/lib/mega-cron.d.ts +66 -0
- package/types/lib/mega-hash.d.ts +32 -0
- package/types/lib/mega-health.d.ts +41 -0
- package/types/lib/mega-job-queue.d.ts +176 -0
- package/types/lib/mega-job-worker.d.ts +130 -0
- package/types/lib/mega-job.d.ts +138 -0
- package/types/lib/mega-logger.d.ts +45 -0
- package/types/lib/mega-metrics.d.ts +285 -0
- package/types/lib/mega-plugin.d.ts +245 -0
- package/types/lib/mega-retry.d.ts +85 -0
- package/types/lib/mega-schedule.d.ts +260 -0
- package/types/lib/mega-shutdown.d.ts +135 -0
- package/types/lib/mega-tracing.d.ts +224 -0
- package/types/lib/mega-worker.d.ts +127 -0
- package/types/lib/otel-resource.d.ts +16 -0
- package/types/lib/worker-runner/process-entry.d.ts +1 -0
- package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
- package/types/lib/worker-runner/thread-entry.d.ts +1 -0
- package/types/lib/ws-hub.d.ts +234 -0
- package/types/models/crud-sql-builder.d.ts +48 -0
- package/types/models/index.d.ts +1 -0
- package/types/models/mega-model.d.ts +138 -0
- package/types/models/model-crud.d.ts +82 -0
- package/types/models/mongo-crud.d.ts +59 -0
- package/types/test/index.d.ts +84 -0
- package/.env +0 -127
- package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
- package/sample/crud/apps/main/models/note.js +0 -71
- package/sample/crud/apps/main/models/user.js +0 -86
- package/sample/crud/package-lock.json +0 -5665
- package/sample/crud/yarn.lock +0 -2142
- package/sample/simple/package-lock.json +0 -1851
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 여러 워커의 Prometheus exposition 텍스트를 하나로 합산한다(순수 — 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* - **counter / histogram(_bucket/_sum/_count)**: 동일 `name{labels}` 끼리 값 **합산**(클러스터 누적).
|
|
5
|
+
* - **gauge**: 합산(메모리·CPU 는 클러스터 합이 유의미). 단 프로세스별 gauge(uptime 등)는 합이 N배가 되는
|
|
6
|
+
* 문서화된 한계 — prom-client 기본도 gauge=sum. `_info`/`target_info` 메타는 합산 대신 **첫 값 유지**(=1).
|
|
7
|
+
* - `# HELP`/`# TYPE` 는 패밀리별 1줄로 dedupe 하고, 패밀리(TYPE 이름) 단위로 묶어 재출력한다.
|
|
8
|
+
*
|
|
9
|
+
* @param {string[]} texts - 각 워커 `collect()` 결과(빈 문자열 허용).
|
|
10
|
+
* @returns {string} 합산된 exposition 텍스트(끝 개행 포함). 입력이 모두 비면 빈 문자열.
|
|
11
|
+
*/
|
|
12
|
+
export function mergeExposition(texts: string[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* 워커 프로세스에 collect 응답기를 설치한다 — 마스터의 `collect` 요청에 자기 `MegaMetrics.collect()` 로 회신.
|
|
15
|
+
* 클러스터 워커가 아니면 no-op. `process.on('message')` 다중 리스너라 MegaCluster shutdown 핸들러와 공존한다.
|
|
16
|
+
*
|
|
17
|
+
* IPC 경로는 fork 워커 안에서만 실행돼 부모 v8 커버리지로 측정 불가다. cluster/process 를 주입 seam 으로
|
|
18
|
+
* 분리해 fake 로 in-process 단위 검증한다(per ADR-165 — `MegaCluster` 와 동일 패턴). 프로덕션 호출부는
|
|
19
|
+
* 인자를 안 넘겨 실 전역을 쓴다.
|
|
20
|
+
* @param {{ _cluster?: import('node:cluster').Cluster, _proc?: NodeJS.Process }} [opts]
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
23
|
+
export function installWorkerResponder({ _cluster, _proc }?: {
|
|
24
|
+
_cluster?: import("node:cluster").Cluster;
|
|
25
|
+
_proc?: NodeJS.Process;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* 마스터 프로세스에 집계기를 설치한다 — 워커의 `request` 를 받아 전 워커에 fan-out, 회신을 모아 합산해 회신.
|
|
29
|
+
* 마스터가 아니면 no-op. `cluster.on('message')` 로 모든 워커 메시지를 한 핸들러로 받는다.
|
|
30
|
+
*
|
|
31
|
+
* IPC 경로는 마스터 안에서만 실행돼 부모 v8 커버리지로 측정 불가다 — cluster 를 주입 seam 으로 분리해
|
|
32
|
+
* fake 로 in-process 단위 검증한다(per ADR-165).
|
|
33
|
+
* @param {{ timeoutMs?: number, _cluster?: import('node:cluster').Cluster }} [opts] - 라운드 수집 timeout(전원 미응답 시 부분 합산). 기본 2000ms.
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
export function installPrimaryAggregator({ timeoutMs, _cluster }?: {
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
_cluster?: import("node:cluster").Cluster;
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* 현재 프로세스 기준 메트릭을 수집한다 — 클러스터 워커면 마스터를 통해 **전 워커 합산**, 단일 프로세스면 로컬.
|
|
42
|
+
* `/metrics` 라우트와 `/demo/metrics` 가 쓴다. timeout/마스터 부재 시 로컬 `collect()` 로 폴백(스크레이프가 안
|
|
43
|
+
* 멈추도록).
|
|
44
|
+
* cluster/process 는 주입 seam(테스트용 — per ADR-165). 프로덕션 호출부는 인자를 안 넘겨 실 전역을 쓴다.
|
|
45
|
+
* @param {{ timeoutMs?: number, _cluster?: import('node:cluster').Cluster, _proc?: NodeJS.Process }} [opts] - 응답 대기 timeout(라운드 timeout 보다 넉넉히). 기본 2500ms.
|
|
46
|
+
* @returns {Promise<string>} Prometheus exposition 텍스트.
|
|
47
|
+
*/
|
|
48
|
+
export function collectCluster({ timeoutMs, _cluster, _proc }?: {
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
_cluster?: import("node:cluster").Cluster;
|
|
51
|
+
_proc?: NodeJS.Process;
|
|
52
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 프로젝트 루트에서 mega.config.js + 활성 앱 config 들 로드 + 검증.
|
|
3
|
+
* @param {string} projectRoot - 프로젝트 루트 절대 경로
|
|
4
|
+
* @returns {Promise<{global: Object, apps: Array<{name: string, config: Object}>}>}
|
|
5
|
+
* @throws {MegaConfigError}
|
|
6
|
+
*/
|
|
7
|
+
export function loadAndValidateConfig(projectRoot: string): Promise<{
|
|
8
|
+
global: Object;
|
|
9
|
+
apps: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
config: Object;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 메인 config 검증.
|
|
3
|
+
* @param {Record<string, any>} globalConfig - mega.config.js export 객체
|
|
4
|
+
* @throws {MegaConfigError}
|
|
5
|
+
*/
|
|
6
|
+
export function validateGlobalConfig(globalConfig: Record<string, any>): void;
|
|
7
|
+
/**
|
|
8
|
+
* 앱 config 검증.
|
|
9
|
+
* @param {Record<string, any>} appConfig - apps/<name>/app.config.js export 객체
|
|
10
|
+
* @param {string} expectedFolderName - 폴더명 (apps/<name>)
|
|
11
|
+
* @param {Record<string, any>} globalConfig - 메인 (Shared-Reference 키 cross-check 용)
|
|
12
|
+
* @throws {MegaConfigError}
|
|
13
|
+
*/
|
|
14
|
+
export function validateAppConfig(appConfig: Record<string, any>, expectedFolderName: string, globalConfig: Record<string, any>): void;
|
|
15
|
+
/**
|
|
16
|
+
* 모든 앱 cross-validate: 호스트 충돌 감지 (ADR-067).
|
|
17
|
+
* @param {Array<{ name: string, config: Record<string, any> }>} apps
|
|
18
|
+
* @throws {MegaConfigError}
|
|
19
|
+
*/
|
|
20
|
+
export function validateHostCollisions(apps: Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
config: Record<string, any>;
|
|
23
|
+
}>): void;
|
|
24
|
+
/**
|
|
25
|
+
* 가까운 키 추천 (Levenshtein 거리 기반).
|
|
26
|
+
* @param {string} input
|
|
27
|
+
* @param {readonly string[]} known
|
|
28
|
+
* @returns {string | undefined}
|
|
29
|
+
*/
|
|
30
|
+
export function suggestKey(input: string, known: readonly string[]): string | undefined;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} AdapterAliasMaps - 앱의 `app.config.js` 별명 선언 (alias → globalKey).
|
|
3
|
+
* @property {Record<string, string>} [databases]
|
|
4
|
+
* @property {Record<string, string>} [caches]
|
|
5
|
+
* @property {Record<string, string>} [buses]
|
|
6
|
+
* @property {Record<string, string>} [locks]
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} AdapterAccessors
|
|
10
|
+
* @property {(alias: string) => import('../adapters/mega-adapter.js').MegaAdapter} db
|
|
11
|
+
* @property {(alias: string) => import('../adapters/mega-adapter.js').MegaAdapter} cache
|
|
12
|
+
* @property {(alias: string) => import('../adapters/mega-adapter.js').MegaAdapter} bus
|
|
13
|
+
* @property {(alias: string) => import('../adapters/mega-adapter.js').MegaAdapter} lock
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 앱의 별명 맵으로 `db/cache/bus/lock` 접근자 4종을 만든다. 접근자는 요청과 무관하게 안정적이므로
|
|
17
|
+
* **앱당 한 번** 만들어 재사용한다(MegaApp 생성자). 각 접근자는 호출 시 별명→globalKey→공유
|
|
18
|
+
* 인스턴스로 해석하고, 미선언 별명이면 throw.
|
|
19
|
+
*
|
|
20
|
+
* @param {AdapterAliasMaps} [aliasMaps] - 앱 별명 선언. 없으면 모든 접근자가 미선언 throw.
|
|
21
|
+
* @param {string} [appName] - 에러 메시지용 앱 이름.
|
|
22
|
+
* @returns {AdapterAccessors}
|
|
23
|
+
*/
|
|
24
|
+
export function buildAdapterAccessors(aliasMaps?: AdapterAliasMaps, appName?: string): AdapterAccessors;
|
|
25
|
+
/**
|
|
26
|
+
* HTTP 요청 단위 ctx 를 만든다 (canonical handler 시그니처 `(req, res, ctx)`, ADR-074/docs/03).
|
|
27
|
+
*
|
|
28
|
+
* canonical ctx 표면: `app / log / requestId / req / reply` + `db/cache/bus` 접근자 +
|
|
29
|
+
* `workers`(CPU 워커 풀, ADR-124) + `tracer`(사용자 직접 span — `ctx.tracer.span`,
|
|
30
|
+
* ADR-126). (i18n `t`, `session` 객체, `services` 자동 DI 등 나머지 canonical 필드도 같은 방식으로 추가된다.)
|
|
31
|
+
*
|
|
32
|
+
* `app` 이 null(standalone Router — 앱에 바인딩 안 된 테스트 경로)이면 `db/cache/bus` 접근자는
|
|
33
|
+
* 생략된다(해당 ctx 로는 어댑터 lookup 불가). `workers` 는 글로벌 자원이라 app 무관하게 항상 노출한다
|
|
34
|
+
* (미등록 이름 접근 시 worker.not_registered fail-fast — 워커 0개면 접근 자체가 없어 무해).
|
|
35
|
+
*
|
|
36
|
+
* @param {object} args
|
|
37
|
+
* @param {import('./mega-app.js').MegaApp | null} args.app
|
|
38
|
+
* @param {import('fastify').FastifyRequest} args.req
|
|
39
|
+
* @param {import('fastify').FastifyReply} args.reply
|
|
40
|
+
* @returns {Record<string, any>}
|
|
41
|
+
*/
|
|
42
|
+
export function buildHttpCtx({ app, req, reply }: {
|
|
43
|
+
app: import("./mega-app.js").MegaApp | null;
|
|
44
|
+
req: import("fastify").FastifyRequest;
|
|
45
|
+
reply: import("fastify").FastifyReply;
|
|
46
|
+
}): Record<string, any>;
|
|
47
|
+
/**
|
|
48
|
+
* 요청당 ctx 를 **한 번만** 만들고 `req` 에 캐싱해 재사용한다(ADR-134). 글로벌 미들웨어(preHandler)와
|
|
49
|
+
* 라우트 핸들러가 같은 요청에서 호출하면 **동일 ctx 객체**를 받으므로, 미들웨어가 ctx 에 심은 값
|
|
50
|
+
* (예: `ctx.user`)을 핸들러가 그대로 본다. 첫 호출이 build, 이후 호출은 캐시 반환.
|
|
51
|
+
*
|
|
52
|
+
* 캐시 키는 비열거 Symbol 이라 `{...ctx}`/`Object.keys(req)` 에 노출되지 않는다. `req` 는 Fastify 가
|
|
53
|
+
* 요청마다 새로 만드는 객체라 요청 스코프로 자연히 격리된다(요청 종료 시 GC).
|
|
54
|
+
*
|
|
55
|
+
* @param {object} args
|
|
56
|
+
* @param {import('./mega-app.js').MegaApp | null} args.app
|
|
57
|
+
* @param {import('fastify').FastifyRequest} args.req
|
|
58
|
+
* @param {import('fastify').FastifyReply} args.reply
|
|
59
|
+
* @returns {Record<string, any>}
|
|
60
|
+
*/
|
|
61
|
+
export function getHttpCtx({ app, req, reply }: {
|
|
62
|
+
app: import("./mega-app.js").MegaApp | null;
|
|
63
|
+
req: import("fastify").FastifyRequest;
|
|
64
|
+
reply: import("fastify").FastifyReply;
|
|
65
|
+
}): Record<string, any>;
|
|
66
|
+
/**
|
|
67
|
+
* - 앱의 `app.config.js` 별명 선언 (alias → globalKey).
|
|
68
|
+
*/
|
|
69
|
+
export type AdapterAliasMaps = {
|
|
70
|
+
databases?: Record<string, string>;
|
|
71
|
+
caches?: Record<string, string>;
|
|
72
|
+
buses?: Record<string, string>;
|
|
73
|
+
locks?: Record<string, string>;
|
|
74
|
+
};
|
|
75
|
+
export type AdapterAccessors = {
|
|
76
|
+
db: (alias: string) => import("../adapters/mega-adapter.js").MegaAdapter;
|
|
77
|
+
cache: (alias: string) => import("../adapters/mega-adapter.js").MegaAdapter;
|
|
78
|
+
bus: (alias: string) => import("../adapters/mega-adapter.js").MegaAdapter;
|
|
79
|
+
lock: (alias: string) => import("../adapters/mega-adapter.js").MegaAdapter;
|
|
80
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 자동 envelope (ADR-018). preSerialization 훅의 마지막 단계에서 호출되어
|
|
3
|
+
* raw data → `{ ok:true, data, meta }`.
|
|
4
|
+
*
|
|
5
|
+
* transform 미들웨어(ADR-091)가 먼저 raw data 를 변환했으면, 그 변환 결과 위에 envelope 만 감쌈
|
|
6
|
+
* (실행 순서: transform → wrap, ADR-076 / ADR-091).
|
|
7
|
+
*
|
|
8
|
+
* 이미 envelope 형태로 보낸 경우(`{ ok:false, error: ... }` 등 명시) 는 그대로 통과 —
|
|
9
|
+
* 글로벌 에러 핸들러가 만든 error envelope 의 이중 wrap 을 방지.
|
|
10
|
+
*
|
|
11
|
+
* @param {Record<string, any>} req - Fastify request
|
|
12
|
+
* @param {Record<string | symbol, any>} reply - Fastify reply
|
|
13
|
+
* @param {any} payload - 핸들러/transform 이 만든 raw data 또는 이미 만들어진 envelope
|
|
14
|
+
* @returns {Object | any} envelope 또는 (이미 envelope 면) 원본 payload
|
|
15
|
+
*/
|
|
16
|
+
export function wrapEnvelope(req: Record<string, any>, reply: Record<string | symbol, any>, payload: any): Object | any;
|
|
17
|
+
/**
|
|
18
|
+
* 에러 envelope (ADR-014). 글로벌 에러 핸들러가 사용.
|
|
19
|
+
* @param {Record<string, any>} req
|
|
20
|
+
* @param {Record<string | symbol, any>} reply
|
|
21
|
+
* @param {{ code: string, message?: string, details?: any }} error
|
|
22
|
+
* @returns {{ ok: false, error: { code: string, message: string, details?: any }, meta: Object }}
|
|
23
|
+
*/
|
|
24
|
+
export function errorEnvelope(req: Record<string, any>, reply: Record<string | symbol, any>, error: {
|
|
25
|
+
code: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
details?: any;
|
|
28
|
+
}): {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: {
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
details?: any;
|
|
34
|
+
};
|
|
35
|
+
meta: Object;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 라우트 `schema.response` 를 envelope 모양으로 합성한다.
|
|
39
|
+
*
|
|
40
|
+
* 배경: 자동 envelope(ADR-018)는 preSerialization 마지막에 raw data 를 `{ ok, data, meta }` 로
|
|
41
|
+
* 감싸는데, 사용자가 raw data 모양의 response schema 를 선언하면 Fastify 직렬화기가 **envelope 를
|
|
42
|
+
* 그 raw 스키마로 직렬화해 ok/data/meta 가 통째로 제거**된다(silent 데이터 소실). 그래서 등록
|
|
43
|
+
* 시점(onRoute)에 각 상태코드 스키마를 `{ ok, data: <사용자 스키마>, error, meta }` 로 재작성한다 —
|
|
44
|
+
* 사용자는 계속 raw data 모양만 선언하고(ADR-091), strict 직렬화(ADR-020: 선언 외 필드 제거)는
|
|
45
|
+
* data 안에서 그대로 동작하며, OpenAPI 명세(@fastify/swagger 가 schema 수집)도 실제 와이어 모양과
|
|
46
|
+
* 일치하게 된다.
|
|
47
|
+
*
|
|
48
|
+
* 합성 제외(원본 유지) 3종:
|
|
49
|
+
* - `type: 'string'` 스키마 — 문자열 payload 는 Fastify 가 preSerialization(envelope wrap) 자체를
|
|
50
|
+
* 건너뛰므로(raw 전송, 예: /metrics) envelope 모양으로 바꾸면 오히려 직렬화가 깨진다.
|
|
51
|
+
* - 이미 envelope 모양(`properties.ok` 보유) — 사용자가 envelope 전체를 직접 선언한 경우.
|
|
52
|
+
* - 스키마가 object 가 아닌 항목 — 알 수 없는 형식은 건드리지 않는다(fail-safe).
|
|
53
|
+
*
|
|
54
|
+
* Fastify v5 의 media-type 형식(`{ content: { 'application/json': { schema } } }`)은 내부 schema
|
|
55
|
+
* 에 같은 규칙을 적용한다.
|
|
56
|
+
*
|
|
57
|
+
* @param {Record<string, any>} response - 라우트 `schema.response` (상태코드/패턴 → JSON Schema).
|
|
58
|
+
* @returns {Record<string, any>} envelope 모양으로 합성된 새 response 객체 (원본 불변).
|
|
59
|
+
*/
|
|
60
|
+
export function synthesizeEnvelopeResponseSchema(response: Record<string, any>): Record<string, any>;
|
|
61
|
+
/**
|
|
62
|
+
* 응답 envelope 헬퍼 (ADR-014, ADR-018).
|
|
63
|
+
*
|
|
64
|
+
* 모든 HTTP 응답은 `{ ok, data|error, meta }` 형태로 통일된다.
|
|
65
|
+
* wrapEnvelope 는 preSerialization 의 마지막 단계에서 raw data 를 감싸고,
|
|
66
|
+
* errorEnvelope 는 글로벌 에러 핸들러에서 에러를 감싼다.
|
|
67
|
+
*
|
|
68
|
+
* @module core/envelope
|
|
69
|
+
*/
|
|
70
|
+
/** request 시작 시간 기록용 symbol (meta.took_ms 계산). reply 객체에 부착. */
|
|
71
|
+
export const REPLY_START_SYMBOL: unique symbol;
|
|
72
|
+
/**
|
|
73
|
+
* 프레임워크가 만든 envelope 임을 표시하는 비열거 마킹 symbol.
|
|
74
|
+
*
|
|
75
|
+
* 이중 wrap 방지 판별을 키 모양 추측(duck-typing)이 아니라 이 마킹으로 한다 — 핸들러가 우연히
|
|
76
|
+
* `{ ok, data }` 모양의 도메인 데이터(예: 외부 API 프록시 응답)를 반환해도 정상적으로 data 로
|
|
77
|
+
* 감싸진다. 비열거라 JSON 직렬화·spread 에는 나타나지 않는다.
|
|
78
|
+
*/
|
|
79
|
+
export const ENVELOPE_MARK: unique symbol;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify setErrorHandler 에 등록할 핸들러 빌더.
|
|
3
|
+
*
|
|
4
|
+
* 우선순위:
|
|
5
|
+
* 1) AJV validation 에러 → MegaValidationError 변환 (ADR-090)
|
|
6
|
+
* 2) MegaHttpError → 그대로 envelope (status/code/message/details 보존)
|
|
7
|
+
* 3) MegaError → 500 + code/message 보존 (도메인 에러지만 HTTP status 없음)
|
|
8
|
+
* 4) 그 외 일반 Error → MegaInternalError (스택·메시지는 로그만, envelope 엔 마스킹)
|
|
9
|
+
*
|
|
10
|
+
* @param {{ exposeInternalDetails?: boolean }} [opts]
|
|
11
|
+
* - exposeInternalDetails: true 면 일반 Error 의 message 를 envelope 에 노출.
|
|
12
|
+
* 기본 false (보안 — 내부 구현 누출 방지).
|
|
13
|
+
* @returns {(error: Error, req: any, reply: any) => any}
|
|
14
|
+
*/
|
|
15
|
+
export function buildErrorHandler({ exposeInternalDetails }?: {
|
|
16
|
+
exposeInternalDetails?: boolean;
|
|
17
|
+
}): (error: Error, req: any, reply: any) => any;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 폼 바디 파서 자동 등록 결과 요약(디버그·테스트용).
|
|
3
|
+
* @typedef {Object} FormbodySummary
|
|
4
|
+
* @property {boolean} enabled - 등록 여부.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* views 설정이 서버사이드 렌더를 켰는지 (Boolean — `has*`). `views.dir` 가 비지 않은 문자열일 때만 true.
|
|
8
|
+
* registerTemplate 의 옵트인 판정과 같은 기준이라 폼 파싱이 뷰 등록과 정확히 짝을 이룬다.
|
|
9
|
+
* @param {unknown} views
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function hasServerViews(views: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Fastify 인스턴스에 `@fastify/formbody` 를 등록한다 — 서버사이드 뷰를 켠 앱에 한해 옵트인.
|
|
15
|
+
*
|
|
16
|
+
* `views` 가 없으면 **미등록**(JSON·text 만 파싱, 기존 동작 유지). 호출 시점은 라우트 등록 이전이어야
|
|
17
|
+
* 한다(content-type 파서는 글로벌이라 등록 후 도착하는 모든 요청에 적용) — MegaApp 생성자가 그 지점이다.
|
|
18
|
+
*
|
|
19
|
+
* @param {import('fastify').FastifyInstance} fastify - 대상 앱 Fastify 인스턴스.
|
|
20
|
+
* @param {Object} opts
|
|
21
|
+
* @param {unknown} opts.views - `MegaViewsConfig`(dir/layoutDir/...). 서버 뷰 옵트인 판정에 쓴다.
|
|
22
|
+
* @param {string} [opts.appName] - 앱 이름(로그용).
|
|
23
|
+
* @param {{ debug?: Function }} [opts.logger] - 흐름 길목 debug 로그(선택).
|
|
24
|
+
* @returns {FormbodySummary}
|
|
25
|
+
*/
|
|
26
|
+
export function registerFormbody(fastify: import("fastify").FastifyInstance, { views, appName, logger }?: {
|
|
27
|
+
views: unknown;
|
|
28
|
+
appName?: string;
|
|
29
|
+
logger?: {
|
|
30
|
+
debug?: Function;
|
|
31
|
+
};
|
|
32
|
+
}): FormbodySummary;
|
|
33
|
+
/**
|
|
34
|
+
* 폼 바디 파서 자동 등록 결과 요약(디버그·테스트용).
|
|
35
|
+
*/
|
|
36
|
+
export type FormbodySummary = {
|
|
37
|
+
/**
|
|
38
|
+
* - 등록 여부.
|
|
39
|
+
*/
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
export class MegaHubLink {
|
|
2
|
+
/**
|
|
3
|
+
* 링크 라이프사이클 이벤트 (wire 타입과 충돌하지 않도록 `link.*` reserved). {@link MegaHubLink#on}
|
|
4
|
+
* 으로 구독한다. **핸들러 인자는 이벤트마다 다르다**(L-2):
|
|
5
|
+
* - `RECONNECTED` → `{ hubId: string }` — 재연결 성공(새 등록 완료). hubId 는 새 hub 의 식별자.
|
|
6
|
+
* - `DISCONNECTED` → `{ code: number, reason: string, isDrain: boolean }` — 등록 후 절단.
|
|
7
|
+
* `isDrain` 은 close code 가 4503(DRAIN)인지(다른 hub 로 회전 신호, ADR-098).
|
|
8
|
+
* - `RECONNECT_FAILED`→ `{ error: Error }` — 재연결 재시도 소진(더 이상 자동 재시도 없음).
|
|
9
|
+
* @type {Readonly<{ RECONNECTED: string, DISCONNECTED: string, RECONNECT_FAILED: string }>}
|
|
10
|
+
*/
|
|
11
|
+
static EVENTS: Readonly<{
|
|
12
|
+
RECONNECTED: string;
|
|
13
|
+
DISCONNECTED: string;
|
|
14
|
+
RECONNECT_FAILED: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} [opts]
|
|
18
|
+
* @param {string} [opts.url] - hub URL (예: 'ws://hub1.internal:19991/_hub'). 누락 시 throw.
|
|
19
|
+
* @param {string} [opts.token] - Bearer 토큰 (wsHub.acceptedTokens 중 하나). 누락 시 throw.
|
|
20
|
+
* @param {string} [opts.bridgeId] - 운영 식별자 (예: 'main-1'). 누락 시 throw.
|
|
21
|
+
* @param {string} [opts.instanceId] - REGISTER instanceId. 기본 bridgeId.
|
|
22
|
+
* @param {string[]} [opts.capabilities] - 선언 capability 목록. 기본 [].
|
|
23
|
+
* @param {import('../lib/mega-retry.js').MegaRetryOptions} [opts.retry] - 지정 시 재연결 활성(ADR-098).
|
|
24
|
+
* `{ retries, minTimeout, maxTimeout, factor, jitter }`. 미지정 → 재연결 끔(기본 동작).
|
|
25
|
+
* @param {number} [opts.connectTimeoutMs=10000] - register_ok 미수신 시 attempt reject 까지(M4).
|
|
26
|
+
* @param {import('./ws-compression.js').WsCompressionConfig} [opts.compression] - Bridge↔Hub link
|
|
27
|
+
* per-message deflate 압축(ADR-078). Global `wsHub.compression` 블록과 동일
|
|
28
|
+
* 스키마 — hub 서버와 양쪽이 협상해야 활성(RFC 7692). 디폴트 OFF. 잘못된 값은 즉시 throw.
|
|
29
|
+
* @param {{ warn?: Function, debug?: Function, info?: Function, error?: Function }} [opts.logger]
|
|
30
|
+
* @param {typeof import('ws').WebSocket} [opts.WebSocketCtor] - 테스트 주입용 ws 생성자.
|
|
31
|
+
* @param {number} [opts.maxBufferedBytes=16777216] - 송신 버퍼(bufferedAmount) 상한(바이트). 초과 시
|
|
32
|
+
* 백프레셔로 보고 소켓을 닫고(1013) 송신을 명시 실패시킨다 — hub 가 느릴 때 bridge 힙이 무한
|
|
33
|
+
* 적재되는 것을 막는다. 재연결(retry) 설정 시 자동 회복.
|
|
34
|
+
*/
|
|
35
|
+
constructor({ url, token, bridgeId, instanceId, capabilities, retry, connectTimeoutMs, compression, logger, WebSocketCtor, maxBufferedBytes }?: {
|
|
36
|
+
url?: string;
|
|
37
|
+
token?: string;
|
|
38
|
+
bridgeId?: string;
|
|
39
|
+
instanceId?: string;
|
|
40
|
+
capabilities?: string[];
|
|
41
|
+
retry?: import("../lib/mega-retry.js").MegaRetryOptions;
|
|
42
|
+
connectTimeoutMs?: number;
|
|
43
|
+
compression?: import("./ws-compression.js").WsCompressionConfig;
|
|
44
|
+
logger?: {
|
|
45
|
+
warn?: Function;
|
|
46
|
+
debug?: Function;
|
|
47
|
+
info?: Function;
|
|
48
|
+
error?: Function;
|
|
49
|
+
};
|
|
50
|
+
WebSocketCtor?: typeof import("ws").WebSocket;
|
|
51
|
+
maxBufferedBytes?: number;
|
|
52
|
+
});
|
|
53
|
+
_url: string;
|
|
54
|
+
_token: string;
|
|
55
|
+
_bridgeId: string;
|
|
56
|
+
_instanceId: string;
|
|
57
|
+
_capabilities: string[];
|
|
58
|
+
_log: {
|
|
59
|
+
warn?: Function;
|
|
60
|
+
debug?: Function;
|
|
61
|
+
info?: Function;
|
|
62
|
+
error?: Function;
|
|
63
|
+
};
|
|
64
|
+
_WS: typeof WebSocket;
|
|
65
|
+
/** 재연결 옵션 (없으면 재연결 비활성). @type {import('../lib/mega-retry.js').MegaRetryOptions | null} */
|
|
66
|
+
_retry: import("../lib/mega-retry.js").MegaRetryOptions | null;
|
|
67
|
+
/** 한 attempt 의 register_ok 대기 한도(M4). reconnect 에서도 동일 적용. @type {number} */
|
|
68
|
+
_connectTimeoutMs: number;
|
|
69
|
+
/** 송신 버퍼(bufferedAmount) 상한 — 초과 시 백프레셔로 소켓을 닫는다(기본 16 MiB). @type {number} */
|
|
70
|
+
_maxBufferedBytes: number;
|
|
71
|
+
/** @type {false | Object} ws 클라이언트 perMessageDeflate 로 전달(_connectOnce). */
|
|
72
|
+
_perMessageDeflate: false | Object;
|
|
73
|
+
/** @type {import('ws').WebSocket | null} */
|
|
74
|
+
_ws: import("ws").WebSocket | null;
|
|
75
|
+
/** @type {boolean} register_ok 수신 여부. */
|
|
76
|
+
_isRegistered: boolean;
|
|
77
|
+
/** @type {string | null} hub 가 부여한 hubId. */
|
|
78
|
+
_hubId: string | null;
|
|
79
|
+
/** @type {number} register_ok 의 heartbeatMs(0 이면 heartbeat 끔). */
|
|
80
|
+
_heartbeatMs: number;
|
|
81
|
+
/** @type {ReturnType<typeof setInterval> | null} */
|
|
82
|
+
_hbTimer: ReturnType<typeof setInterval> | null;
|
|
83
|
+
/** type(wire)/lifecycle event → 핸들러 집합. @type {Map<string, Set<Function>>} */
|
|
84
|
+
_handlers: Map<string, Set<Function>>;
|
|
85
|
+
/** close() 로 의도적으로 닫는 중인지 — true 면 자동 재연결하지 않는다. @type {boolean} */
|
|
86
|
+
_isClosing: boolean;
|
|
87
|
+
/** 재연결 진행 중 가드(중복 reconnect 방지). @type {boolean} */
|
|
88
|
+
_isReconnecting: boolean;
|
|
89
|
+
/** close() 가 진행 중인 재시도(백오프 대기)를 취소하기 위한 컨트롤러. @type {AbortController | null} */
|
|
90
|
+
_abort: AbortController | null;
|
|
91
|
+
/** register_ok 로 협상된 hub 프로토콜 버전(등록 전 null, 레거시 hub 면 1). @type {number | null} */
|
|
92
|
+
_negotiatedProtocolVersion: number | null;
|
|
93
|
+
/**
|
|
94
|
+
* 레거시 hub 모드 — REGISTER 의 `protocolVersion` 필드를 모르는 구버전 hub(strict 스키마가
|
|
95
|
+
* `hub.invalid_message` 로 거부)로 판정되면 true. 이후 register 는 필드 없이(v1) 보낸다.
|
|
96
|
+
* @type {boolean}
|
|
97
|
+
*/
|
|
98
|
+
_isLegacyHub: boolean;
|
|
99
|
+
/** hub 가 부여한 hubId (register 전엔 null). */
|
|
100
|
+
get hubId(): string;
|
|
101
|
+
/** register_ok 를 받아 사용 가능한 상태인지. */
|
|
102
|
+
get isRegistered(): boolean;
|
|
103
|
+
/** 협상된 hub 프로토콜 버전 (register 전엔 null, 레거시 hub 면 1). */
|
|
104
|
+
get negotiatedProtocolVersion(): number;
|
|
105
|
+
/** 소켓이 OPEN(=1) 인지. */
|
|
106
|
+
get isOpen(): boolean;
|
|
107
|
+
/** 재연결이 활성(retry 옵션 지정)인지. */
|
|
108
|
+
get canReconnect(): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* 수신 메시지/라이프사이클 핸들러 등록. 같은 type 에 여러 개 등록 가능(모두 호출).
|
|
111
|
+
* @param {string} wireType - {@link HUB_MESSAGE_TYPES} 의 값(`hub.broadcast` 등) 또는
|
|
112
|
+
* {@link MegaHubLink.EVENTS} 의 라이프사이클 이벤트(`link.*`).
|
|
113
|
+
* @param {(msg: Object) => void} handler
|
|
114
|
+
* @returns {this}
|
|
115
|
+
*/
|
|
116
|
+
on(wireType: string, handler: (msg: Object) => void): this;
|
|
117
|
+
/**
|
|
118
|
+
* hub 연결 + REGISTER 핸드셰이크. register_ok 수신 시 resolve.
|
|
119
|
+
*
|
|
120
|
+
* `retry` 옵션이 있으면 최초 연결도 지수 백오프로 재시도한다(ADR-098). 없으면 단발 시도.
|
|
121
|
+
*
|
|
122
|
+
* @param {Object} [opts]
|
|
123
|
+
* @param {number} [opts.connectTimeoutMs] - register_ok 대기 한도 override(M4). **이 값은 인스턴스에
|
|
124
|
+
* 저장되어(`this._connectTimeoutMs`) 이후 자동 재연결 attempt 에도 동일하게 적용된다** — 이 한 번의
|
|
125
|
+
* connect() 에만 적용되는 것이 아니다(L-1). 재연결마다 같은 타임아웃을 쓰는 동작은 의도된 것이다.
|
|
126
|
+
* @returns {Promise<{ hubId: string, heartbeatMs: number }>}
|
|
127
|
+
* @throws register 전 에러/종료/타임아웃(재시도 소진 포함) 시 reject.
|
|
128
|
+
*/
|
|
129
|
+
connect({ connectTimeoutMs }?: {
|
|
130
|
+
connectTimeoutMs?: number;
|
|
131
|
+
}): Promise<{
|
|
132
|
+
hubId: string;
|
|
133
|
+
heartbeatMs: number;
|
|
134
|
+
}>;
|
|
135
|
+
/**
|
|
136
|
+
* 단일 연결 시도 — ws open → REGISTER → register_ok. 한 attempt 의 Promise 를 돌려준다.
|
|
137
|
+
* 재시도/재연결은 호출부({@link MegaHubLink#connect}/{@link MegaHubLink#_reconnect})가 감싼다.
|
|
138
|
+
*
|
|
139
|
+
* @param {{ connectTimeoutMs: number }} args
|
|
140
|
+
* @returns {Promise<{ hubId: string, heartbeatMs: number }>}
|
|
141
|
+
* @private
|
|
142
|
+
*/
|
|
143
|
+
private _connectOnce;
|
|
144
|
+
/**
|
|
145
|
+
* 등록 후 연결이 끊겼을 때 처리 — DISCONNECTED emit + (retry 활성·의도적 close 아님) 자동 재연결.
|
|
146
|
+
* @param {number} code - WS close code (4503=drain 등).
|
|
147
|
+
* @param {string} reason
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
private _handleEstablishedClose;
|
|
151
|
+
/**
|
|
152
|
+
* 자동 재연결 — 백오프로 _connectOnce 반복. 성공 시 RECONNECTED, 소진 시 RECONNECT_FAILED emit.
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
private _reconnect;
|
|
157
|
+
/**
|
|
158
|
+
* 사용자 접속 알림 (JOIN). presence 등록.
|
|
159
|
+
* @param {{ userId: string, sessionId: string, channels: string[], metadata?: Object }} entry
|
|
160
|
+
* @returns {void}
|
|
161
|
+
*/
|
|
162
|
+
join(entry: {
|
|
163
|
+
userId: string;
|
|
164
|
+
sessionId: string;
|
|
165
|
+
channels: string[];
|
|
166
|
+
metadata?: Object;
|
|
167
|
+
}): void;
|
|
168
|
+
/**
|
|
169
|
+
* 단일 세션 종료 (LEAVE).
|
|
170
|
+
* @param {string} sessionId
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
173
|
+
leave(sessionId: string): void;
|
|
174
|
+
/**
|
|
175
|
+
* 다수 세션 일괄 종료 (BULK_LEAVE) — 여러 세션의 presence LEAVE 를 한 프레임으로 보낸다.
|
|
176
|
+
*
|
|
177
|
+
* 공개 API 다(프레임워크 내부는 현재 hub 의 bridge-gone 자동 감지에 의존해 직접 호출하지 않음).
|
|
178
|
+
* 다음 같은 **대량 정리** 상황에서 애플리케이션이 직접 호출한다:
|
|
179
|
+
* - 대량 logout(한 사용자의 여러 세션을 한 번에 종료),
|
|
180
|
+
* - 클라이언트 풀/탭 정리(연결 다수를 묶어 종료),
|
|
181
|
+
* - bridge 가 자체 drain 으로 보유 세션을 능동적으로 비울 때(소켓 close 를 기다리지 않고 선통지).
|
|
182
|
+
* 세션 1건이면 {@link MegaHubLink#leave} 를 쓰는 편이 단순하다.
|
|
183
|
+
*
|
|
184
|
+
* @param {string[]} sessionIds
|
|
185
|
+
* @returns {void}
|
|
186
|
+
*/
|
|
187
|
+
bulkLeave(sessionIds: string[]): void;
|
|
188
|
+
/**
|
|
189
|
+
* 채널 전체 푸시 (BROADCAST). hub 가 가입 bridge 들에 fan-out.
|
|
190
|
+
* @param {{ ns: string, channel: string, message: Object, exceptSessionIds?: string[] }} payload
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*/
|
|
193
|
+
broadcast(payload: {
|
|
194
|
+
ns: string;
|
|
195
|
+
channel: string;
|
|
196
|
+
message: Object;
|
|
197
|
+
exceptSessionIds?: string[];
|
|
198
|
+
}): void;
|
|
199
|
+
/**
|
|
200
|
+
* 특정 사용자에게 직접 (DIRECT). hub 가 userId→sessionIds[] fan-out (ADR-035).
|
|
201
|
+
* @param {{ userId: string, message: Object }} payload
|
|
202
|
+
* @returns {void}
|
|
203
|
+
*/
|
|
204
|
+
direct(payload: {
|
|
205
|
+
userId: string;
|
|
206
|
+
message: Object;
|
|
207
|
+
}): void;
|
|
208
|
+
/**
|
|
209
|
+
* 세션 메타데이터 갱신 (METADATA) — 명시 필드만(ADR-059).
|
|
210
|
+
* @param {{ sessionId: string, metadata: Object }} payload
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
updateMetadata(payload: {
|
|
214
|
+
sessionId: string;
|
|
215
|
+
metadata: Object;
|
|
216
|
+
}): void;
|
|
217
|
+
/**
|
|
218
|
+
* 바이너리 메타 프레임 (BINARY). raw bytes 후속 프레임 전송은 본 Step 미포함(envelope 한정).
|
|
219
|
+
* @param {{ ref: string, mimeType: string, bytes: number }} payload
|
|
220
|
+
* @returns {void}
|
|
221
|
+
*/
|
|
222
|
+
binary(payload: {
|
|
223
|
+
ref: string;
|
|
224
|
+
mimeType: string;
|
|
225
|
+
bytes: number;
|
|
226
|
+
}): void;
|
|
227
|
+
/** 연결 종료 — 의도적 close 표시 + 진행 중 재연결 취소 + heartbeat 정리 후 소켓 close. */
|
|
228
|
+
close(): void;
|
|
229
|
+
/**
|
|
230
|
+
* 타입별 송신 — register_ok 이후에만 허용. register 전 호출은 fail-closed throw.
|
|
231
|
+
* @param {string} type - wire type(`hub.*`).
|
|
232
|
+
* @param {Object} payload
|
|
233
|
+
* @returns {void}
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
private _send;
|
|
237
|
+
/**
|
|
238
|
+
* envelope 직렬화 후 소켓 송신.
|
|
239
|
+
* @param {Object} envelope
|
|
240
|
+
* @returns {void}
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
private _sendEnvelope;
|
|
244
|
+
/**
|
|
245
|
+
* 수신 메시지를 등록된 핸들러에 디스패치. 핸들러 throw 는 격리(다른 핸들러 보호).
|
|
246
|
+
* @param {import('../lib/hub-protocol.js').HubEnvelope} msg
|
|
247
|
+
* @returns {void}
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
private _dispatch;
|
|
251
|
+
/**
|
|
252
|
+
* 라이프사이클 이벤트 emit (RECONNECTED 등). 디스패치와 같은 핸들러 맵을 쓰되 인자는 이벤트 데이터.
|
|
253
|
+
* @param {string} event - {@link MegaHubLink.EVENTS} 값.
|
|
254
|
+
* @param {Object} data
|
|
255
|
+
* @returns {void}
|
|
256
|
+
* @private
|
|
257
|
+
*/
|
|
258
|
+
private _emit;
|
|
259
|
+
/** heartbeat 루프 시작 (heartbeatMs>0 일 때만). 프로세스 종료 막지 않게 unref. @private */
|
|
260
|
+
private _startHeartbeat;
|
|
261
|
+
/** heartbeat 루프 정지. @private */
|
|
262
|
+
private _stopHeartbeat;
|
|
263
|
+
}
|
|
264
|
+
import { WebSocket } from 'ws';
|