mega-framework 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.env +127 -0
  2. package/.env.example +186 -0
  3. package/.prettierrc.json +8 -0
  4. package/CHANGELOG.md +259 -0
  5. package/LICENSE +21 -0
  6. package/README.md +153 -0
  7. package/bin/mega-ws-hub.js +15 -0
  8. package/bin/mega.js +38 -0
  9. package/docker-compose.yml +201 -0
  10. package/eslint.config.js +57 -0
  11. package/infra/otel-collector-config.yaml +43 -0
  12. package/jsconfig.json +18 -0
  13. package/package.json +121 -0
  14. package/sample/crud/.env +18 -0
  15. package/sample/crud/.env.example +50 -0
  16. package/sample/crud/README.md +85 -0
  17. package/sample/crud/apps/main/app.config.js +114 -0
  18. package/sample/crud/apps/main/channels/chat-bus.js +115 -0
  19. package/sample/crud/apps/main/channels/chat-channel.js +145 -0
  20. package/sample/crud/apps/main/controllers/auth-controller.js +144 -0
  21. package/sample/crud/apps/main/controllers/cron-controller.js +34 -0
  22. package/sample/crud/apps/main/controllers/guide-controller.js +37 -0
  23. package/sample/crud/apps/main/controllers/jobs-controller.js +43 -0
  24. package/sample/crud/apps/main/controllers/logs-controller.js +35 -0
  25. package/sample/crud/apps/main/controllers/metrics-controller.js +22 -0
  26. package/sample/crud/apps/main/controllers/note-controller.js +116 -0
  27. package/sample/crud/apps/main/controllers/perf-controller.js +38 -0
  28. package/sample/crud/apps/main/controllers/redis-controller.js +36 -0
  29. package/sample/crud/apps/main/controllers/tracing-controller.js +43 -0
  30. package/sample/crud/apps/main/controllers/upload-controller.js +98 -0
  31. package/sample/crud/apps/main/controllers/user-controller.js +34 -0
  32. package/sample/crud/apps/main/controllers/web-controller.js +137 -0
  33. package/sample/crud/apps/main/controllers/worker-controller.js +57 -0
  34. package/sample/crud/apps/main/controllers/ws-controller.js +29 -0
  35. package/sample/crud/apps/main/jobs/email-job.js +72 -0
  36. package/sample/crud/apps/main/locales/client/en.json +3 -0
  37. package/sample/crud/apps/main/locales/client/ko.json +3 -0
  38. package/sample/crud/apps/main/locales/server/en.json +316 -0
  39. package/sample/crud/apps/main/locales/server/ko.json +316 -0
  40. package/sample/crud/apps/main/middleware/web-auth.js +40 -0
  41. package/sample/crud/apps/main/middleware/ws-auth.js +48 -0
  42. package/sample/crud/apps/main/migrations/20260606000001-create-users.js +27 -0
  43. package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +30 -0
  44. package/sample/crud/apps/main/models/note.js +71 -0
  45. package/sample/crud/apps/main/models/user.js +86 -0
  46. package/sample/crud/apps/main/public/css/app.css +101 -0
  47. package/sample/crud/apps/main/public/css/guide.css +137 -0
  48. package/sample/crud/apps/main/public/js/app.js +54 -0
  49. package/sample/crud/apps/main/public/js/perf.js +129 -0
  50. package/sample/crud/apps/main/public/js/theme-init.js +12 -0
  51. package/sample/crud/apps/main/public/js/upload-demo.js +63 -0
  52. package/sample/crud/apps/main/public/js/worker-demo.js +92 -0
  53. package/sample/crud/apps/main/public/js/ws-chat.js +161 -0
  54. package/sample/crud/apps/main/public/vendor/bootstrap/bootstrap.bundle.min.js +7 -0
  55. package/sample/crud/apps/main/public/vendor/bootstrap/bootstrap.min.css +6 -0
  56. package/sample/crud/apps/main/public/vendor/highlight/github-dark.css +109 -0
  57. package/sample/crud/apps/main/public/vendor/highlight/github.css +118 -0
  58. package/sample/crud/apps/main/public/vendor/mega-client-wasm/README.md +19 -0
  59. package/sample/crud/apps/main/public/vendor/mega-client-wasm/mega_client_wasm.d.ts +196 -0
  60. package/sample/crud/apps/main/public/vendor/mega-client-wasm/mega_client_wasm.js +1187 -0
  61. package/sample/crud/apps/main/public/vendor/mega-client-wasm/mega_client_wasm_bg.wasm +0 -0
  62. package/sample/crud/apps/main/routes/auth.js +15 -0
  63. package/sample/crud/apps/main/routes/cron.js +14 -0
  64. package/sample/crud/apps/main/routes/guide.js +25 -0
  65. package/sample/crud/apps/main/routes/jobs.js +14 -0
  66. package/sample/crud/apps/main/routes/logs.js +28 -0
  67. package/sample/crud/apps/main/routes/metrics.js +13 -0
  68. package/sample/crud/apps/main/routes/notes.js +19 -0
  69. package/sample/crud/apps/main/routes/perf.js +47 -0
  70. package/sample/crud/apps/main/routes/redis.js +14 -0
  71. package/sample/crud/apps/main/routes/tracing.js +14 -0
  72. package/sample/crud/apps/main/routes/upload.js +16 -0
  73. package/sample/crud/apps/main/routes/users.js +54 -0
  74. package/sample/crud/apps/main/routes/web.js +23 -0
  75. package/sample/crud/apps/main/routes/worker.js +15 -0
  76. package/sample/crud/apps/main/routes/ws.js +30 -0
  77. package/sample/crud/apps/main/schedules/cron-counter-schedule.js +30 -0
  78. package/sample/crud/apps/main/services/auth-service.js +74 -0
  79. package/sample/crud/apps/main/services/cron-demo-service.js +66 -0
  80. package/sample/crud/apps/main/services/guide-service.js +145 -0
  81. package/sample/crud/apps/main/services/jobs-demo-service.js +83 -0
  82. package/sample/crud/apps/main/services/logs-demo-service.js +59 -0
  83. package/sample/crud/apps/main/services/metrics-demo-service.js +144 -0
  84. package/sample/crud/apps/main/services/note-service.js +75 -0
  85. package/sample/crud/apps/main/services/perf-service.js +302 -0
  86. package/sample/crud/apps/main/services/redis-demo-service.js +75 -0
  87. package/sample/crud/apps/main/services/tracing-demo-service.js +69 -0
  88. package/sample/crud/apps/main/services/upload-demo-service.js +48 -0
  89. package/sample/crud/apps/main/services/user-service.js +65 -0
  90. package/sample/crud/apps/main/views/auth/login.ejs +57 -0
  91. package/sample/crud/apps/main/views/auth/register.ejs +71 -0
  92. package/sample/crud/apps/main/views/cron/index.ejs +92 -0
  93. package/sample/crud/apps/main/views/guide/index.ejs +24 -0
  94. package/sample/crud/apps/main/views/guide/page.ejs +64 -0
  95. package/sample/crud/apps/main/views/home.ejs +82 -0
  96. package/sample/crud/apps/main/views/jobs/index.ejs +113 -0
  97. package/sample/crud/apps/main/views/layouts/main.ejs +112 -0
  98. package/sample/crud/apps/main/views/logs/index.ejs +80 -0
  99. package/sample/crud/apps/main/views/metrics/index.ejs +123 -0
  100. package/sample/crud/apps/main/views/notes/edit.ejs +45 -0
  101. package/sample/crud/apps/main/views/notes/list.ejs +74 -0
  102. package/sample/crud/apps/main/views/notes/new.ejs +45 -0
  103. package/sample/crud/apps/main/views/perf/index.ejs +90 -0
  104. package/sample/crud/apps/main/views/redis/index.ejs +65 -0
  105. package/sample/crud/apps/main/views/tracing/index.ejs +106 -0
  106. package/sample/crud/apps/main/views/upload/index.ejs +79 -0
  107. package/sample/crud/apps/main/views/users/edit.ejs +48 -0
  108. package/sample/crud/apps/main/views/users/list.ejs +81 -0
  109. package/sample/crud/apps/main/views/users/new.ejs +48 -0
  110. package/sample/crud/apps/main/views/worker/index.ejs +70 -0
  111. package/sample/crud/apps/main/views/ws/index.ejs +62 -0
  112. package/sample/crud/apps/main/workers/hash-worker.js +17 -0
  113. package/sample/crud/apps/main/workers/hash.task.js +22 -0
  114. package/sample/crud/ecosystem.config.cjs +9 -0
  115. package/sample/crud/mega.config.js +105 -0
  116. package/sample/crud/package-lock.json +5665 -0
  117. package/sample/crud/package.json +28 -0
  118. package/sample/crud/test/apps/main/auth-flow.integration.test.js +177 -0
  119. package/sample/crud/test/apps/main/auth-service.test.js +93 -0
  120. package/sample/crud/test/apps/main/chat-bus.test.js +101 -0
  121. package/sample/crud/test/apps/main/chat-channel.test.js +144 -0
  122. package/sample/crud/test/apps/main/cron-demo-service.test.js +93 -0
  123. package/sample/crud/test/apps/main/demo-flow.integration.test.js +386 -0
  124. package/sample/crud/test/apps/main/email-job.test.js +76 -0
  125. package/sample/crud/test/apps/main/guide-service.test.js +68 -0
  126. package/sample/crud/test/apps/main/hash-task.test.js +30 -0
  127. package/sample/crud/test/apps/main/jobs-demo-service.test.js +88 -0
  128. package/sample/crud/test/apps/main/logs-demo-service.test.js +85 -0
  129. package/sample/crud/test/apps/main/metrics-demo-service.test.js +90 -0
  130. package/sample/crud/test/apps/main/note-service.test.js +68 -0
  131. package/sample/crud/test/apps/main/perf-service.test.js +121 -0
  132. package/sample/crud/test/apps/main/perf.integration.test.js +202 -0
  133. package/sample/crud/test/apps/main/redis-demo-service.test.js +98 -0
  134. package/sample/crud/test/apps/main/tracing-demo-service.test.js +90 -0
  135. package/sample/crud/test/apps/main/upload-demo-service.test.js +61 -0
  136. package/sample/crud/test/apps/main/user-service.test.js +65 -0
  137. package/sample/crud/test/apps/main/ws-chat.integration.test.js +232 -0
  138. package/sample/crud/vitest.config.js +8 -0
  139. package/sample/crud/yarn.lock +2142 -0
  140. package/sample/simple/.env.example +15 -0
  141. package/sample/simple/README.md +52 -0
  142. package/sample/simple/apps/main/app.config.js +35 -0
  143. package/sample/simple/apps/main/controllers/pages-controller.js +22 -0
  144. package/sample/simple/apps/main/locales/client/en.json +3 -0
  145. package/sample/simple/apps/main/locales/client/ko.json +3 -0
  146. package/sample/simple/apps/main/locales/server/en.json +23 -0
  147. package/sample/simple/apps/main/locales/server/ko.json +23 -0
  148. package/sample/simple/apps/main/public/css/app.css +101 -0
  149. package/sample/simple/apps/main/public/hello.txt +1 -0
  150. package/sample/simple/apps/main/public/js/app.js +54 -0
  151. package/sample/simple/apps/main/public/js/theme-init.js +12 -0
  152. package/sample/simple/apps/main/public/vendor/bootstrap/bootstrap.bundle.min.js +7 -0
  153. package/sample/simple/apps/main/public/vendor/bootstrap/bootstrap.min.css +6 -0
  154. package/sample/simple/apps/main/routes/index.js +9 -0
  155. package/sample/simple/apps/main/routes/pages.js +12 -0
  156. package/sample/simple/apps/main/views/index.ejs +56 -0
  157. package/sample/simple/apps/main/views/layouts/main.ejs +74 -0
  158. package/sample/simple/ecosystem.config.cjs +10 -0
  159. package/sample/simple/mega.config.js +27 -0
  160. package/sample/simple/package-lock.json +1851 -0
  161. package/sample/simple/package.json +25 -0
  162. package/sample/simple/test/apps/main/index.test.js +13 -0
  163. package/sample/simple/vitest.config.js +8 -0
  164. package/src/adapters/adapter-manager.js +305 -0
  165. package/src/adapters/adapter-options.js +208 -0
  166. package/src/adapters/file-adapter.js +350 -0
  167. package/src/adapters/file-session-adapter.js +363 -0
  168. package/src/adapters/index.js +38 -0
  169. package/src/adapters/maria-adapter.js +425 -0
  170. package/src/adapters/mega-adapter.js +511 -0
  171. package/src/adapters/mega-bus-adapter.js +81 -0
  172. package/src/adapters/mega-cache-adapter.js +94 -0
  173. package/src/adapters/mega-db-adapter.js +72 -0
  174. package/src/adapters/mega-lock-adapter.js +118 -0
  175. package/src/adapters/mega-log-sink-adapter.js +46 -0
  176. package/src/adapters/mega-session-adapter.js +72 -0
  177. package/src/adapters/mongo-adapter.js +396 -0
  178. package/src/adapters/nats-adapter.js +370 -0
  179. package/src/adapters/postgres-adapter.js +341 -0
  180. package/src/adapters/redis-adapter.js +331 -0
  181. package/src/adapters/redis-session-adapter.js +261 -0
  182. package/src/adapters/redlock-adapter.js +385 -0
  183. package/src/adapters/registry.js +157 -0
  184. package/src/adapters/sqlite-adapter.js +309 -0
  185. package/src/auth/index.js +103 -0
  186. package/src/cli/commands/console-cmd.js +56 -0
  187. package/src/cli/commands/new.js +101 -0
  188. package/src/cli/commands/routes.js +107 -0
  189. package/src/cli/commands/scaffold.js +120 -0
  190. package/src/cli/commands/test-cmd.js +45 -0
  191. package/src/cli/generators/index.js +368 -0
  192. package/src/cli/index.js +472 -0
  193. package/src/cli/template-engine.js +72 -0
  194. package/src/cli/ws-hub.js +582 -0
  195. package/src/core/ajv-mapper.js +80 -0
  196. package/src/core/boot.js +323 -0
  197. package/src/core/cluster-metrics.js +278 -0
  198. package/src/core/config-loader.js +115 -0
  199. package/src/core/config-validator.js +322 -0
  200. package/src/core/ctx-builder.js +253 -0
  201. package/src/core/envelope.js +88 -0
  202. package/src/core/error-mapper.js +116 -0
  203. package/src/core/formbody.js +69 -0
  204. package/src/core/hub-link.js +552 -0
  205. package/src/core/i18n.js +525 -0
  206. package/src/core/index.js +63 -0
  207. package/src/core/mega-app.js +1138 -0
  208. package/src/core/mega-cluster.js +232 -0
  209. package/src/core/mega-server.js +176 -0
  210. package/src/core/mega-service.js +41 -0
  211. package/src/core/migration-runner.js +196 -0
  212. package/src/core/multipart.js +282 -0
  213. package/src/core/openapi.js +114 -0
  214. package/src/core/router.js +388 -0
  215. package/src/core/routes-loader.js +57 -0
  216. package/src/core/scope-registry.js +53 -0
  217. package/src/core/security.js +275 -0
  218. package/src/core/services-loader.js +98 -0
  219. package/src/core/session-cleanup-schedule.js +57 -0
  220. package/src/core/session-store.js +55 -0
  221. package/src/core/session.js +414 -0
  222. package/src/core/static-assets.js +126 -0
  223. package/src/core/template.js +294 -0
  224. package/src/core/workers-manager.js +193 -0
  225. package/src/core/ws-compression.js +112 -0
  226. package/src/core/ws-controller.js +109 -0
  227. package/src/core/ws-message.js +176 -0
  228. package/src/core/ws-upgrade.js +445 -0
  229. package/src/errors/config-error.js +16 -0
  230. package/src/errors/http-errors.js +130 -0
  231. package/src/errors/index.js +19 -0
  232. package/src/errors/mega-error.js +34 -0
  233. package/src/eslint-plugin/index.js +15 -0
  234. package/src/eslint-plugin/no-direct-model-import.js +113 -0
  235. package/src/index.js +131 -0
  236. package/src/lib/asp/config.js +83 -0
  237. package/src/lib/asp/crypto.js +145 -0
  238. package/src/lib/asp/errors.js +49 -0
  239. package/src/lib/asp/nonce-cache.js +94 -0
  240. package/src/lib/asp/plugin.js +263 -0
  241. package/src/lib/asp/ws-terminator.js +101 -0
  242. package/src/lib/env-mapper.js +222 -0
  243. package/src/lib/hub-protocol.js +322 -0
  244. package/src/lib/index.js +42 -0
  245. package/src/lib/logger/telegram-core.js +150 -0
  246. package/src/lib/logger/telegram-transport.js +126 -0
  247. package/src/lib/mega-brute-force.js +225 -0
  248. package/src/lib/mega-circuit-breaker.js +412 -0
  249. package/src/lib/mega-cron.js +169 -0
  250. package/src/lib/mega-hash.js +179 -0
  251. package/src/lib/mega-health.js +91 -0
  252. package/src/lib/mega-job-queue.js +600 -0
  253. package/src/lib/mega-job-worker.js +295 -0
  254. package/src/lib/mega-job.js +140 -0
  255. package/src/lib/mega-logger.js +128 -0
  256. package/src/lib/mega-metrics.js +661 -0
  257. package/src/lib/mega-plugin.js +650 -0
  258. package/src/lib/mega-retry.js +95 -0
  259. package/src/lib/mega-schedule.js +507 -0
  260. package/src/lib/mega-shutdown.js +176 -0
  261. package/src/lib/mega-tracing.js +715 -0
  262. package/src/lib/mega-worker.js +653 -0
  263. package/src/lib/worker-runner/process-entry.js +30 -0
  264. package/src/lib/worker-runner/task-dispatch.js +72 -0
  265. package/src/lib/worker-runner/thread-entry.js +26 -0
  266. package/src/models/index.js +7 -0
  267. package/src/models/mega-model.js +151 -0
  268. package/src/test/index.js +288 -0
  269. package/templates/adapter/code.tpl +40 -0
  270. package/templates/adapter/test.tpl +13 -0
  271. package/templates/app/app.config.tpl +10 -0
  272. package/templates/app/route.tpl +10 -0
  273. package/templates/app/test.tpl +13 -0
  274. package/templates/channel/code.tpl +38 -0
  275. package/templates/channel/test.tpl +19 -0
  276. package/templates/controller/code.tpl +16 -0
  277. package/templates/controller/route.tpl +9 -0
  278. package/templates/controller/test.tpl +14 -0
  279. package/templates/job/code.tpl +23 -0
  280. package/templates/job/test.tpl +17 -0
  281. package/templates/locale/code.tpl +3 -0
  282. package/templates/locale/test.tpl +13 -0
  283. package/templates/middleware/code.tpl +13 -0
  284. package/templates/middleware/test.tpl +11 -0
  285. package/templates/migration/code.tpl +20 -0
  286. package/templates/migration/test.tpl +14 -0
  287. package/templates/model/code.tpl +21 -0
  288. package/templates/model/test.tpl +29 -0
  289. package/templates/project/app.config.tpl +8 -0
  290. package/templates/project/app.config.views.tpl +37 -0
  291. package/templates/project/ecosystem.config.tpl +10 -0
  292. package/templates/project/env.tpl +12 -0
  293. package/templates/project/gitignore.tpl +8 -0
  294. package/templates/project/locales/client/en.json.tpl +3 -0
  295. package/templates/project/locales/client/ko.json.tpl +3 -0
  296. package/templates/project/locales/server/en.json.tpl +17 -0
  297. package/templates/project/locales/server/ko.json.tpl +17 -0
  298. package/templates/project/mega.config.tpl +11 -0
  299. package/templates/project/package.tpl +25 -0
  300. package/templates/project/public/css/app.css +101 -0
  301. package/templates/project/public/js/app.js +54 -0
  302. package/templates/project/public/js/theme-init.js +12 -0
  303. package/templates/project/public/vendor/bootstrap/bootstrap.bundle.min.js +7 -0
  304. package/templates/project/public/vendor/bootstrap/bootstrap.min.css +6 -0
  305. package/templates/project/readme.tpl +48 -0
  306. package/templates/project/route.test.tpl +13 -0
  307. package/templates/project/route.test.views.tpl +15 -0
  308. package/templates/project/route.tpl +10 -0
  309. package/templates/project/route.views.tpl +10 -0
  310. package/templates/project/views/index.ejs.tpl +58 -0
  311. package/templates/project/views/layout.ejs.tpl +73 -0
  312. package/templates/project/vitest.config.tpl +8 -0
  313. package/templates/route/code.tpl +11 -0
  314. package/templates/route/test.tpl +26 -0
  315. package/templates/schedule/code.tpl +19 -0
  316. package/templates/schedule/test.tpl +17 -0
  317. package/templates/service/code.tpl +18 -0
  318. package/templates/service/test.tpl +17 -0
  319. package/templates/worker/code.tpl +14 -0
  320. package/templates/worker/task.tpl +13 -0
  321. package/templates/worker/test.tpl +18 -0
  322. package/vitest.config.js +33 -0
@@ -0,0 +1,370 @@
1
+ // @ts-check
2
+ /**
3
+ * MegaNatsAdapter — NATS 메시지 버스 어댑터 (`nats` 공식 driver 래퍼, ADR-112).
4
+ *
5
+ * **첫 bus 도메인 어댑터**(`MegaBusAdapter` 첫 구체). DB/cache 와 달리 pub/sub·req/reply·
6
+ * queue group 메시징 인터페이스를 구현한다.
7
+ *
8
+ * # 표준 표면 (MegaBusAdapter 상속)
9
+ * - `_connect()` — `connect({ servers, ...auth, ...options })` (driver 는 connect 시점 lazy import).
10
+ * connect() 자체가 서버 응답까지 기다리므로 별도 ping 불필요(실패 시 throw=검증).
11
+ * - `_disconnect()`— `nc.drain()` — 진행 중 메시지/구독을 모두 비운 뒤 **연결까지 닫는다**(공식 문서:
12
+ * "Finally, the connection is closed"). 그래서 별도 `close()` 는 호출하지 않는다.
13
+ * - `_native()` — `NatsConnection`(raw handle, ADR-009). 사용자가 직접 NATS API(jetstream 등) 접근.
14
+ * - `healthCheck()`— `nc.rtt()` (서버 왕복 ping)으로 실제 응답성 확인.
15
+ * - `getStats()` — 베이스 stats + nats 특화(server/연결 stats: inMsgs/outMsgs/inBytes/outBytes).
16
+ * - `publish/subscribe/request` + `enqueue/process` — 아래 인터페이스.
17
+ *
18
+ * # 직렬화 = JSONCodec
19
+ * payload 는 NATS wire 에서 `Uint8Array` 다. 본 어댑터는 `JSONCodec` 로 인코드/디코드를 표준화해
20
+ * 사용자는 JS 값을 그대로 publish/subscribe 한다(수신 측에서 같은 값으로 복원). `undefined` payload
21
+ * 는 `null` 로 정규화(JSONCodec 가 undefined 를 인코드하지 못함).
22
+ *
23
+ * # queue (job) 인터페이스 — 단순 publish + queue group (jetstream 미사용, ADR-112)
24
+ * `enqueue(job, msg)` = 해당 subject 로 **단순 publish**. `process(job, handler)` = **queue group**
25
+ * 구독(같은 queue 의 구독자끼리 메시지를 load-balance 분배). jetstream(영속 큐·재시도·DLQ)은
26
+ * `MegaJob` 백엔드에서 다룬다(ADR-028) — 코어 fan-out 까지.
27
+ *
28
+ * # 콜백 에러 표면화 (silent 금지)
29
+ * subscribe/process 콜백은 비동기 메시지 전달이라 호출자에게 직접 throw 할 수 없다. 구독 에러
30
+ * (NatsError) 또는 handler throw 는 **`console.error` 로 표면화**한다(로거 미주입 베이스).
31
+ * 트레이싱이 정식 logger 로 대체 가능.
32
+ *
33
+ * # pool 미지원
34
+ * NATS 클라이언트는 단일 멀티플렉스 연결이라 풀 개념이 없다. `config.pool` 지정 시 throw.
35
+ *
36
+ * # 설정 (services.buses.<key>) — 통합 옵션 구조 (ADR-109/112)
37
+ * ```js
38
+ * services: {
39
+ * buses: {
40
+ * main: {
41
+ * driver: 'nats',
42
+ * // (a) 연결 — url 또는 discrete (host/port) (+ token 또는 user/password 인증)
43
+ * url: 'nats://host:4222', // 또는 ↓
44
+ * host: 'localhost', port: 4222,
45
+ * token: '...', // 또는 user/password
46
+ * // (b) options — connect() passthrough (pool 은 미지원)
47
+ * options: { name: 'mega', reconnect: true, maxReconnectAttempts: -1, pingInterval: 30000,
48
+ * tls: {}, jwt: '...', nkey: '...' },
49
+ * },
50
+ * },
51
+ * }
52
+ * ```
53
+ * `url`(또는 deprecated 별칭 `connectionString`) **XOR** discrete(host/port/user/password). `token` 은
54
+ * 별도 인증 축(discrete 모드 권장 — url 에 auth 포함 시 생략). 토큰/jwt/url 은 healthCheck/getStats/
55
+ * 에러 details 에 절대 노출하지 않는다.
56
+ *
57
+ * @module adapters/nats-adapter
58
+ */
59
+ import { MegaValidationError, MegaInternalError } from '../errors/http-errors.js'
60
+ import { MegaBusAdapter } from './mega-bus-adapter.js'
61
+ import { resolveConnection, assertPlainObject } from './adapter-options.js'
62
+ import * as Registry from './registry.js'
63
+
64
+ /** NATS 기본 클라이언트 포트(discrete 모드에서 port 미지정 시). */
65
+ const DEFAULT_NATS_PORT = 4222
66
+ /** request() 기본 타임아웃(ms). */
67
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30000
68
+
69
+ /**
70
+ * @typedef {object} NatsConfig
71
+ * @property {string} [driver] - 'nats' (매니저가 사용 — 어댑터는 무시).
72
+ * @property {string} [url] - `nats://host:port` 연결 문자열 (discrete 와 배타).
73
+ * @property {string} [connectionString] - `url` 의 deprecated 별칭 (하위 호환).
74
+ * @property {string} [host] @property {number} [port] @property {string} [user] @property {string} [password]
75
+ * @property {string} [token] - 토큰 인증 (별도 축 — url/discrete 어느 쪽과도 조합 가능).
76
+ * @property {any} [pool] - 미지원 — 지정 시 `adapter.invalid_option` throw (NATS 는 단일 연결, ADR-112).
77
+ * @property {Record<string, any>} [options] - connect() passthrough (name, reconnect, maxReconnectAttempts, pingInterval, tls, jwt, nkey, …).
78
+ */
79
+
80
+ export class MegaNatsAdapter extends MegaBusAdapter {
81
+ /** @type {import('nats').NatsConnection | null} 연결된 NatsConnection (connect 후에만). */
82
+ #nc = null
83
+ /** @type {import('nats').Codec<any> | null} JSONCodec (connect 시 생성). */
84
+ #codec = null
85
+ /** @type {import('nats').ConnectionOptions} _connect 에서 `connect()` 에 넘길 옵션(생성자에서 고정). */
86
+ #connectOptions
87
+
88
+ /**
89
+ * @param {NatsConfig} [config] - services.buses.<key> 설정.
90
+ * @throws {MegaValidationError} `adapter.connection_required` - url·discrete 둘 다 없음.
91
+ * @throws {MegaValidationError} `adapter.connection_conflict` - url + discrete 동시 지정.
92
+ * @throws {MegaValidationError} `adapter.invalid_option` - pool 지정/옵션 타입 오류.
93
+ */
94
+ constructor(config = /** @type {any} */ ({})) {
95
+ super(config)
96
+
97
+ // NATS 는 단일 멀티플렉스 연결이라 풀 개념이 없다 — pool 지정 명시 거부.
98
+ if (config.pool !== undefined) {
99
+ throw new MegaValidationError(
100
+ 'adapter.invalid_option',
101
+ 'nats: connection pooling is not supported (NATS uses a single multiplexed connection). Remove "pool".',
102
+ { details: { driver: 'nats', option: 'pool' } },
103
+ )
104
+ }
105
+
106
+ // 연결 모드(url XOR discrete) 결정. token 은 별도 인증 축이라 connection 충돌 검사에 넣지 않는다.
107
+ const conn = resolveConnection(config, { driver: 'nats', dbConflictsWithUrl: false })
108
+ assertPlainObject('options', config.options, { driver: 'nats' })
109
+
110
+ /** @type {import('nats').ConnectionOptions} */
111
+ const connectOptions = {}
112
+ // options(passthrough) 먼저 — 아래 servers/auth 가 항상 이긴다(연결 필수값 보호).
113
+ if (config.options !== undefined) Object.assign(connectOptions, config.options)
114
+
115
+ // servers: url 모드면 url 그대로, discrete 모드면 host[:port].
116
+ if (conn.url !== undefined) {
117
+ connectOptions.servers = conn.url
118
+ } else {
119
+ const host = conn.host ?? 'localhost'
120
+ connectOptions.servers = conn.port !== undefined ? `${host}:${conn.port}` : `${host}:${DEFAULT_NATS_PORT}`
121
+ }
122
+ // 인증 — discrete user/password 또는 token(별도 축). 지정된 것만 설정.
123
+ if (conn.user !== undefined) connectOptions.user = conn.user
124
+ if (conn.password !== undefined) connectOptions.pass = conn.password
125
+ if (config.token !== undefined) connectOptions.token = config.token
126
+
127
+ this.#connectOptions = connectOptions
128
+ }
129
+
130
+ /**
131
+ * `connect()` 로 NATS 연결 수립. connect() 가 서버 핸드셰이크까지 await 하므로 성공=연결 검증 완료
132
+ * (실패 시 throw → 베이스가 상태 failed). driver 는 **connect 시점에 lazy import**(DB 어댑터 정합).
133
+ *
134
+ * @protected
135
+ * @returns {Promise<void>}
136
+ */
137
+ async _connect() {
138
+ const { connect, JSONCodec } = await import('nats')
139
+ const nc = await connect(this.#connectOptions)
140
+ this.#nc = nc
141
+ this.#codec = JSONCodec()
142
+ }
143
+
144
+ /**
145
+ * NATS 연결 drain — 진행 중 메시지/구독을 모두 비운 뒤 연결을 닫는다(공식 문서). 별도 close 불필요.
146
+ * 베이스 상태 머신이 connected 상태에서만 호출을 보장한다.
147
+ * @protected
148
+ * @returns {Promise<void>}
149
+ */
150
+ async _disconnect() {
151
+ if (this.#nc !== null) {
152
+ const nc = this.#nc
153
+ this.#nc = null
154
+ this.#codec = null
155
+ // 이미 닫혀 있으면(서버측 절단 등) drain 이 throw 할 수 있어 가드.
156
+ if (!nc.isClosed()) await nc.drain()
157
+ }
158
+ }
159
+
160
+ /**
161
+ * raw NatsConnection handle (ADR-009). `connect()` 후에만 베이스 `native` getter 가 호출한다.
162
+ * @protected
163
+ * @returns {import('nats').NatsConnection}
164
+ */
165
+ _native() {
166
+ if (this.#nc === null) {
167
+ // 베이스 native getter 가 state 검증을 먼저 하므로 정상 경로에선 도달 안 함 — 방어.
168
+ return this._notImplemented('native')
169
+ }
170
+ return this.#nc
171
+ }
172
+
173
+ /**
174
+ * 헬스 체크 — 실제 `rtt`(서버 왕복 ping)로 응답성 확인. 실패는 throw 없이 `ok:false` + 사유.
175
+ * @returns {Promise<{ ok: boolean, driver: 'nats', state: string, server?: string, rttMs?: number, error?: string }>}
176
+ */
177
+ async healthCheck() {
178
+ if (this.state !== 'connected' || this.#nc === null || this.#nc.isClosed()) {
179
+ return { ok: false, driver: 'nats', state: this.state }
180
+ }
181
+ try {
182
+ const rttMs = await this.#nc.rtt()
183
+ return { ok: true, driver: 'nats', state: this.state, server: this.#nc.getServer(), rttMs }
184
+ } catch (err) {
185
+ return {
186
+ ok: false,
187
+ driver: 'nats',
188
+ state: this.state,
189
+ error: err instanceof Error ? err.message : String(err),
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * 누적 통계 + nats 특화(server + 연결 stats). 연결 전이면 server/stats 는 undefined.
196
+ * @returns {ReturnType<import('./mega-adapter.js').MegaAdapter['getStats']> & { driver: string, server: string | undefined, nats: import('nats').Stats | undefined }}
197
+ */
198
+ getStats() {
199
+ const nc = this.#nc
200
+ return {
201
+ ...super.getStats(),
202
+ driver: 'nats',
203
+ server: nc?.getServer(),
204
+ nats: nc ? nc.stats() : undefined,
205
+ }
206
+ }
207
+
208
+ // ──────────────────────────────────────────────────────────────────────
209
+ // MegaBusAdapter 인터페이스 — publish / subscribe / request + enqueue / process
210
+ // hook + 상태검증 + stats 는 `_instrument` 가 처리(ADR-077).
211
+ // ──────────────────────────────────────────────────────────────────────
212
+
213
+ /**
214
+ * fire-and-forget 발행 (ack X). payload 는 JSONCodec 로 인코드.
215
+ * @param {string} subject
216
+ * @param {any} payload
217
+ * @returns {Promise<void>}
218
+ */
219
+ async publish(subject, payload) {
220
+ return this._instrument('publish', { subject }, async () => {
221
+ const nc = /** @type {import('nats').NatsConnection} */ (this.#nc)
222
+ nc.publish(subject, this.#encode(payload))
223
+ })
224
+ }
225
+
226
+ /**
227
+ * 구독. `handler` 는 `(msg, replyFn?) => any` — msg 는 디코드된 값, replyFn 은 reply subject 가
228
+ * 있을 때만 제공(`request` 응답용). 반환된 핸들의 `unsubscribe()` 로 정리.
229
+ *
230
+ * @param {string} subject
231
+ * @param {(msg: any, replyFn?: (payload: any) => void) => any} handler
232
+ * @returns {Promise<{ unsubscribe: () => Promise<void> }>}
233
+ */
234
+ async subscribe(subject, handler) {
235
+ return this._instrument('subscribe', { subject }, async () => {
236
+ const nc = /** @type {import('nats').NatsConnection} */ (this.#nc)
237
+ const sub = nc.subscribe(subject, {
238
+ callback: (err, msg) => this.#dispatch('subscribe', subject, handler, err, msg),
239
+ })
240
+ return {
241
+ unsubscribe: async () => {
242
+ sub.unsubscribe()
243
+ },
244
+ }
245
+ })
246
+ }
247
+
248
+ /**
249
+ * req/reply 패턴. `timeout`(ms) 초과 시 `bus.request_timeout`, 응답자 없으면 `bus.no_responders`.
250
+ * @param {string} subject
251
+ * @param {any} payload
252
+ * @param {{ timeout?: number }} [opts]
253
+ * @returns {Promise<any>} 디코드된 reply.
254
+ */
255
+ async request(subject, payload, { timeout = DEFAULT_REQUEST_TIMEOUT_MS } = {}) {
256
+ return this._instrument('request', { subject, timeout }, async () => {
257
+ const nc = /** @type {import('nats').NatsConnection} */ (this.#nc)
258
+ try {
259
+ const reply = await nc.request(subject, this.#encode(payload), { timeout })
260
+ return /** @type {import('nats').Codec<any>} */ (this.#codec).decode(reply.data)
261
+ } catch (err) {
262
+ // NATS 타임아웃/무응답을 명시 에러 코드로 변환(silent 무시 X). 그 외 에러는 원본 전파.
263
+ const code = /** @type {any} */ (err)?.code
264
+ if (code === 'TIMEOUT') {
265
+ throw new MegaInternalError('bus.request_timeout', `nats request("${subject}") timed out after ${timeout}ms.`, {
266
+ details: { subject, timeout },
267
+ cause: err,
268
+ })
269
+ }
270
+ if (code === '503') {
271
+ throw new MegaInternalError('bus.no_responders', `nats request("${subject}"): no responders subscribed to the subject.`, {
272
+ details: { subject },
273
+ cause: err,
274
+ })
275
+ }
276
+ throw err
277
+ }
278
+ })
279
+ }
280
+
281
+ /**
282
+ * 잡 enqueue — 단순 publish (queue group 분배는 `process` 측 책임, ADR-112).
283
+ * @param {string} jobName
284
+ * @param {any} payload
285
+ * @returns {Promise<void>}
286
+ */
287
+ async enqueue(jobName, payload) {
288
+ return this._instrument('enqueue', { jobName }, async () => {
289
+ const nc = /** @type {import('nats').NatsConnection} */ (this.#nc)
290
+ nc.publish(jobName, this.#encode(payload))
291
+ })
292
+ }
293
+
294
+ /**
295
+ * 잡 처리 등록 — **queue group** 구독(같은 queue 구독자끼리 load-balance). 기본 queue 이름은 jobName
296
+ * (같은 잡의 모든 워커가 한 그룹).
297
+ *
298
+ * **subscription 핸들을 반환하지 않는 건 의도적**(L-2): worker 는 앱 수명 동안 상주하는 것이
299
+ * 정상이고(개별 잡 처리 등록을 런타임에 떼었다 붙였다 하는 패턴은 비표준), 정리는 disconnect 시
300
+ * `_disconnect()` 의 `nc.drain()` 이 **모든 구독을 일괄 비우며** 처리한다. 개별 unsubscribe 가
301
+ * 필요한 일시 구독은 `subscribe()`(핸들 반환)를 쓴다 — `process` 는 fire-and-forget `enqueue` 와
302
+ * 대칭인 상주 worker 용이다. 영속 큐/재시도/DLQ 가 필요하면 `MegaJob`(jetstream, ADR-028).
303
+ *
304
+ * @param {string} jobName
305
+ * @param {(payload: any) => any} handler
306
+ * @param {{ queue?: string }} [opts]
307
+ * @returns {Promise<void>}
308
+ */
309
+ async process(jobName, handler, { queue = jobName } = {}) {
310
+ return this._instrument('process', { jobName, queue }, async () => {
311
+ const nc = /** @type {import('nats').NatsConnection} */ (this.#nc)
312
+ nc.subscribe(jobName, {
313
+ queue,
314
+ callback: (err, msg) => this.#dispatch('process', jobName, (m) => handler(m), err, msg),
315
+ })
316
+ })
317
+ }
318
+
319
+ /**
320
+ * payload → Uint8Array (JSONCodec). undefined 는 null 로 정규화(JSONCodec 가 undefined 미지원).
321
+ * @param {any} payload @returns {Uint8Array}
322
+ */
323
+ #encode(payload) {
324
+ return /** @type {import('nats').Codec<any>} */ (this.#codec).encode(payload === undefined ? null : payload)
325
+ }
326
+
327
+ /**
328
+ * 구독/잡 콜백 공통 디스패처 — 에러·디코드·handler 호출을 표면화한다(silent 금지).
329
+ * 구독 에러(NatsError)·디코드 실패·handler throw 를 모두 `console.error` 로 드러낸다.
330
+ *
331
+ * @param {'subscribe' | 'process'} kind
332
+ * @param {string} subject
333
+ * @param {(msg: any, replyFn?: (payload: any) => void) => any} handler
334
+ * @param {import('nats').NatsError | null} err
335
+ * @param {import('nats').Msg} msg
336
+ * @returns {void}
337
+ */
338
+ #dispatch(kind, subject, handler, err, msg) {
339
+ if (err) {
340
+ // 구독 레벨 에러 — 콜백은 호출자에게 throw 못하므로 표면화.
341
+ console.error(`[MegaNatsAdapter] ${kind}("${subject}") subscription error:`, err)
342
+ return
343
+ }
344
+ let decoded
345
+ try {
346
+ decoded = /** @type {import('nats').Codec<any>} */ (this.#codec).decode(msg.data)
347
+ } catch (decodeErr) {
348
+ console.error(`[MegaNatsAdapter] ${kind}("${subject}") payload decode failed:`, decodeErr)
349
+ return
350
+ }
351
+ // reply subject 가 있으면 replyFn 제공(request 응답용). subscribe 만 해당 — process 는 단방향.
352
+ const replyFn =
353
+ kind === 'subscribe' && msg.reply ? /** @param {any} p */ (p) => msg.respond(this.#encode(p)) : undefined
354
+ try {
355
+ const out = handler(decoded, replyFn)
356
+ // handler 가 async 면 reject 도 표면화(떠다니는 promise 가 silent 실패되지 않게).
357
+ if (out && typeof (/** @type {any} */ (out).then) === 'function') {
358
+ /** @type {Promise<any>} */ (out).catch((hErr) =>
359
+ console.error(`[MegaNatsAdapter] ${kind}("${subject}") async handler rejected:`, hErr),
360
+ )
361
+ }
362
+ } catch (hErr) {
363
+ console.error(`[MegaNatsAdapter] ${kind}("${subject}") handler threw:`, hErr)
364
+ }
365
+ }
366
+ }
367
+
368
+ // 빌트인 driver 자기등록 (ADR-044) — 배럴(`adapters/index.js`)이 본 모듈을 import 하면 트리거된다.
369
+ // nats 는 _connect() 의 lazy import 까지 로드되지 않으므로 등록은 안전(미사용 환경 비강제).
370
+ Registry.register('nats', MegaNatsAdapter)