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
package/src/cli/watch.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* `mega start --watch` 의 dev watch supervisor (ADR-220, ADR-182 개정).
|
|
4
|
+
*
|
|
5
|
+
* Node 내장 `--watch-path` 재실행(구 ADR-182)은 **ignore 패턴이 없어** 런타임이 쓰는 파일까지
|
|
6
|
+
* 재시작을 유발했다 — dev 의 i18n saveMissing 이 `locales/*.json` 자동 기입(재시작 무한 루프),
|
|
7
|
+
* 업로드가 `uploads/`/`var/` 에 파일 저장(사용자 입력이 서버를 재시작), 마이그레이션 generate 가
|
|
8
|
+
* `.mega/` journal 갱신. 본 모듈이 그 재실행을 **자체 supervisor**(fs.watch recursive + 미니 glob
|
|
9
|
+
* ignore + debounce + SIGTERM respawn)로 대체한다 — 신규 dep 0(chokidar 미도입: 필요한 매칭이
|
|
10
|
+
* `**`/`*`/`?` 수준이라 풀스펙 glob 라이브러리가 과함).
|
|
11
|
+
*
|
|
12
|
+
* ignore 합성 = config `watch.ignore` + CLI `--watch-ignore`. **숨은 코드 디폴트는 없다**(사용자
|
|
13
|
+
* 결정 — 개발자가 보지 못하는 목록은 폴더명이 다른 프로젝트에서 오히려 혼란) — 권장 목록은 스캐폴드
|
|
14
|
+
* `.env` 의 `WATCH_IGNORE` 가 실값으로 명시하고 `mega.config.js` 가 읽어 `watch.ignore` 로 넘긴다.
|
|
15
|
+
* ignore 0 이면 supervisor 가 기동 시 경고 1줄을 낸다(locales saveMissing 루프 등 위험 가시화).
|
|
16
|
+
*
|
|
17
|
+
* @module cli/watch
|
|
18
|
+
*/
|
|
19
|
+
import { watch as fsWatch, existsSync, statSync } from 'node:fs'
|
|
20
|
+
import { join, relative, sep } from 'node:path'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 미니 glob → RegExp. 지원: 더블스타+슬래시(0개 이상의 디렉토리), 더블스타(경로 전체), `*`(세그먼트
|
|
24
|
+
* 내), `?`(1글자). 그 외 문자는 리터럴(정규식 특수문자 escape). 매칭 대상은 루트 상대 POSIX 경로
|
|
25
|
+
* 전체(^…$). 패턴 예시는 스캐폴드 `.env` 의 `WATCH_IGNORE` 참조 — JS 블록 주석 안에는
|
|
26
|
+
* 더블스타+슬래시 시퀀스를 쓸 수 없어(주석 조기 종료) 여기 직접 못 적는다.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} pattern - glob 패턴(예 `.env.*`).
|
|
29
|
+
* @returns {RegExp}
|
|
30
|
+
*/
|
|
31
|
+
export function globToRegExp(pattern) {
|
|
32
|
+
let re = ''
|
|
33
|
+
let i = 0
|
|
34
|
+
while (i < pattern.length) {
|
|
35
|
+
const ch = pattern[i]
|
|
36
|
+
if (ch === '*') {
|
|
37
|
+
if (pattern[i + 1] === '*') {
|
|
38
|
+
// `**/` 는 0개 이상의 디렉토리(루트 레벨 포함), 그 외 `**` 는 임의 문자열.
|
|
39
|
+
if (pattern[i + 2] === '/') {
|
|
40
|
+
re += '(?:.*/)?'
|
|
41
|
+
i += 3
|
|
42
|
+
} else {
|
|
43
|
+
re += '.*'
|
|
44
|
+
i += 2
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
re += '[^/]*'
|
|
48
|
+
i += 1
|
|
49
|
+
}
|
|
50
|
+
} else if (ch === '?') {
|
|
51
|
+
re += '[^/]'
|
|
52
|
+
i += 1
|
|
53
|
+
} else {
|
|
54
|
+
re += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
55
|
+
i += 1
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return new RegExp(`^${re}$`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ignore 패턴 합성 — config(`watch.ignore`) + CLI(`--watch-ignore a,b`). 숨은 디폴트 없음(모듈
|
|
63
|
+
* docstring 참조). 잘못된 모양은 fail-fast(silent 무시 금지).
|
|
64
|
+
*
|
|
65
|
+
* @param {{ ignore?: unknown }} [watchConfig] - mega.config.js 의 `watch`.
|
|
66
|
+
* @param {string | undefined} [cliIgnore] - `--watch-ignore` 값(콤마 구분).
|
|
67
|
+
* @returns {string[]}
|
|
68
|
+
* @throws {TypeError} config `watch.ignore` 가 문자열 배열이 아닐 때.
|
|
69
|
+
*/
|
|
70
|
+
export function mergeWatchIgnore(watchConfig = {}, cliIgnore = undefined) {
|
|
71
|
+
/** @type {string[]} */
|
|
72
|
+
const merged = []
|
|
73
|
+
if (watchConfig.ignore !== undefined) {
|
|
74
|
+
if (!Array.isArray(watchConfig.ignore) || watchConfig.ignore.some((p) => typeof p !== 'string' || p.length === 0)) {
|
|
75
|
+
throw new TypeError('config watch.ignore 는 비어있지 않은 문자열 배열이어야 합니다.')
|
|
76
|
+
}
|
|
77
|
+
merged.push(...watchConfig.ignore)
|
|
78
|
+
}
|
|
79
|
+
if (typeof cliIgnore === 'string' && cliIgnore.length > 0) {
|
|
80
|
+
merged.push(...cliIgnore.split(',').map((p) => p.trim()).filter((p) => p.length > 0))
|
|
81
|
+
}
|
|
82
|
+
return merged
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 루트 상대 경로가 ignore 에 걸리는지 (Boolean — `is*`).
|
|
87
|
+
* @param {string} rel - 루트 상대 POSIX 경로(예 `apps/main/locales/ko.json`).
|
|
88
|
+
* @param {RegExp[]} regexps - {@link globToRegExp} 컴파일 결과.
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
export function isIgnoredPath(rel, regexps) {
|
|
92
|
+
return regexps.some((r) => r.test(rel))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* watch supervisor — 감시 경로의 변경(ignore 통과분)을 debounce 후 자식(`mega start`)을
|
|
97
|
+
* SIGTERM → respawn 한다. 자식이 스스로 죽으면(crash) 재시작하지 않고 다음 변경을 기다린다
|
|
98
|
+
* (crash-loop 방지 — nodemon 의 "app crashed - waiting for file changes" 와 동형).
|
|
99
|
+
*
|
|
100
|
+
* @param {object} o
|
|
101
|
+
* @param {string} o.projectRoot
|
|
102
|
+
* @param {string[]} o.watchPaths - 감시할 절대경로(존재하는 것만 — 호출측이 필터).
|
|
103
|
+
* @param {string[]} o.ignore - glob 패턴 목록.
|
|
104
|
+
* @param {() => import('node:child_process').ChildProcess} o.spawnChild - 자식 기동 팩토리(주입 — 테스트).
|
|
105
|
+
* @param {(msg: string) => void} o.out
|
|
106
|
+
* @param {number} [o.debounceMs=500] - 연속 변경 디바운스.
|
|
107
|
+
* @param {(dir: string, opts: object, cb: (event: string, filename: string | Buffer | null) => void) => { close(): void }} [o.watchFn] -
|
|
108
|
+
* fs.watch 주입(테스트). 기본 node:fs watch.
|
|
109
|
+
* @returns {Promise<number>} 부모(supervisor)의 exit code — SIGINT/SIGTERM 수신 시 자식 정리 후 0.
|
|
110
|
+
*/
|
|
111
|
+
export function startWatchSupervisor({ projectRoot, watchPaths, ignore, spawnChild, out, debounceMs = 500, watchFn = /** @type {any} */ (fsWatch) }) {
|
|
112
|
+
const regexps = ignore.map(globToRegExp)
|
|
113
|
+
/** @type {import('node:child_process').ChildProcess | null} */
|
|
114
|
+
let child = null
|
|
115
|
+
/** @type {NodeJS.Timeout | null} */
|
|
116
|
+
let pending = null
|
|
117
|
+
let shuttingDown = false
|
|
118
|
+
let restarting = false
|
|
119
|
+
|
|
120
|
+
const start = () => {
|
|
121
|
+
child = spawnChild()
|
|
122
|
+
child.on('exit', (code, signal) => {
|
|
123
|
+
if (shuttingDown) return
|
|
124
|
+
if (restarting) {
|
|
125
|
+
restarting = false
|
|
126
|
+
start()
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
// 자식이 스스로 종료(crash/정상) — 재시작 폭주 대신 다음 변경 대기(P4: 이유 출력).
|
|
130
|
+
out(`mega: app exited (code=${code ?? `signal:${signal}`}) — waiting for file changes before restart`)
|
|
131
|
+
child = null
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const scheduleRestart = (/** @type {string} */ rel) => {
|
|
136
|
+
if (pending) clearTimeout(pending)
|
|
137
|
+
pending = setTimeout(() => {
|
|
138
|
+
pending = null
|
|
139
|
+
out(`mega: restarting due to changes (${rel})`)
|
|
140
|
+
if (child && child.exitCode === null && !child.killed) {
|
|
141
|
+
restarting = true
|
|
142
|
+
child.kill('SIGTERM') // graceful — exit 핸들러가 respawn.
|
|
143
|
+
} else {
|
|
144
|
+
start() // crash 후 대기 상태 — 변경으로 재기동.
|
|
145
|
+
}
|
|
146
|
+
}, debounceMs)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @type {Array<{ close(): void }>} */
|
|
150
|
+
const watchers = []
|
|
151
|
+
for (const abs of watchPaths) {
|
|
152
|
+
const isDir = statSync(abs).isDirectory()
|
|
153
|
+
const watcher = watchFn(abs, { recursive: isDir }, (_event, filename) => {
|
|
154
|
+
// filename 은 감시 루트 상대(파일 watch 면 basename) — 루트 상대로 정규화해 ignore 매칭.
|
|
155
|
+
const name = typeof filename === 'string' ? filename : (filename ? String(filename) : '')
|
|
156
|
+
const rel = relative(projectRoot, isDir ? join(abs, name) : abs).split(sep).join('/')
|
|
157
|
+
if (isIgnoredPath(rel, regexps)) return
|
|
158
|
+
scheduleRestart(rel)
|
|
159
|
+
})
|
|
160
|
+
watchers.push(watcher)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
start()
|
|
164
|
+
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
/** @type {NodeJS.Signals[]} */
|
|
167
|
+
const sigs = ['SIGINT', 'SIGTERM']
|
|
168
|
+
const onSignal = () => {
|
|
169
|
+
shuttingDown = true
|
|
170
|
+
if (pending) clearTimeout(pending)
|
|
171
|
+
for (const w of watchers) w.close()
|
|
172
|
+
for (const s of sigs) process.removeListener(s, onSignal)
|
|
173
|
+
if (child && child.exitCode === null && !child.killed) {
|
|
174
|
+
child.once('exit', () => resolve(0))
|
|
175
|
+
child.kill('SIGTERM')
|
|
176
|
+
} else {
|
|
177
|
+
resolve(0)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const s of sigs) process.on(s, onSignal)
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** {@link startWatchSupervisor} 의 watchPaths 기본 집합 — 존재하는 것만.
|
|
185
|
+
* @param {string} projectRoot @returns {string[]} */
|
|
186
|
+
export function defaultWatchPaths(projectRoot) {
|
|
187
|
+
return [join(projectRoot, 'apps'), join(projectRoot, 'shared'), join(projectRoot, 'mega.config.js')].filter(existsSync)
|
|
188
|
+
}
|
package/src/core/ajv-mapper.js
CHANGED
|
@@ -15,6 +15,16 @@ const SENSITIVE_FIELD_RE = /(password|secret|token)/i
|
|
|
15
15
|
/** PII 마스킹된 value 표기. */
|
|
16
16
|
const REDACTED = '[REDACTED]'
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* envelope `details` 배열 상한 (ADR-193 — allErrors DoS 방어의 응답측 bound).
|
|
20
|
+
*
|
|
21
|
+
* allErrors:true 는 위반을 전수 수집하므로(@fastify/ajv-compiler 디폴트가 allErrors:false 인 이유 =
|
|
22
|
+
* DoS 가능성 명시) 악의적 페이로드가 수천 개 위반을 만들 수 있다. 입력측은 Fastify bodyLimit
|
|
23
|
+
* (기본 1 MiB)이 막고, 응답·로그측은 이 cap 이 막는다 — 초과분은 잘라내되 silent 가 아니라
|
|
24
|
+
* `rule:'truncated'` 합성 항목으로 생략 개수를 명시한다.
|
|
25
|
+
*/
|
|
26
|
+
export const MAX_VALIDATION_DETAILS = 20
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Fastify 가 던진 AJV 검증 에러 객체 → MegaValidationError 로 매핑.
|
|
20
30
|
*
|
|
@@ -30,7 +40,17 @@ const REDACTED = '[REDACTED]'
|
|
|
30
40
|
*/
|
|
31
41
|
export function ajvErrorToValidationError(err) {
|
|
32
42
|
if (!err || !Array.isArray(err.validation)) return null
|
|
33
|
-
const
|
|
43
|
+
const omitted = Math.max(0, err.validation.length - MAX_VALIDATION_DETAILS)
|
|
44
|
+
const details = err.validation.slice(0, MAX_VALIDATION_DETAILS).map((e) => ajvItemToDetail(e))
|
|
45
|
+
if (omitted > 0) {
|
|
46
|
+
// cap 초과는 silent 절단 금지 — 생략 개수를 합성 항목으로 명시(클라이언트·로그 양쪽에서 보임).
|
|
47
|
+
details.push({
|
|
48
|
+
field: '',
|
|
49
|
+
rule: 'truncated',
|
|
50
|
+
value: { omitted },
|
|
51
|
+
message: `... and ${omitted} more validation errors (details capped at ${MAX_VALIDATION_DETAILS})`,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
34
54
|
const context = err.validationContext ?? 'request'
|
|
35
55
|
return new MegaValidationError('validation.failed', `Validation failed for ${context}`, {
|
|
36
56
|
details,
|
|
@@ -45,11 +65,18 @@ export function ajvErrorToValidationError(err) {
|
|
|
45
65
|
*/
|
|
46
66
|
function ajvItemToDetail(e) {
|
|
47
67
|
// instancePath 예: '/email' → field = 'email'. '/items/0/name' → 'items[0].name'
|
|
48
|
-
|
|
68
|
+
let field = ajvPathToField(e.instancePath || e.dataPath || '')
|
|
49
69
|
// ADR-090: required 같은 키워드는 instancePath 가 부모라 params.missingProperty 가 실제 필드.
|
|
50
70
|
// 마스킹 판단은 instancePath 필드 + missingProperty 둘 다 본다.
|
|
51
71
|
const missing = e.params && typeof e.params.missingProperty === 'string' ? e.params.missingProperty : ''
|
|
52
|
-
|
|
72
|
+
// required 위반의 field 는 누락 필드 경로로 보정 — 루트 required 면 field 가 '' 라 클라이언트가
|
|
73
|
+
// 어느 필드인지 알 수 없던 것을 missingProperty 로 채운다(ADR-188). 부모 경로가 있으면 dot 연결.
|
|
74
|
+
if (e.keyword === 'required' && missing) {
|
|
75
|
+
field = field ? `${field}.${missing}` : missing
|
|
76
|
+
}
|
|
77
|
+
// 정규식 1회 실행 (G1 M-2, ADR-214) — field·missingProperty 를 합쳐 한 번만 검사한다. allErrors
|
|
78
|
+
// (ADR-193) 이후 400 1건당 detail × 2회 실행되던 비용 절반화. 매칭 의미는 동일(부분 일치 OR).
|
|
79
|
+
const isSensitive = SENSITIVE_FIELD_RE.test(missing ? `${field} ${missing}` : field)
|
|
53
80
|
return {
|
|
54
81
|
field,
|
|
55
82
|
rule: e.keyword || 'unknown',
|