mega-framework 0.1.6 → 0.1.8
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/README.md +9 -0
- package/bin/mega-ws-hub.js +2 -2
- package/package.json +33 -9
- package/sample/crud/.env +10 -1
- package/sample/crud/.env.example +10 -1
- 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/locales/server/en.json +12 -1
- package/sample/crud/apps/main/locales/server/ko.json +12 -1
- 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 +10 -2
- package/sample/crud/package.json +3 -3
- package/sample/crud/scripts/start-ws-hub.sh +20 -6
- package/sample/simple/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/sample/simple/package.json +2 -2
- package/src/adapters/adapter-manager.js +2 -1
- package/src/adapters/adapter-options.js +44 -3
- package/src/adapters/file-adapter.js +9 -5
- package/src/adapters/file-session-adapter.js +4 -3
- package/src/adapters/maria-adapter.js +33 -7
- package/src/adapters/mega-cache-adapter.js +83 -6
- package/src/adapters/mega-db-adapter.js +10 -1
- package/src/adapters/mongo-adapter.js +40 -8
- package/src/adapters/postgres-adapter.js +33 -6
- package/src/adapters/redis-adapter.js +7 -3
- package/src/adapters/sqlite-adapter.js +26 -3
- package/src/cli/commands/console-cmd.js +3 -1
- package/src/cli/commands/new.js +13 -3
- package/src/cli/commands/scaffold.js +173 -33
- package/src/cli/generators/index.js +140 -3
- package/src/cli/index.js +437 -155
- package/src/cli/watch.js +188 -0
- package/src/core/ajv-mapper.js +30 -3
- package/src/core/boot.js +464 -245
- package/src/core/cluster-metrics.js +13 -4
- package/src/core/ctx-builder.js +65 -3
- package/src/core/envelope.js +119 -12
- package/src/core/hub-link.js +89 -18
- package/src/core/i18n.js +11 -1
- package/src/core/index.js +7 -3
- package/src/core/mega-app.js +253 -505
- package/src/core/mega-cluster.js +4 -1
- 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 +131 -0
- package/src/core/router.js +70 -65
- package/src/core/scope-registry.js +1 -0
- package/src/core/security.js +70 -12
- package/src/core/session-store.js +14 -1
- 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 +636 -0
- package/src/core/ws-roster.js +50 -8
- package/src/core/ws-upgrade.js +223 -12
- package/src/index.js +1 -1
- package/src/lib/hub-protocol.js +29 -0
- package/src/lib/mega-circuit-breaker.js +5 -3
- package/src/lib/mega-health.js +35 -4
- package/src/lib/mega-job-queue.js +151 -34
- package/src/lib/mega-job.js +37 -1
- package/src/lib/mega-metrics.js +31 -13
- package/src/lib/mega-plugin.js +34 -3
- package/src/lib/mega-schedule.js +40 -22
- package/src/lib/mega-shutdown.js +114 -39
- package/src/lib/mega-tracing.js +66 -19
- package/src/lib/mega-worker.js +33 -6
- package/src/lib/otel-resource.js +36 -0
- package/src/{cli → lib}/ws-hub.js +139 -15
- 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/adr/code.tpl +23 -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 +93 -0
- package/types/adapters/file-adapter.d.ts +105 -0
- package/types/adapters/file-session-adapter.d.ts +103 -0
- package/types/adapters/index.d.ts +20 -0
- package/types/adapters/maria-adapter.d.ts +117 -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 +73 -0
- package/types/adapters/mega-db-adapter.d.ts +50 -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 +150 -0
- package/types/adapters/nats-adapter.d.ts +108 -0
- package/types/adapters/postgres-adapter.d.ts +141 -0
- package/types/adapters/redis-adapter.d.ts +78 -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 +112 -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 +122 -0
- package/types/cli/index.d.ts +234 -0
- package/types/cli/template-engine.d.ts +40 -0
- package/types/cli/watch.d.ts +59 -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 +103 -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 +266 -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 +93 -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 +25 -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 +108 -0
- package/types/core/ws-upgrade.d.ts +260 -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 +243 -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 +48 -0
- package/types/lib/mega-job-queue.d.ts +188 -0
- package/types/lib/mega-job-worker.d.ts +130 -0
- package/types/lib/mega-job.d.ts +145 -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 +129 -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 +259 -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,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 브라우저↔Bridge WS 프레임 최대 크기 디폴트 (bytes, L-3 / ADR-099).
|
|
3
|
+
* 1 MiB — Hub(`DEFAULT_MAX_PAYLOAD_BYTES`, src/lib/ws-hub.js)와 대칭. 초과 프레임은 ws 가 1009 close.
|
|
4
|
+
* core→cli 역방향 import 를 피하려 값을 여기 별도 정의(두 곳 모두 1 MiB 로 동일).
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_WS_MAX_PAYLOAD_BYTES: 1048576;
|
|
7
|
+
/**
|
|
8
|
+
* MegaApp — 한 도메인 집합(hosts)에 대응하는 Fastify 인스턴스 래퍼.
|
|
9
|
+
*
|
|
10
|
+
* baseline — Fastify 인스턴스 보유, listen/close 라이프사이클.
|
|
11
|
+
* 라우터 등록·미들웨어 통합.
|
|
12
|
+
* 자동 envelope (ADR-018) + 글로벌 에러 핸들러 (ADR-025/090).
|
|
13
|
+
*
|
|
14
|
+
* ADR-002 (Fastify), ADR-063 (앱당 Fastify 인스턴스 격리), ADR-004 (dev *.localhost).
|
|
15
|
+
*/
|
|
16
|
+
export class MegaApp {
|
|
17
|
+
/**
|
|
18
|
+
* WS ASP 옵트인 정규화. masterSecret 없으면 null (평문 WS).
|
|
19
|
+
* @param {{ masterSecret?: string, websocket?: { namespaces?: string[] } }} [asp]
|
|
20
|
+
* @returns {{ masterSecret: string, namespaces: string[] } | null}
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
private static _normalizeWsAsp;
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} opts
|
|
26
|
+
* @param {string} opts.name - 앱 이름 (apps/<name> 폴더와 일치, ADR-067 검증됨)
|
|
27
|
+
* @param {string[]} [opts.hosts] - 도메인 매핑 (scaffold 모드 필수, single 모드 옵셔널)
|
|
28
|
+
* @param {'scaffold'|'single'} [opts.mode='scaffold'] - 부팅 모드 (ADR-013)
|
|
29
|
+
* @param {Object} [opts.fastifyOptions] - Fastify 생성 옵션 (logger 등)
|
|
30
|
+
* @param {import('pino').Logger|false} [opts.logger] - pino 로거 인스턴스 (ADR-023/141). bootApp 이
|
|
31
|
+
* `global.logger`(MegaLoggerConfig)로 만들어 모든 앱에 주입한다. 미주입이면 `false`(무로그). Fastify 가
|
|
32
|
+
* 요청별 child(`req.log`, reqId 바인딩)를 만들고, `ctx.log` 가 이를 노출한다(trace_id 는 mixin 자동 주입).
|
|
33
|
+
* @param {boolean} [opts.exposeInternalDetails=false] - true 면 일반 Error 메시지를
|
|
34
|
+
* envelope 에 노출 (디버그용). 기본 false — 보안 (ADR-025).
|
|
35
|
+
* @param {Object|false} [opts.helmet] - fastify-helmet 옵션 (보안 헤더). false=미등록, undefined=디폴트 ON
|
|
36
|
+
* (ADR-047/127). 라우트 옵션 오버라이드는 완전 교체(ADR-073).
|
|
37
|
+
* @param {Object|false} [opts.cors] - fastify-cors 옵션. false=미등록, undefined=origin:false(교차출처 거부, 안전 디폴트).
|
|
38
|
+
* @param {Object|false} [opts.rateLimit] - fastify-rate-limit 옵션. false=미등록, undefined=디폴트(IP당 1000/min, ADR-048/197).
|
|
39
|
+
* 멀티 인스턴스 분산 카운팅은 `caches.rate` 별명(Redis), 미선언 시 in-memory 폴백.
|
|
40
|
+
* @param {Object|false} [opts.csrf] - fastify-csrf-protection 옵션. false=미등록, undefined=디폴트 ON.
|
|
41
|
+
* ADR-051: JSON 요청은 토큰 면제+Origin 검증, 폼 요청만 토큰 검증.
|
|
42
|
+
* @param {{ masterSecret?: string, http?: { enabledPaths?: string[], driftMs?: number, headerSignal?: string, timestampHeader?: string, nonceCache?: any }, websocket?: { namespaces?: string[] } }} [opts.asp] -
|
|
43
|
+
* ASP 옵트인. `masterSecret` 있으면 모든 WS 프레임이 `E:`/`P:` prefix 코덱 경유;
|
|
44
|
+
* `websocket.namespaces` 에 포함된 경로만 기본 암호화(`E:`), 나머지는 평문(`P:`, ADR-083). `http.enabledPaths`
|
|
45
|
+
* 가 있으면 그 glob 경로에 HTTP ASP terminator 자동 등록(ADR-127). 미지정 시 raw JSON WS.
|
|
46
|
+
* @param {{ compression?: import('./ws-compression.js').WsCompressionConfig, maxPayloadBytes?: number }} [opts.websocket] -
|
|
47
|
+
* 브라우저↔Bridge WS 옵션 (ADR-078 / MegaWebsocketAppConfig). `compression`
|
|
48
|
+
* 블록으로 per-message deflate 옵트인. 디폴트 OFF. 부팅 시 threshold 음수 / serverMaxWindowBits
|
|
49
|
+
* 범위(9~15) 위반 throw. `maxPayloadBytes` 는 WS 프레임 최대 크기(L-3, 디폴트 1 MiB — Hub 와 대칭);
|
|
50
|
+
* 초과 프레임은 ws 가 1009 close. 양의 정수 아니면 디폴트로 폴백(부팅 검증은 config-validator 가 throw).
|
|
51
|
+
* @param {Record<string, string>} [opts.databases] - 어댑터 별명 맵 `{ alias: globalKey }` (ADR-102
|
|
52
|
+
* 글로벌 공유). `ctx.db(alias)` 가 globalKey 로 바꿔 전역 공유 인스턴스(MegaAdapterManager)를 반환.
|
|
53
|
+
* 실 인스턴스는 `mega.config.js` 의 `services.databases.<globalKey>` 에 정의되고 매니저가 소유·connect.
|
|
54
|
+
* @param {Record<string, string>} [opts.caches] - 캐시 별명 맵 `{ alias: globalKey }` (`ctx.cache(alias)`).
|
|
55
|
+
* @param {Record<string, string>} [opts.buses] - 버스 별명 맵 `{ alias: globalKey }` (`ctx.bus(alias)`).
|
|
56
|
+
* @param {Record<string, string>} [opts.locks] - 락 별명 맵 `{ alias: globalKey }` (`ctx.lock(alias)`, ADR-113).
|
|
57
|
+
* @param {{ maxFileSize?: number, maxFiles?: number, allowedMimeTypes?: string[] }} [opts.upload] -
|
|
58
|
+
* 파일 업로드 옵트인 (MegaUploadConfig, per ADR-133). 있으면 `@fastify/multipart` 를 자동 등록해
|
|
59
|
+
* `req.file()`/`req.files()`/`req.saveUploads(dir)` 를 제공한다(없으면 multipart 요청 415).
|
|
60
|
+
* `maxFileSize`(디폴트 10 MB)·`maxFiles`(미지정=무제한)·`allowedMimeTypes`(빈 배열=전부 허용, `image/*`
|
|
61
|
+
* 와일드카드 지원). MIME 화이트리스트 위반은 415, 크기·개수 초과는 413. CSRF(폼 토큰)는 보안 플러그인이,
|
|
62
|
+
* ASP(body 평문 통과)는 ASP terminator 가 자동 통합. 트레이싱 `mega.upload.*` + 메트릭 `mega_upload_*`.
|
|
63
|
+
* @param {false | { store: { driver: 'file'|'redis', [k: string]: any }, secret?: string, ttlMs?: number, rolling?: boolean, cookie?: object, csrf?: boolean }} [opts.session] -
|
|
64
|
+
* 세션 옵트인 (ADR-129/046). `store` = 백엔드(file/redis) inline config. `secret` =
|
|
65
|
+
* 쿠키 HMAC 시크릿(미지정 시 `opts.sessionSecret` — bootApp 이 global `server.sessionSecret` 주입).
|
|
66
|
+
* `ttlMs`(기본 24h)·`rolling`(기본 true)·`cookie`(httpOnly/secure/sameSite). `csrf:true` 면 CSRF 를
|
|
67
|
+
* 세션 모드로(시크릿을 세션 `_csrf` 키에 바인딩). `false`/미지정 → 세션 비활성.
|
|
68
|
+
* @param {string} [opts.sessionSecret] - 세션 쿠키 HMAC 시크릿(global `server.sessionSecret`). `session.secret` 미지정 시 사용.
|
|
69
|
+
* @param {{ default?: string, available?: string[], fallback?: string, cookieName?: string, autoComplete?: { enabled?: boolean, dir?: string, debounceMs?: number }, localesDir?: string, resources?: object, exposeTranslations?: boolean, translationsPath?: string }} [opts.i18n] -
|
|
70
|
+
* i18n 옵트인 (ADR-037/038/039/135). 있으면 앱 전용 `i18next` 인스턴스를 등록해
|
|
71
|
+
* `req.lang`/`req.t`/`req.setLocale`/`req.translations` + `ctx.lang`/`ctx.t` 를 제공한다(없으면 ctx.t 는
|
|
72
|
+
* passthrough). 언어 결정은 **쿠키만**(ADR-038, `cookieName` 디폴트 `mega.lang`). scope 분리(server/client,
|
|
73
|
+
* ADR-039) — `ctx.t()` 는 server scope, `GET /i18n/translations` 는 client scope 만 노출. dev 면 누락 키
|
|
74
|
+
* 자동 생성(saveMissing axion 패턴, `autoComplete`). 트레이싱 `mega.i18n.*` + 메트릭 `mega_i18n_*`.
|
|
75
|
+
* @param {{ dir?: string, layoutDir?: string, partialsDir?: string, defaultLayout?: string, cache?: boolean }} [opts.views] -
|
|
76
|
+
* 서버사이드 템플릿 옵트인 (ADR-011/136 — EJS + ejs-mate). `dir`(뷰 루트, 예
|
|
77
|
+
* `apps/<name>/views`)이 있을 때만 `reply.render(view, data, { layout })`(정본 res.render) + `ctx.render`
|
|
78
|
+
* 를 제공한다(없으면 미정의 → 호출 시 fail-fast). 렌더 data 에 요청별 i18n `t`/`lang` 자동 병합(다국어 뷰).
|
|
79
|
+
* XSS auto-escape(`<%=`) 기본 + 경로 탐색 차단. 트레이싱 `mega.template.*` + 메트릭 `mega_template_*`.
|
|
80
|
+
* @param {{ enabled?: boolean, path?: string, info?: { title?: string, version?: string, description?: string }, auth?: Function[], theme?: Object }} [opts.openapi] -
|
|
81
|
+
* OpenAPI/Swagger 옵트인 (MegaOpenApiAppConfig, ADR-070/140 — `@fastify/swagger` + `@fastify/swagger-ui`).
|
|
82
|
+
* `enabled:true` 일 때만 라우트 schema 를 OpenAPI 3.x 명세로 수집하고 `path`(디폴트 `/docs`)에 swagger-ui 를
|
|
83
|
+
* 등록한다(디폴트 OFF — 프로덕션 노출 방지). `info`={title,version,description}. `auth` 미들웨어 배열로 docs
|
|
84
|
+
* 접근 보호(빈 배열=공개, `(req, reply, ctx)` 시그니처). `theme`=swagger-ui uiConfig. 라우트 옵션
|
|
85
|
+
* `openapi:{tags,summary,description,deprecated}` 가 명세에 반영(router.js).
|
|
86
|
+
* @param {{ enabled?: boolean, dir?: string, prefix?: string, cacheControl?: string, dotfiles?: boolean }} [opts.staticAssets] -
|
|
87
|
+
* 정적 자산 옵트인 (MegaStaticAssetsAppConfig, ADR-071/139 — `@fastify/static`). `enabled:true` + 실존 `dir`
|
|
88
|
+
* (서빙 루트, 예 `apps/<name>/public`)일 때만 `${prefix}/<파일>`(prefix 디폴트 `/static`)로 디스크 파일을 서빙
|
|
89
|
+
* (디폴트 OFF). `cacheControl` = raw `Cache-Control` 헤더 문자열(예 `'public, max-age=3600'`). `dotfiles`
|
|
90
|
+
* 디폴트 false(`.git`/`.env` 차단). `enabled:true` 인데 `dir` 누락/미존재 시 프로덕션 부팅 throw / dev warn+skip.
|
|
91
|
+
* prefix 가 `health.metricsPath` 와 같으면 부팅 throw(ADR-072).
|
|
92
|
+
* @param {{ enabled?: boolean, paths?: { live?: string, ready?: string }, exposeCheckDetails?: boolean, exposeMetrics?: boolean, metricsPath?: string, metricsAllowList?: string[] }} [opts.health] -
|
|
93
|
+
* 운영 관측성 config(Global-only, ADR-072/131). bootApp 이 global `health` 블록을 주입한다.
|
|
94
|
+
* `exposeCheckDetails:true` 면 readiness 응답에 체크별 전체 필드(error 메시지 등)를 노출 — 기본 false
|
|
95
|
+
* (체크별 `{ ok }` 만, ADR-186).
|
|
96
|
+
* `exposeMetrics:true` 면 `metricsPath`(디폴트 `/metrics`)에 Prometheus `/metrics` 라우트를 등록(보안 면제).
|
|
97
|
+
* `metricsAllowList` = 접근 허용 IP/CIDR(빈 배열이면 메인 포트 전체 노출, ADR-131). metricsPath 가 health
|
|
98
|
+
* 경로와 충돌하면 부팅 throw. SDK 초기화(MegaMetrics.init)는 bootApp/prepareRuntime 이 담당.
|
|
99
|
+
* @param {Array<{ plugin: Function, opts?: object }>} [opts.plugins] - 플러그인이 `mega.app.use(...)`
|
|
100
|
+
* 로 등록한 Fastify 플러그인 묶음(정본 §14 hook표 — "모든 앱 인스턴스에 등록"). 부팅 orchestrator
|
|
101
|
+
* (`bootApp`)가 `MegaPluginHost.fastifyPlugins` 를 모든 앱에 주입한다(ADR-123). 각 원소는
|
|
102
|
+
* `this.fastify.register(plugin, opts)` 로 등록.
|
|
103
|
+
* @param {Function[]} [opts.globalMiddlewares] - 플러그인이 `mega.middlewares.global(...)` 로 등록한
|
|
104
|
+
* 글로벌 미들웨어(모든 라우트 적용). orchestrator 가 주입한다. 계약: `async (req, reply, ctx) => void`
|
|
105
|
+
* — 라우트 핸들러와 동일한 canonical 시그니처(ADR-074/134). `ctx` 는 요청당 1회 만들어 핸들러와
|
|
106
|
+
* 공유하므로, 미들웨어가 `ctx` 에 심은 값을 핸들러가 본다. 기존 `(req, reply)` 미들웨어는 3번째
|
|
107
|
+
* 인자를 무시하므로 하위 호환.
|
|
108
|
+
* @param {Function[]} [opts.globalTransforms] - 앱/전역 레벨 응답 변환(ADR-021 체인의 "앱 → 전역"
|
|
109
|
+
* 구간, ADR-194). 계약: `async (req, reply, payload) => payload`. 라우트(+파일) transform 뒤·
|
|
110
|
+
* 자동 envelope wrap 직전에 배열 순서대로 실행(주입자가 앱 항목 먼저·전역 항목 뒤로 배열).
|
|
111
|
+
* health/metrics 등 `config.skipGlobalLifecycle` 라우트는 제외.
|
|
112
|
+
* @param {Function[]} [opts.globalAfters] - 앱/전역 레벨 응답 후 side-effect(ADR-194). 계약:
|
|
113
|
+
* `async (req, reply) => void` — 응답 전송 후(onResponse), throw 는 warn 로그 후 무시(ADR-091).
|
|
114
|
+
* 라우트(+파일) after 뒤에 실행. `config.skipGlobalLifecycle` 라우트는 제외.
|
|
115
|
+
*/
|
|
116
|
+
constructor(opts: {
|
|
117
|
+
name: string;
|
|
118
|
+
hosts?: string[];
|
|
119
|
+
mode?: "scaffold" | "single";
|
|
120
|
+
fastifyOptions?: Object;
|
|
121
|
+
logger?: import("pino").Logger | false;
|
|
122
|
+
exposeInternalDetails?: boolean;
|
|
123
|
+
helmet?: Object | false;
|
|
124
|
+
cors?: Object | false;
|
|
125
|
+
rateLimit?: Object | false;
|
|
126
|
+
csrf?: Object | false;
|
|
127
|
+
asp?: {
|
|
128
|
+
masterSecret?: string;
|
|
129
|
+
http?: {
|
|
130
|
+
enabledPaths?: string[];
|
|
131
|
+
driftMs?: number;
|
|
132
|
+
headerSignal?: string;
|
|
133
|
+
timestampHeader?: string;
|
|
134
|
+
nonceCache?: any;
|
|
135
|
+
};
|
|
136
|
+
websocket?: {
|
|
137
|
+
namespaces?: string[];
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
websocket?: {
|
|
141
|
+
compression?: import("./ws-compression.js").WsCompressionConfig;
|
|
142
|
+
maxPayloadBytes?: number;
|
|
143
|
+
};
|
|
144
|
+
databases?: Record<string, string>;
|
|
145
|
+
caches?: Record<string, string>;
|
|
146
|
+
buses?: Record<string, string>;
|
|
147
|
+
locks?: Record<string, string>;
|
|
148
|
+
upload?: {
|
|
149
|
+
maxFileSize?: number;
|
|
150
|
+
maxFiles?: number;
|
|
151
|
+
allowedMimeTypes?: string[];
|
|
152
|
+
};
|
|
153
|
+
session?: false | {
|
|
154
|
+
store: {
|
|
155
|
+
driver: "file" | "redis";
|
|
156
|
+
[k: string]: any;
|
|
157
|
+
};
|
|
158
|
+
secret?: string;
|
|
159
|
+
ttlMs?: number;
|
|
160
|
+
rolling?: boolean;
|
|
161
|
+
cookie?: object;
|
|
162
|
+
csrf?: boolean;
|
|
163
|
+
};
|
|
164
|
+
sessionSecret?: string;
|
|
165
|
+
i18n?: {
|
|
166
|
+
default?: string;
|
|
167
|
+
available?: string[];
|
|
168
|
+
fallback?: string;
|
|
169
|
+
cookieName?: string;
|
|
170
|
+
autoComplete?: {
|
|
171
|
+
enabled?: boolean;
|
|
172
|
+
dir?: string;
|
|
173
|
+
debounceMs?: number;
|
|
174
|
+
};
|
|
175
|
+
localesDir?: string;
|
|
176
|
+
resources?: object;
|
|
177
|
+
exposeTranslations?: boolean;
|
|
178
|
+
translationsPath?: string;
|
|
179
|
+
};
|
|
180
|
+
views?: {
|
|
181
|
+
dir?: string;
|
|
182
|
+
layoutDir?: string;
|
|
183
|
+
partialsDir?: string;
|
|
184
|
+
defaultLayout?: string;
|
|
185
|
+
cache?: boolean;
|
|
186
|
+
};
|
|
187
|
+
openapi?: {
|
|
188
|
+
enabled?: boolean;
|
|
189
|
+
path?: string;
|
|
190
|
+
info?: {
|
|
191
|
+
title?: string;
|
|
192
|
+
version?: string;
|
|
193
|
+
description?: string;
|
|
194
|
+
};
|
|
195
|
+
auth?: Function[];
|
|
196
|
+
theme?: Object;
|
|
197
|
+
};
|
|
198
|
+
staticAssets?: {
|
|
199
|
+
enabled?: boolean;
|
|
200
|
+
dir?: string;
|
|
201
|
+
prefix?: string;
|
|
202
|
+
cacheControl?: string;
|
|
203
|
+
dotfiles?: boolean;
|
|
204
|
+
};
|
|
205
|
+
health?: {
|
|
206
|
+
enabled?: boolean;
|
|
207
|
+
paths?: {
|
|
208
|
+
live?: string;
|
|
209
|
+
ready?: string;
|
|
210
|
+
};
|
|
211
|
+
exposeCheckDetails?: boolean;
|
|
212
|
+
exposeMetrics?: boolean;
|
|
213
|
+
metricsPath?: string;
|
|
214
|
+
metricsAllowList?: string[];
|
|
215
|
+
};
|
|
216
|
+
plugins?: Array<{
|
|
217
|
+
plugin: Function;
|
|
218
|
+
opts?: object;
|
|
219
|
+
}>;
|
|
220
|
+
globalMiddlewares?: Function[];
|
|
221
|
+
globalTransforms?: Function[];
|
|
222
|
+
globalAfters?: Function[];
|
|
223
|
+
});
|
|
224
|
+
name: string;
|
|
225
|
+
hosts: string[];
|
|
226
|
+
mode: string;
|
|
227
|
+
/** @type {import('./ctx-builder.js').AdapterAccessors} */
|
|
228
|
+
_adapterAccessors: import("./ctx-builder.js").AdapterAccessors;
|
|
229
|
+
/** @type {MegaBruteForce|null} ctx.bruteForce 단축의 lazy 인스턴스(앱당 1개 재사용). */
|
|
230
|
+
_bruteForce: MegaBruteForce | null;
|
|
231
|
+
/**
|
|
232
|
+
* name → 서비스 클래스. 부팅 시 services-loader 가 `apps/<name>/services/*.js` 를 스캔해 채운다
|
|
233
|
+
* (ADR-148). 요청별 `ctx.services.<name>` lazy DI 가 이 맵을 보고 인스턴스화한다. 기본은 빈 Map
|
|
234
|
+
* (서비스 없는 앱·standalone 테스트).
|
|
235
|
+
* @type {Map<string, Function>}
|
|
236
|
+
*/
|
|
237
|
+
_serviceRegistry: Map<string, Function>;
|
|
238
|
+
/** @type {string|null} OpenAPI 옵트인 시 swagger-ui 경로(ADR-140). envelope onRoute 가 이 경로를 제외. */
|
|
239
|
+
_openapiPath: string | null;
|
|
240
|
+
/** @type {Function[]} */
|
|
241
|
+
_globalTransforms: Function[];
|
|
242
|
+
/** @type {Function[]} */
|
|
243
|
+
_globalAfters: Function[];
|
|
244
|
+
_wsAsp: {
|
|
245
|
+
masterSecret: string;
|
|
246
|
+
namespaces: string[];
|
|
247
|
+
};
|
|
248
|
+
/** @type {false | Object} _ensureWss 의 WebSocketServer perMessageDeflate 로 전달. */
|
|
249
|
+
_wsPerMessageDeflate: false | Object;
|
|
250
|
+
/** @type {number} _ensureWss 의 WebSocketServer maxPayload 로 전달. */
|
|
251
|
+
_wsMaxPayloadBytes: number;
|
|
252
|
+
/** @type {Router|null} single 모드 직접 등록(app.ws)용 lazy 라우터. */
|
|
253
|
+
_router: Router | null;
|
|
254
|
+
/** @type {WebSocketServer|null} noServer 모드 WS 핸드셰이커 (lazy). */
|
|
255
|
+
_wss: WebSocketServer | null;
|
|
256
|
+
fastify: Fastify.FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("pino").Logger<never, boolean>, Fastify.FastifyTypeProviderDefault> & PromiseLike<Fastify.FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("pino").Logger<never, boolean>, Fastify.FastifyTypeProviderDefault>> & {
|
|
257
|
+
__linterBrands: "SafePromiseLike";
|
|
258
|
+
};
|
|
259
|
+
_presence: MegaWsPresence;
|
|
260
|
+
/** @type {import('../adapters/mega-session-adapter.js').MegaSessionAdapter | null} */
|
|
261
|
+
_sessionStore: import("../adapters/mega-session-adapter.js").MegaSessionAdapter | null;
|
|
262
|
+
/**
|
|
263
|
+
* rate-limit Redis 백엔드 해석기를 만든다 (ADR-048). 앱이 `caches.rate` 별명을 선언했고 그 어댑터가
|
|
264
|
+
* Redis(`MegaRedisAdapter`)면 ioredis raw handle 을 반환하는 함수를, 아니면 `undefined` 를 돌려준다
|
|
265
|
+
* (= in-memory 폴백, 단일 인스턴스 dev). 해석은 보안 등록 시점(생성자)에 1회 호출되며, 어댑터는
|
|
266
|
+
* 그 전에 `MegaAdapterManager` 가 connect 해 둔 상태여야 한다(부팅 orchestrator 가 보장).
|
|
267
|
+
*
|
|
268
|
+
* Redis 가 아닌 캐시를 `rate` 로 매핑한 misconfiguration 은 silent 무시하지 않고 warn 후 in-memory
|
|
269
|
+
* 폴백한다(ADR-048 — rate-limit 은 ioredis 전용, 폴백은 비치명적·문서화된 디폴트).
|
|
270
|
+
*
|
|
271
|
+
* @param {Record<string, string>} [caches] - 앱 캐시 별명 맵 `{ alias: globalKey }`.
|
|
272
|
+
* @returns {(() => import('ioredis').Redis | null) | undefined}
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
private _buildRateStoreResolver;
|
|
276
|
+
/**
|
|
277
|
+
* Single 모드 WS 채널 등록. `router.ws` 와 동일 검증 위임.
|
|
278
|
+
*
|
|
279
|
+
* 단일 파일 모드에서 `routes/` 스캔 없이 직접 채널을 등록한다 (02-architecture §3). 등록은
|
|
280
|
+
* `fastify._megaWsRoutes` 에 보관되며 {@link MegaApp#listen} 시 upgrade 핸들오프가 배선된다.
|
|
281
|
+
*
|
|
282
|
+
* @param {string} path - WS 경로 (= namespace).
|
|
283
|
+
* @param {Function} ChannelClass - MegaWebSocketController 상속.
|
|
284
|
+
* @param {{ before?: Function[], schemas?: Object }} [opts]
|
|
285
|
+
* @returns {this} 체이닝용.
|
|
286
|
+
* @throws {import('./router.js').MegaRouteError} 형식/상속 위반.
|
|
287
|
+
*/
|
|
288
|
+
ws(path: string, ChannelClass: Function, opts?: {
|
|
289
|
+
before?: Function[];
|
|
290
|
+
schemas?: Object;
|
|
291
|
+
}): this;
|
|
292
|
+
/**
|
|
293
|
+
* 등록된 WS 라우트 목록 (router.ws + app.ws 통합). 비어 있으면 빈 배열.
|
|
294
|
+
* @returns {Array<{ path: string, ns: string, ChannelClass: Function, opts: { before?: Function[], schemas?: Object }, schemaValidators?: Record<string, import('ajv').ValidateFunction> | null }>}
|
|
295
|
+
*/
|
|
296
|
+
get wsRoutes(): Array<{
|
|
297
|
+
path: string;
|
|
298
|
+
ns: string;
|
|
299
|
+
ChannelClass: Function;
|
|
300
|
+
opts: {
|
|
301
|
+
before?: Function[];
|
|
302
|
+
schemas?: Object;
|
|
303
|
+
};
|
|
304
|
+
schemaValidators?: Record<string, import("ajv").ValidateFunction> | null;
|
|
305
|
+
}>;
|
|
306
|
+
/**
|
|
307
|
+
* 이 앱의 `ctx.db/cache/bus` 접근자 3종 (ADR-102). 요청 ctx 빌더(HTTP·WS)가 spread 해서 노출한다.
|
|
308
|
+
* 별명→globalKey→전역 공유 어댑터로 해석하며, 미선언 별명·미등록 키는 호출 시 throw.
|
|
309
|
+
* @returns {import('./ctx-builder.js').AdapterAccessors}
|
|
310
|
+
*/
|
|
311
|
+
get adapterAccessors(): import("./ctx-builder.js").AdapterAccessors;
|
|
312
|
+
/**
|
|
313
|
+
* 이 앱의 서비스 레지스트리(name → 클래스, ADR-148). 요청 ctx 빌더가 `ctx.services.<name>` lazy DI 의
|
|
314
|
+
* 클래스 lookup 출처로 읽는다. 부팅 orchestrator 가 `setServiceRegistry` 로 채운다.
|
|
315
|
+
* @returns {Map<string, Function>}
|
|
316
|
+
*/
|
|
317
|
+
get serviceRegistry(): Map<string, Function>;
|
|
318
|
+
/**
|
|
319
|
+
* 서비스 레지스트리 주입 (부팅 시 1회, ADR-148). services-loader 가 만든 name→Class 맵으로 교체한다.
|
|
320
|
+
* @param {Map<string, Function>} registry - name → 서비스 클래스.
|
|
321
|
+
* @returns {void}
|
|
322
|
+
* @throws {TypeError} Map 이 아니면.
|
|
323
|
+
*/
|
|
324
|
+
setServiceRegistry(registry: Map<string, Function>): void;
|
|
325
|
+
/**
|
|
326
|
+
* `ctx.bruteForce` 단축이 반환하는 MegaBruteForce 인스턴스(03-api-spec §10/§786, ADR-049/130).
|
|
327
|
+
* **lazy** — 처음 접근할 때만 만들고 앱당 재사용한다. 백엔드는 `caches.rate` 별명(ADR-049 디폴트)을
|
|
328
|
+
* 해석한 redis 어댑터. rate 캐시를 선언 안 한 앱이 이 getter 를 건드리면 cache 접근자가 fail-fast
|
|
329
|
+
* (`adapter.not_registered`) — brute-force 를 안 쓰는 앱은 이 getter 를 호출하지 않으므로 무해하다.
|
|
330
|
+
* 세분 정책(도메인별 maxAttempts 등)은 `new MegaBruteForce({ cache: ctx.cache('alias'), key, ... })`
|
|
331
|
+
* 로 직접 구성한다(별도 config 키는 향후 Step, ADR-130).
|
|
332
|
+
* @returns {MegaBruteForce}
|
|
333
|
+
*/
|
|
334
|
+
get bruteForce(): MegaBruteForce;
|
|
335
|
+
/** 현재 연결된 hub link (미연결 시 null). */
|
|
336
|
+
get hubLink(): import("./hub-link.js").MegaHubLink;
|
|
337
|
+
/**
|
|
338
|
+
* 이 bridge 를 hub 에 연결한다 (ADR-033/059) — 계약·옵션은 {@link MegaWsPresence#connectHub} 정본.
|
|
339
|
+
* @param {any} [config] - MegaBridgeHubConfig(§2.1) + 구독 채널.
|
|
340
|
+
* @returns {Promise<import('./hub-link.js').MegaHubLink>} 등록 완료된 link.
|
|
341
|
+
*/
|
|
342
|
+
connectHub(config?: any): Promise<import("./hub-link.js").MegaHubLink>;
|
|
343
|
+
/**
|
|
344
|
+
* 실 사용자 세션을 hub presence 에 등록하고 로컬 매핑을 만든다 — {@link MegaWsPresence#joinSession} 위임.
|
|
345
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn
|
|
346
|
+
* @param {{ userId: string, sessionId: string, channels?: string[], metadata?: Object }} [entry]
|
|
347
|
+
* @returns {this}
|
|
348
|
+
*/
|
|
349
|
+
joinSession(conn: import("./ws-upgrade.js").MegaWsConnection, entry?: {
|
|
350
|
+
userId: string;
|
|
351
|
+
sessionId: string;
|
|
352
|
+
channels?: string[];
|
|
353
|
+
metadata?: Object;
|
|
354
|
+
}): this;
|
|
355
|
+
/**
|
|
356
|
+
* 채널 broadcast — 로컬 즉시 전달 + (hub/NATS 연결 시) 클러스터 fan-out. {@link MegaWsPresence#broadcast} 위임.
|
|
357
|
+
* @param {{ ns: string, channel: string, message: { type: string, payload?: Object }, exceptSessionIds?: string[] }} args
|
|
358
|
+
* @returns {void}
|
|
359
|
+
*/
|
|
360
|
+
broadcast(args: {
|
|
361
|
+
ns: string;
|
|
362
|
+
channel: string;
|
|
363
|
+
message: {
|
|
364
|
+
type: string;
|
|
365
|
+
payload?: Object;
|
|
366
|
+
};
|
|
367
|
+
exceptSessionIds?: string[];
|
|
368
|
+
}): void;
|
|
369
|
+
/**
|
|
370
|
+
* 특정 사용자에게 직접 전송 (ADR-035) — {@link MegaWsPresence#directToUser} 위임.
|
|
371
|
+
* @param {string} userId
|
|
372
|
+
* @param {{ type: string, payload?: Object }} message
|
|
373
|
+
* @returns {void}
|
|
374
|
+
*/
|
|
375
|
+
directToUser(userId: string, message: {
|
|
376
|
+
type: string;
|
|
377
|
+
payload?: Object;
|
|
378
|
+
}): void;
|
|
379
|
+
/**
|
|
380
|
+
* 세션 presence 메타데이터 갱신 (ADR-059) — {@link MegaWsPresence#updateMetadata} 위임.
|
|
381
|
+
* @param {string} sessionId
|
|
382
|
+
* @param {Object} metadata
|
|
383
|
+
* @returns {this}
|
|
384
|
+
*/
|
|
385
|
+
updateMetadata(sessionId: string, metadata: Object): this;
|
|
386
|
+
/**
|
|
387
|
+
* NATS 클러스터 fan-out/roster 배선 (ADR-176, boot 자동 호출) — {@link MegaWsPresence#setWsCluster} 위임.
|
|
388
|
+
* @param {import('./ws-cluster.js').MegaWsCluster|null} cluster
|
|
389
|
+
* @returns {this}
|
|
390
|
+
*/
|
|
391
|
+
setWsCluster(cluster: import("./ws-cluster.js").MegaWsCluster | null): this;
|
|
392
|
+
/**
|
|
393
|
+
* ns(WS 채널 경로) 기준 접속자 목록 (ADR-176) — {@link MegaWsPresence#roster} 위임.
|
|
394
|
+
* @param {string} ns
|
|
395
|
+
* @returns {Array<{ sessionId: string, userId: string, metadata?: Object }>}
|
|
396
|
+
*/
|
|
397
|
+
roster(ns: string): Array<{
|
|
398
|
+
sessionId: string;
|
|
399
|
+
userId: string;
|
|
400
|
+
metadata?: Object;
|
|
401
|
+
}>;
|
|
402
|
+
/**
|
|
403
|
+
* 채널별 redis roster 배선 (ADR-177, boot 자동 호출) — {@link MegaWsPresence#setWsRoster} 위임.
|
|
404
|
+
* @param {import('./ws-roster.js').MegaWsRedisRoster|null} roster
|
|
405
|
+
* @returns {this}
|
|
406
|
+
*/
|
|
407
|
+
setWsRoster(roster: import("./ws-roster.js").MegaWsRedisRoster | null): this;
|
|
408
|
+
/**
|
|
409
|
+
* 채널들의 cluster-wide 접속자 목록 (ADR-177) — {@link MegaWsPresence#presenceList} 위임.
|
|
410
|
+
* @param {string[]} channels
|
|
411
|
+
* @returns {Promise<Array<{ sessionId: string, userId: string, metadata?: Object }>>}
|
|
412
|
+
*/
|
|
413
|
+
presenceList(channels: string[]): Promise<Array<{
|
|
414
|
+
sessionId: string;
|
|
415
|
+
userId: string;
|
|
416
|
+
metadata?: Object;
|
|
417
|
+
}>>;
|
|
418
|
+
/**
|
|
419
|
+
* 이 워커의 로컬 멤버 목록 (redis roster heartbeat 갱신 대상, ADR-177) — {@link MegaWsPresence#localRosterMembers} 위임.
|
|
420
|
+
* @returns {Array<{ channel: string, sessionId: string, member: { userId: string, metadata?: Object } }>}
|
|
421
|
+
*/
|
|
422
|
+
localRosterMembers(): Array<{
|
|
423
|
+
channel: string;
|
|
424
|
+
sessionId: string;
|
|
425
|
+
member: {
|
|
426
|
+
userId: string;
|
|
427
|
+
metadata?: Object;
|
|
428
|
+
};
|
|
429
|
+
}>;
|
|
430
|
+
/**
|
|
431
|
+
* broadcast payload 로컬 전달 (framework-internal — boot 의 MegaWsCluster 배선이 사용).
|
|
432
|
+
* @param {{ ns: string, channel?: string, message: { type: string, payload?: Object }, exceptSessionIds?: string[] }} payload
|
|
433
|
+
* @returns {void}
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
private _deliverBroadcast;
|
|
437
|
+
/**
|
|
438
|
+
* direct payload 로컬 전달 (framework-internal — boot 의 MegaWsCluster 배선이 사용).
|
|
439
|
+
* @param {{ userId: string, message: { type: string, payload?: Object } }} payload
|
|
440
|
+
* @returns {void}
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
443
|
+
private _deliverDirect;
|
|
444
|
+
/**
|
|
445
|
+
* hub 의 DISCONNECT(admin-kick, ADR-097) 라우팅 처리 (framework-internal).
|
|
446
|
+
* @param {{ sessionId: string, reason?: string, requeue?: boolean }} payload
|
|
447
|
+
* @returns {void}
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
private _handleHubDisconnect;
|
|
451
|
+
/**
|
|
452
|
+
* 로컬 WS 연결 등록 (driveWsConnection 이 호출 — framework-internal).
|
|
453
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn
|
|
454
|
+
* @returns {void}
|
|
455
|
+
*/
|
|
456
|
+
_trackWsConn(conn: import("./ws-upgrade.js").MegaWsConnection): void;
|
|
457
|
+
/**
|
|
458
|
+
* 로컬 WS 연결 해제 (close 시 driveWsConnection 이 호출 — framework-internal).
|
|
459
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn
|
|
460
|
+
* @returns {void}
|
|
461
|
+
*/
|
|
462
|
+
_untrackWsConn(conn: import("./ws-upgrade.js").MegaWsConnection): void;
|
|
463
|
+
set _hubLink(v: import("./hub-link.js").MegaHubLink | null);
|
|
464
|
+
/** @returns {import('./hub-link.js').MegaHubLink|null} */
|
|
465
|
+
get _hubLink(): import("./hub-link.js").MegaHubLink | null;
|
|
466
|
+
set _wsCluster(v: import("./ws-cluster.js").MegaWsCluster | null);
|
|
467
|
+
/** @returns {import('./ws-cluster.js').MegaWsCluster|null} */
|
|
468
|
+
get _wsCluster(): import("./ws-cluster.js").MegaWsCluster | null;
|
|
469
|
+
set _wsRoster(v: import("./ws-roster.js").MegaWsRedisRoster | null);
|
|
470
|
+
/** @returns {import('./ws-roster.js').MegaWsRedisRoster|null} */
|
|
471
|
+
get _wsRoster(): import("./ws-roster.js").MegaWsRedisRoster | null;
|
|
472
|
+
/** @returns {Map<string, import('./ws-upgrade.js').MegaWsConnection>} */
|
|
473
|
+
get _sessionConns(): Map<string, import("./ws-upgrade.js").MegaWsConnection>;
|
|
474
|
+
/** @returns {Map<string, Set<import('./ws-upgrade.js').MegaWsConnection>>} */
|
|
475
|
+
get _userConns(): Map<string, Set<import("./ws-upgrade.js").MegaWsConnection>>;
|
|
476
|
+
/** @returns {Map<string, Set<import('./ws-upgrade.js').MegaWsConnection>>} */
|
|
477
|
+
get _wsConns(): Map<string, Set<import("./ws-upgrade.js").MegaWsConnection>>;
|
|
478
|
+
/**
|
|
479
|
+
* HTTP `'upgrade'` 이벤트 처리 — 경로 매칭 → 인증(before) → 핸드셰이크 → 채널 라이프사이클.
|
|
480
|
+
*
|
|
481
|
+
* single 모드는 {@link MegaApp#listen} 이, scaffold 모드는 MegaServer 가 호스트 분기 후 호출한다.
|
|
482
|
+
* 매칭 WS 라우트가 없으면 404 로 거부 (HTTP 핸드셰이크 단계라 WS close 아님).
|
|
483
|
+
*
|
|
484
|
+
* @param {import('node:http').IncomingMessage} req
|
|
485
|
+
* @param {import('node:stream').Duplex} socket
|
|
486
|
+
* @param {Buffer} head
|
|
487
|
+
* @returns {void}
|
|
488
|
+
*/
|
|
489
|
+
handleUpgrade(req: import("node:http").IncomingMessage, socket: import("node:stream").Duplex, head: Buffer): void;
|
|
490
|
+
/**
|
|
491
|
+
* 연결별 프레임 코덱 선택 (ADR-083). ASP 설정 시 namespace 활성 여부로 E:/P: 기본 결정.
|
|
492
|
+
* @param {{ path: string }} route
|
|
493
|
+
* @param {import('node:http').IncomingMessage} req
|
|
494
|
+
* @returns {import('./ws-upgrade.js').WsFrameCodec}
|
|
495
|
+
* @private
|
|
496
|
+
*/
|
|
497
|
+
private _buildWsCodec;
|
|
498
|
+
/**
|
|
499
|
+
* noServer 모드 WebSocketServer lazy 생성 (WS 라우트가 있을 때만).
|
|
500
|
+
* @returns {WebSocketServer}
|
|
501
|
+
* @private
|
|
502
|
+
*/
|
|
503
|
+
private _ensureWss;
|
|
504
|
+
/**
|
|
505
|
+
* 등록된 WS 라우트가 있으면 httpServer 의 'upgrade' 이벤트를 자기 handleUpgrade 에 배선.
|
|
506
|
+
* single 모드 listen 에서 호출. WS 라우트가 없으면 no-op (불필요한 리스너 회피).
|
|
507
|
+
* @param {import('node:http').Server} httpServer
|
|
508
|
+
* @returns {void}
|
|
509
|
+
*/
|
|
510
|
+
_attachWsUpgrade(httpServer: import("node:http").Server): void;
|
|
511
|
+
/**
|
|
512
|
+
* Single 모드 전용 listen — MegaServer 거치지 않고 직접 listen.
|
|
513
|
+
* Scaffold 모드는 MegaServer.mount() + MegaServer.listen() 사용.
|
|
514
|
+
* @param {{ port?: number, host?: string }} [opts]
|
|
515
|
+
*/
|
|
516
|
+
listen(opts?: {
|
|
517
|
+
port?: number;
|
|
518
|
+
host?: string;
|
|
519
|
+
}): Promise<string>;
|
|
520
|
+
/** 인스턴스 close — hub link · WS 연결 정리 후 진행 중 요청 grace 후 종료. */
|
|
521
|
+
close(): Promise<void>;
|
|
522
|
+
/** 이 앱의 세션 스토어 어댑터 (세션 미활성 시 null). 테스트·헬스용. */
|
|
523
|
+
get sessionStore(): import("../adapters/mega-session-adapter.js").MegaSessionAdapter;
|
|
524
|
+
}
|
|
525
|
+
import { MegaBruteForce } from '../lib/mega-brute-force.js';
|
|
526
|
+
import { Router } from './router.js';
|
|
527
|
+
import { WebSocketServer } from 'ws';
|
|
528
|
+
import Fastify from 'fastify';
|
|
529
|
+
import { MegaWsPresence } from './ws-presence.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 코어가 워커에 부착하는 비표준 프로퍼티(`_megaBirthAt`, 수명 측정용)를 포함한 Worker.
|
|
3
|
+
* @typedef {import('node:cluster').Worker & { _megaBirthAt?: number }} MegaWorker
|
|
4
|
+
*/
|
|
5
|
+
export class MegaCluster {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} [opts]
|
|
8
|
+
* @param {number | 'max'} [opts.instances] - 워커 수. 'max' 면 CPU 코어 수.
|
|
9
|
+
* @param {boolean} [opts.respawn=true] - 워커 crash 시 자동 재시작
|
|
10
|
+
* @param {number} [opts.gracePeriodMs=30000] - SIGTERM 후 강제 kill 까지 대기. ⚠️ 워커가
|
|
11
|
+
* `MegaShutdown.setupSignals()` 를 쓰면 워커 종료 예산 상한은 hardKill(기본 60s)이다 — 마스터 grace 가
|
|
12
|
+
* 그보다 작으면 워커 drain 도중 SIGKILL 되는 예산 역전이 생긴다. `mega start` CLI 는
|
|
13
|
+
* `DEFAULT_HARD_KILL_MS + 5s` 로 위계화해 전달한다(직접 생성 시에도 동일 권장).
|
|
14
|
+
* @param {import('node:cluster').Cluster} [opts._cluster] - 테스트용 cluster 주입(기본 node:cluster). per ADR-165
|
|
15
|
+
* @param {NodeJS.Process} [opts._proc] - 테스트용 process 주입(기본 전역 process). per ADR-165
|
|
16
|
+
* @param {{ info?: Function, warn?: Function, error?: Function } | null} [opts.logger] - 조율 로그용 pino 로거.
|
|
17
|
+
* 미설정이면 console 폴백(마스터는 bootApp 을 안 해 pino 인스턴스가 없을 수 있다, ADR-180).
|
|
18
|
+
*/
|
|
19
|
+
constructor(opts?: {
|
|
20
|
+
instances?: number | "max";
|
|
21
|
+
respawn?: boolean;
|
|
22
|
+
gracePeriodMs?: number;
|
|
23
|
+
_cluster?: import("node:cluster").Cluster;
|
|
24
|
+
_proc?: NodeJS.Process;
|
|
25
|
+
logger?: {
|
|
26
|
+
info?: Function;
|
|
27
|
+
warn?: Function;
|
|
28
|
+
error?: Function;
|
|
29
|
+
} | null;
|
|
30
|
+
});
|
|
31
|
+
_instances: number;
|
|
32
|
+
_respawn: boolean;
|
|
33
|
+
_gracePeriodMs: number;
|
|
34
|
+
_shuttingDown: boolean;
|
|
35
|
+
_cluster: cluster.Cluster;
|
|
36
|
+
_proc: NodeJS.Process;
|
|
37
|
+
/** @type {{ info?: Function, warn?: Function, error?: Function } | null} 조율 로그용 로거(없으면 console). */
|
|
38
|
+
_logger: {
|
|
39
|
+
info?: Function;
|
|
40
|
+
warn?: Function;
|
|
41
|
+
error?: Function;
|
|
42
|
+
} | null;
|
|
43
|
+
/** @type {Set<MegaWorker>} */
|
|
44
|
+
_workers: Set<MegaWorker>;
|
|
45
|
+
/** @type {(() => Promise<void>) | null} */
|
|
46
|
+
_workerFn: (() => Promise<void>) | null;
|
|
47
|
+
_maxRapidRespawn: number;
|
|
48
|
+
_minRespawnIntervalMs: number;
|
|
49
|
+
/** @type {number[]} 윈도우 내 빠른-crash 타임스탬프(ms). */
|
|
50
|
+
_rapidCrashTimes: number[];
|
|
51
|
+
/** @type {ReturnType<typeof setTimeout>|null} SIGTERM grace 강제-kill 타이머(전원 정상종료 시 취소). */
|
|
52
|
+
_graceTimer: ReturnType<typeof setTimeout> | null;
|
|
53
|
+
/**
|
|
54
|
+
* 마스터 / 워커 둘 다 호출. 마스터면 fork, 워커면 사용자 함수 실행.
|
|
55
|
+
* @param {() => Promise<void>} workerFn - 워커 프로세스에서 실행할 부팅 함수.
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
start(workerFn: () => Promise<void>): Promise<void>;
|
|
59
|
+
/** boolean 네이밍 (ADR-036). */
|
|
60
|
+
isShuttingDown(): boolean;
|
|
61
|
+
/** boolean — primary 프로세스인지. */
|
|
62
|
+
isPrimary(): boolean;
|
|
63
|
+
/** boolean — worker 프로세스인지. */
|
|
64
|
+
isWorker(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* 조율 로그용 로거 주입(생성 후, start 전에 호출). 마스터는 bootApp 을 안 해 pino 가 없으므로 CLI 가
|
|
67
|
+
* config 로 만든 인스턴스를 넘긴다(ADR-180). 미주입이면 console 폴백.
|
|
68
|
+
* @param {{ info?: Function, warn?: Function, error?: Function } | null | undefined} logger
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
setLogger(logger: {
|
|
72
|
+
info?: Function;
|
|
73
|
+
warn?: Function;
|
|
74
|
+
error?: Function;
|
|
75
|
+
} | null | undefined): void;
|
|
76
|
+
/**
|
|
77
|
+
* @private 조율 로그 — 주입 로거가 있으면 그 레벨로, 없으면 console 폴백(`[mega-cluster]` 접두).
|
|
78
|
+
* @param {'info'|'warn'|'error'} level @param {string} msg
|
|
79
|
+
*/
|
|
80
|
+
private _log;
|
|
81
|
+
/**
|
|
82
|
+
* @private 마스터 프로세스 부팅 시퀀스.
|
|
83
|
+
* @param {() => Promise<void>} workerFn
|
|
84
|
+
*/
|
|
85
|
+
private _startPrimary;
|
|
86
|
+
/** @private 워커 fork + 라이프사이클 wiring. */
|
|
87
|
+
private _forkWorker;
|
|
88
|
+
/** @private 모든 워커에 shutdown 메시지 송신. */
|
|
89
|
+
private _broadcastShutdown;
|
|
90
|
+
/**
|
|
91
|
+
* @private 워커 프로세스 시작 — 사용자 함수 실행 + 마스터 메시지 수신.
|
|
92
|
+
* @param {() => Promise<void>} workerFn
|
|
93
|
+
*/
|
|
94
|
+
private _startWorker;
|
|
95
|
+
/** 외부에서 강제 종료 트리거 (테스트용). */
|
|
96
|
+
shutdown(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 코어가 워커에 부착하는 비표준 프로퍼티(`_megaBirthAt`, 수명 측정용)를 포함한 Worker.
|
|
100
|
+
*/
|
|
101
|
+
export type MegaWorker = import("node:cluster").Worker & {
|
|
102
|
+
_megaBirthAt?: number;
|
|
103
|
+
};
|
|
104
|
+
import cluster from 'node:cluster';
|