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.
Files changed (248) hide show
  1. package/README.md +9 -0
  2. package/bin/mega-ws-hub.js +2 -2
  3. package/package.json +33 -9
  4. package/sample/crud/.env +10 -1
  5. package/sample/crud/.env.example +10 -1
  6. package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
  7. package/sample/crud/.mega/journal/snapshot.json +261 -0
  8. package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
  9. package/sample/crud/apps/main/controllers/web-controller.js +7 -5
  10. package/sample/crud/apps/main/locales/server/en.json +12 -1
  11. package/sample/crud/apps/main/locales/server/ko.json +12 -1
  12. package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
  13. package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
  14. package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
  15. package/sample/crud/apps/main/models/log-partition-model.js +105 -0
  16. package/sample/crud/apps/main/models/note-model.js +79 -0
  17. package/sample/crud/apps/main/models/user-level-model.js +24 -0
  18. package/sample/crud/apps/main/models/user-model.js +146 -0
  19. package/sample/crud/apps/main/models/user-type-model.js +21 -0
  20. package/sample/crud/apps/main/models/wallet-model.js +24 -0
  21. package/sample/crud/apps/main/routes/users.js +55 -10
  22. package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
  23. package/sample/crud/apps/main/services/auth-service.js +39 -24
  24. package/sample/crud/apps/main/services/log-partition-service.js +101 -0
  25. package/sample/crud/apps/main/services/note-service.js +6 -6
  26. package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
  27. package/sample/crud/apps/main/services/user-service.js +62 -21
  28. package/sample/crud/apps/main/views/auth/login.ejs +6 -6
  29. package/sample/crud/apps/main/views/auth/register.ejs +46 -5
  30. package/sample/crud/apps/main/views/users/edit.ejs +42 -5
  31. package/sample/crud/apps/main/views/users/list.ejs +6 -2
  32. package/sample/crud/apps/main/views/users/new.ejs +56 -4
  33. package/sample/crud/docs/log_partition_design.mm.md +23 -0
  34. package/sample/crud/mega.config.js +10 -2
  35. package/sample/crud/package.json +3 -3
  36. package/sample/crud/scripts/start-ws-hub.sh +20 -6
  37. package/sample/simple/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  38. package/sample/simple/package.json +2 -2
  39. package/src/adapters/adapter-manager.js +2 -1
  40. package/src/adapters/adapter-options.js +44 -3
  41. package/src/adapters/file-adapter.js +9 -5
  42. package/src/adapters/file-session-adapter.js +4 -3
  43. package/src/adapters/maria-adapter.js +33 -7
  44. package/src/adapters/mega-cache-adapter.js +83 -6
  45. package/src/adapters/mega-db-adapter.js +10 -1
  46. package/src/adapters/mongo-adapter.js +40 -8
  47. package/src/adapters/postgres-adapter.js +33 -6
  48. package/src/adapters/redis-adapter.js +7 -3
  49. package/src/adapters/sqlite-adapter.js +26 -3
  50. package/src/cli/commands/console-cmd.js +3 -1
  51. package/src/cli/commands/new.js +13 -3
  52. package/src/cli/commands/scaffold.js +173 -33
  53. package/src/cli/generators/index.js +140 -3
  54. package/src/cli/index.js +437 -155
  55. package/src/cli/watch.js +188 -0
  56. package/src/core/ajv-mapper.js +30 -3
  57. package/src/core/boot.js +464 -245
  58. package/src/core/cluster-metrics.js +13 -4
  59. package/src/core/ctx-builder.js +65 -3
  60. package/src/core/envelope.js +119 -12
  61. package/src/core/hub-link.js +89 -18
  62. package/src/core/i18n.js +11 -1
  63. package/src/core/index.js +7 -3
  64. package/src/core/mega-app.js +253 -505
  65. package/src/core/mega-cluster.js +4 -1
  66. package/src/core/mega-server.js +40 -9
  67. package/src/core/migration/dialect-registry.js +107 -0
  68. package/src/core/migration/dialects/README.md +62 -0
  69. package/src/core/migration/dialects/maria.js +496 -0
  70. package/src/core/migration/dialects/mongo.js +824 -0
  71. package/src/core/migration/dialects/postgres.js +563 -0
  72. package/src/core/migration/dialects/sqlite.js +476 -0
  73. package/src/core/migration/differ.js +456 -0
  74. package/src/core/migration/generate.js +508 -0
  75. package/src/core/migration/journal.js +167 -0
  76. package/src/core/migration/model-scan.js +84 -0
  77. package/src/core/migration/mongo-migration-db.js +97 -0
  78. package/src/core/migration/schema-builder.js +400 -0
  79. package/src/core/migration/schema-validator.js +315 -0
  80. package/src/core/migration-lock.js +205 -0
  81. package/src/core/migration-runner.js +166 -38
  82. package/src/core/multipart.js +28 -5
  83. package/src/core/pipeline.js +131 -0
  84. package/src/core/router.js +70 -65
  85. package/src/core/scope-registry.js +1 -0
  86. package/src/core/security.js +70 -12
  87. package/src/core/session-store.js +14 -1
  88. package/src/core/workers-manager.js +12 -1
  89. package/src/core/ws-cluster.js +10 -3
  90. package/src/core/ws-message.js +48 -4
  91. package/src/core/ws-presence.js +636 -0
  92. package/src/core/ws-roster.js +50 -8
  93. package/src/core/ws-upgrade.js +223 -12
  94. package/src/index.js +1 -1
  95. package/src/lib/hub-protocol.js +29 -0
  96. package/src/lib/mega-circuit-breaker.js +5 -3
  97. package/src/lib/mega-health.js +35 -4
  98. package/src/lib/mega-job-queue.js +151 -34
  99. package/src/lib/mega-job.js +37 -1
  100. package/src/lib/mega-metrics.js +31 -13
  101. package/src/lib/mega-plugin.js +34 -3
  102. package/src/lib/mega-schedule.js +40 -22
  103. package/src/lib/mega-shutdown.js +114 -39
  104. package/src/lib/mega-tracing.js +66 -19
  105. package/src/lib/mega-worker.js +33 -6
  106. package/src/lib/otel-resource.js +36 -0
  107. package/src/{cli → lib}/ws-hub.js +139 -15
  108. package/src/models/crud-sql-builder.js +133 -0
  109. package/src/models/mega-model.js +82 -2
  110. package/src/models/model-crud.js +483 -0
  111. package/src/models/mongo-crud.js +285 -0
  112. package/templates/adr/code.tpl +23 -0
  113. package/templates/model/code-mongo.tpl +35 -0
  114. package/templates/model/code.tpl +15 -1
  115. package/templates/model/test-mongo.tpl +38 -0
  116. package/templates/model/test.tpl +4 -0
  117. package/types/adapters/adapter-manager.d.ts +95 -0
  118. package/types/adapters/adapter-options.d.ts +93 -0
  119. package/types/adapters/file-adapter.d.ts +105 -0
  120. package/types/adapters/file-session-adapter.d.ts +103 -0
  121. package/types/adapters/index.d.ts +20 -0
  122. package/types/adapters/maria-adapter.d.ts +117 -0
  123. package/types/adapters/mega-adapter.d.ts +215 -0
  124. package/types/adapters/mega-bus-adapter.d.ts +45 -0
  125. package/types/adapters/mega-cache-adapter.d.ts +73 -0
  126. package/types/adapters/mega-db-adapter.d.ts +50 -0
  127. package/types/adapters/mega-lock-adapter.d.ts +62 -0
  128. package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
  129. package/types/adapters/mega-session-adapter.d.ts +32 -0
  130. package/types/adapters/mongo-adapter.d.ts +150 -0
  131. package/types/adapters/nats-adapter.d.ts +108 -0
  132. package/types/adapters/postgres-adapter.d.ts +141 -0
  133. package/types/adapters/redis-adapter.d.ts +78 -0
  134. package/types/adapters/redis-session-adapter.d.ts +82 -0
  135. package/types/adapters/redlock-adapter.d.ts +149 -0
  136. package/types/adapters/registry.d.ts +46 -0
  137. package/types/adapters/sqlite-adapter.d.ts +112 -0
  138. package/types/auth/index.d.ts +24 -0
  139. package/types/cli/commands/console-cmd.d.ts +37 -0
  140. package/types/cli/commands/new.d.ts +16 -0
  141. package/types/cli/commands/routes.d.ts +36 -0
  142. package/types/cli/commands/scaffold.d.ts +78 -0
  143. package/types/cli/commands/test-cmd.d.ts +14 -0
  144. package/types/cli/generators/index.d.ts +122 -0
  145. package/types/cli/index.d.ts +234 -0
  146. package/types/cli/template-engine.d.ts +40 -0
  147. package/types/cli/watch.d.ts +59 -0
  148. package/types/core/ajv-mapper.d.ts +27 -0
  149. package/types/core/boot.d.ts +233 -0
  150. package/types/core/cluster-metrics.d.ts +52 -0
  151. package/types/core/config-loader.d.ts +13 -0
  152. package/types/core/config-validator.d.ts +30 -0
  153. package/types/core/ctx-builder.d.ts +103 -0
  154. package/types/core/envelope.d.ts +79 -0
  155. package/types/core/error-mapper.d.ts +17 -0
  156. package/types/core/formbody.d.ts +41 -0
  157. package/types/core/hub-link.d.ts +266 -0
  158. package/types/core/i18n.d.ts +178 -0
  159. package/types/core/index.d.ts +28 -0
  160. package/types/core/mega-app.d.ts +529 -0
  161. package/types/core/mega-cluster.d.ts +104 -0
  162. package/types/core/mega-server.d.ts +91 -0
  163. package/types/core/mega-service.d.ts +31 -0
  164. package/types/core/migration/dialect-registry.d.ts +22 -0
  165. package/types/core/migration/dialects/maria.d.ts +99 -0
  166. package/types/core/migration/dialects/mongo.d.ts +89 -0
  167. package/types/core/migration/dialects/postgres.d.ts +117 -0
  168. package/types/core/migration/dialects/sqlite.d.ts +111 -0
  169. package/types/core/migration/differ.d.ts +47 -0
  170. package/types/core/migration/generate.d.ts +56 -0
  171. package/types/core/migration/journal.d.ts +52 -0
  172. package/types/core/migration/model-scan.d.ts +19 -0
  173. package/types/core/migration/mongo-migration-db.d.ts +7 -0
  174. package/types/core/migration/schema-builder.d.ts +197 -0
  175. package/types/core/migration/schema-validator.d.ts +20 -0
  176. package/types/core/migration-lock.d.ts +33 -0
  177. package/types/core/migration-runner.d.ts +101 -0
  178. package/types/core/multipart.d.ts +86 -0
  179. package/types/core/openapi.d.ts +62 -0
  180. package/types/core/pipeline.d.ts +93 -0
  181. package/types/core/router.d.ts +159 -0
  182. package/types/core/routes-loader.d.ts +21 -0
  183. package/types/core/scope-registry.d.ts +14 -0
  184. package/types/core/security.d.ts +77 -0
  185. package/types/core/services-loader.d.ts +27 -0
  186. package/types/core/session-cleanup-schedule.d.ts +19 -0
  187. package/types/core/session-store.d.ts +25 -0
  188. package/types/core/session.d.ts +77 -0
  189. package/types/core/static-assets.d.ts +73 -0
  190. package/types/core/template.d.ts +106 -0
  191. package/types/core/workers-manager.d.ts +79 -0
  192. package/types/core/ws-cluster.d.ts +208 -0
  193. package/types/core/ws-compression.d.ts +112 -0
  194. package/types/core/ws-controller.d.ts +65 -0
  195. package/types/core/ws-message.d.ts +106 -0
  196. package/types/core/ws-presence.d.ts +273 -0
  197. package/types/core/ws-roster.d.ts +108 -0
  198. package/types/core/ws-upgrade.d.ts +260 -0
  199. package/types/errors/config-error.d.ts +10 -0
  200. package/types/errors/http-errors.d.ts +120 -0
  201. package/types/errors/index.d.ts +3 -0
  202. package/types/errors/mega-error.d.ts +32 -0
  203. package/types/index.d.ts +39 -0
  204. package/types/lib/asp/config.d.ts +49 -0
  205. package/types/lib/asp/crypto.d.ts +43 -0
  206. package/types/lib/asp/errors.d.ts +30 -0
  207. package/types/lib/asp/nonce-cache.d.ts +52 -0
  208. package/types/lib/asp/plugin.d.ts +30 -0
  209. package/types/lib/asp/ws-terminator.d.ts +45 -0
  210. package/types/lib/env-mapper.d.ts +14 -0
  211. package/types/lib/hub-protocol.d.ts +106 -0
  212. package/types/lib/index.d.ts +22 -0
  213. package/types/lib/logger/telegram-core.d.ts +104 -0
  214. package/types/lib/logger/telegram-transport.d.ts +45 -0
  215. package/types/lib/mega-brute-force.d.ts +66 -0
  216. package/types/lib/mega-circuit-breaker.d.ts +243 -0
  217. package/types/lib/mega-cron.d.ts +66 -0
  218. package/types/lib/mega-hash.d.ts +32 -0
  219. package/types/lib/mega-health.d.ts +48 -0
  220. package/types/lib/mega-job-queue.d.ts +188 -0
  221. package/types/lib/mega-job-worker.d.ts +130 -0
  222. package/types/lib/mega-job.d.ts +145 -0
  223. package/types/lib/mega-logger.d.ts +45 -0
  224. package/types/lib/mega-metrics.d.ts +285 -0
  225. package/types/lib/mega-plugin.d.ts +245 -0
  226. package/types/lib/mega-retry.d.ts +85 -0
  227. package/types/lib/mega-schedule.d.ts +260 -0
  228. package/types/lib/mega-shutdown.d.ts +135 -0
  229. package/types/lib/mega-tracing.d.ts +224 -0
  230. package/types/lib/mega-worker.d.ts +129 -0
  231. package/types/lib/otel-resource.d.ts +16 -0
  232. package/types/lib/worker-runner/process-entry.d.ts +1 -0
  233. package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
  234. package/types/lib/worker-runner/thread-entry.d.ts +1 -0
  235. package/types/lib/ws-hub.d.ts +259 -0
  236. package/types/models/crud-sql-builder.d.ts +48 -0
  237. package/types/models/index.d.ts +1 -0
  238. package/types/models/mega-model.d.ts +138 -0
  239. package/types/models/model-crud.d.ts +82 -0
  240. package/types/models/mongo-crud.d.ts +59 -0
  241. package/types/test/index.d.ts +84 -0
  242. package/.env +0 -127
  243. package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
  244. package/sample/crud/apps/main/models/note.js +0 -71
  245. package/sample/crud/apps/main/models/user.js +0 -86
  246. package/sample/crud/package-lock.json +0 -5665
  247. package/sample/crud/yarn.lock +0 -2142
  248. package/sample/simple/package-lock.json +0 -1851
@@ -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
+ }
@@ -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 details = err.validation.map((e) => ajvItemToDetail(e))
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
- const field = ajvPathToField(e.instancePath || e.dataPath || '')
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
- const isSensitive = SENSITIVE_FIELD_RE.test(field) || SENSITIVE_FIELD_RE.test(missing)
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',