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,72 @@
1
+ // @ts-check
2
+ /**
3
+ * 워커 런타임(스레드/프로세스) **공통 디스패처** — `static taskFile` 모듈을 로드하고, 부모가 보낸
4
+ * `{ id, taskName, args }` 메시지를 그 모듈이 export 한 task 함수로 라우팅한다 (ADR-124).
5
+ *
6
+ * thread(`worker_threads`)·process(`child_process`) 진입점이 채널(parentPort vs process)만 다르고 로직은
7
+ * 동일하므로 한 곳으로 모은다. 사용자 taskFile 은 **평범한 async 함수들을 named export** 하면 된다
8
+ * (메시지 루프 보일러플레이트 없음):
9
+ *
10
+ * @example
11
+ * // workers/image-tasks.js (= static taskFile)
12
+ * export async function resize({ src, dst, width }) { ... return result }
13
+ *
14
+ * @module lib/worker-runner/task-dispatch
15
+ */
16
+ import { pathToFileURL } from 'node:url'
17
+
18
+ /**
19
+ * taskFile 모듈을 동적 import 한다. 절대경로를 file:// URL 로 변환해 OS·ESM 호환성을 보장한다.
20
+ * @param {string} taskFile - 워커 진입 모듈 절대경로.
21
+ * @returns {Promise<Record<string, any>>} 모듈 네임스페이스(task 함수들).
22
+ */
23
+ export function loadTaskModule(taskFile) {
24
+ return import(pathToFileURL(taskFile).href)
25
+ }
26
+
27
+ /**
28
+ * Error 를 스레드/프로세스 경계 너머로 보낼 수 있게 평탄화한다 (structuredClone/IPC 는 Error 인스턴스의
29
+ * name/message/stack 을 보존하지 않으므로 명시 직렬화). 부모가 이를 받아 다시 Error 로 복원한다.
30
+ * @param {unknown} err
31
+ * @returns {{ name: string, message: string, stack?: string, code?: string }}
32
+ */
33
+ export function serializeError(err) {
34
+ if (err instanceof Error) {
35
+ return {
36
+ name: err.name,
37
+ message: err.message,
38
+ stack: err.stack,
39
+ // MegaError 계열의 code 도 보존(있으면).
40
+ ...(typeof (/** @type {any} */ (err).code) === 'string' ? { code: /** @type {any} */ (err).code } : {}),
41
+ }
42
+ }
43
+ return { name: 'Error', message: String(err) }
44
+ }
45
+
46
+ /**
47
+ * 부모가 보낸 task 디스패치 메시지 1건을 처리한다 — 모듈에서 `taskName` 함수를 찾아 실행하고 결과/에러를
48
+ * 부모로 돌려보낸다. 미존재 task·실행 실패를 묵살하지 않고 `{ ok:false, error }` 로 명시 보고.
49
+ *
50
+ * @param {Record<string, any>} mod - loadTaskModule 결과.
51
+ * @param {any} msg - 부모 메시지 `{ id, taskName, args }`.
52
+ * @param {(reply: any) => void} send - 부모로 회신하는 채널(`parentPort.postMessage` / `process.send`).
53
+ * @returns {Promise<void>}
54
+ */
55
+ export async function handleTaskMessage(mod, msg, send) {
56
+ // ready 핸드셰이크 등 task 가 아닌 메시지는 무시(부모는 task 메시지에 number id 를 항상 싣는다).
57
+ if (!msg || typeof msg.id !== 'number') return
58
+ const { id, taskName, args } = msg
59
+ try {
60
+ const fn = mod[taskName]
61
+ if (typeof fn !== 'function') {
62
+ throw new Error(
63
+ `MegaWorker: task '${taskName}' is not an exported function in the task module ` +
64
+ `(exports: ${Object.keys(mod).filter((k) => typeof mod[k] === 'function').join(', ') || '(none)'}).`,
65
+ )
66
+ }
67
+ const result = await fn(args)
68
+ send({ id, ok: true, result })
69
+ } catch (err) {
70
+ send({ id, ok: false, error: serializeError(err) })
71
+ }
72
+ }
@@ -0,0 +1,26 @@
1
+ // @ts-check
2
+ /**
3
+ * `worker_threads` 진입점 (mode:'thread') — `MegaWorker` 가 `new Worker(thread-entry, { workerData })` 로
4
+ * 띄운다 (ADR-124). taskFile 을 로드한 뒤 `{ ready:true }` 핸드셰이크를 보내고, 이후
5
+ * 부모(`MegaWorker`)의 task 메시지를 공통 디스패처로 처리한다.
6
+ *
7
+ * 상단 await(동적 import)가 실패하면 모듈 평가가 reject 되어 워커가 `error`/`exit` 로 죽고, 부모의 spawn
8
+ * 핸드셰이크가 그걸 spawn 실패로 보고한다(silent 아님).
9
+ *
10
+ * @module lib/worker-runner/thread-entry
11
+ */
12
+ import { parentPort, workerData } from 'node:worker_threads'
13
+ import { loadTaskModule, handleTaskMessage } from './task-dispatch.js'
14
+
15
+ if (!parentPort) {
16
+ throw new Error('thread-entry.js must run inside a worker_thread (parentPort is null).')
17
+ }
18
+
19
+ const port = parentPort
20
+ const mod = await loadTaskModule(workerData.taskFile)
21
+ port.on('message', (msg) => {
22
+ // handleTaskMessage 는 자체적으로 try/catch 해 항상 회신하므로 여기서 추가 처리는 불필요.
23
+ void handleTaskMessage(mod, msg, (reply) => port.postMessage(reply))
24
+ })
25
+ // 로드 완료 신호 — 부모는 이 메시지를 받고서야 task 를 디스패치한다(race 방지).
26
+ port.postMessage({ ready: true })
@@ -0,0 +1,7 @@
1
+ // @ts-check
2
+ /**
3
+ * models 배럴 — 데이터 모델 베이스 (ADR-008/009/010/081).
4
+ *
5
+ * @module models
6
+ */
7
+ export { MegaModel } from './mega-model.js'
@@ -0,0 +1,151 @@
1
+ // @ts-check
2
+ /**
3
+ * MegaModel — SQL · Document DB 전용 얇은 모델 베이스 (ADR-008/009/010/081).
4
+ *
5
+ * ORM·쿼리 빌더는 **의도적으로 없다**(ADR-009). 모델은 세 가지만 한다:
6
+ * 1. `static get db` 로 어댑터 native handle(pg Pool / MongoClient / mariadb Pool /
7
+ * better-sqlite3 Database)을 노출 → 사용자가 native API 로 도메인 메서드를 직접 작성.
8
+ * 2. `static withTransaction(fn)` 으로 명시적 트랜잭션 경계 제공(ADR-010) → 어댑터에 위임.
9
+ * 3. `static query(sql, params)` 로 **계측된** SQL 실행(ADR-138) → 어댑터 `query` 도메인 메서드에
10
+ * 위임(자동 span·메트릭). `this.db.query`(native, 계측 미통과)와 구분된다.
11
+ *
12
+ * # 사용 예 (13-sample-code §4)
13
+ * ```js
14
+ * export class User extends MegaModel {
15
+ * static adapter = 'primary' // mega.config.js 의 services.databases.primary 키 (글로벌, ADR-061)
16
+ * static table = 'users' // 데이터 소스 식별자 (SQL table / Mongo collection 통일, ADR-081)
17
+ * static async findByEmail(email) {
18
+ * const { rows } = await this.db.query('select * from users where email = $1', [email])
19
+ * return rows[0] ?? null
20
+ * }
21
+ * }
22
+ * // 트랜잭션 (ADR-010 — 자동 request-scoped 트랜잭션 없음, 항상 명시적)
23
+ * await User.withTransaction(async (tx) => { await tx.query('...') })
24
+ * ```
25
+ *
26
+ * # 어댑터 lookup 경로 — `ctx.db(alias)` 와 다름 (중요)
27
+ * `MegaModel.adapter` 는 **전역 services.databases 의 globalKey** 를 직접 가리킨다(03-api-spec
28
+ * §162, 08-class-specs §3.1). 앱별 별명(app.config.js 의 `databases: { alias: globalKey }`)이
29
+ * 아니다 — 모델은 ctx(요청 컨텍스트) 없이 클래스 static 으로 평가되므로 글로벌 매니저
30
+ * ({@link module:adapters/adapter-manager}) 에서 globalKey 로 바로 공유 인스턴스를 잡는다.
31
+ * 반면 `ctx.db('alias')` 는 앱 별명을 globalKey 로 바꾼 뒤 같은 매니저를 본다(ADR-102).
32
+ * 둘 다 종착지는 동일한 전역 공유 어댑터 인스턴스다.
33
+ *
34
+ * # db 는 native handle, withTransaction 만 어댑터 표면 (ADR-102 문구 정합)
35
+ * `ctx.db(key)` 는 어댑터(MegaDbAdapter)를 돌려주지만(ADR-102), `MegaModel.db` 는 그 어댑터의
36
+ * `.native`(raw handle)를 돌려준다 — 4개 정본(03/06/08/13)이 일관되게 `this.db.query(...)` 처럼
37
+ * native API 직접 호출을 보인다. ADR-102 가 말한 "MegaModel 이 어댑터 표면을 쓴다"는
38
+ * `withTransaction` 위임을 가리킨다(트랜잭션은 native handle 이 아니라 어댑터가 경계를 관리).
39
+ *
40
+ * @module models/mega-model
41
+ */
42
+ import { MegaInternalError } from '../errors/http-errors.js'
43
+ import * as MegaAdapterManager from '../adapters/adapter-manager.js'
44
+
45
+ export class MegaModel {
46
+ /**
47
+ * 어댑터 globalKey — `mega.config.js` 의 `services.databases.<key>` 와 정확히 일치 (ADR-061).
48
+ * 서브클래스가 박는다. 비어 있으면 db/withTransaction 접근 시 throw.
49
+ * @type {string}
50
+ */
51
+ static adapter = ''
52
+
53
+ /**
54
+ * 데이터 소스 식별자. SQL 의 table 또는 MongoDB 의 collection 모두 본 필드에 명시 (ADR-081).
55
+ * 어댑터별 native 호출 시 의미는 동일. 비어 있으면 db/withTransaction 접근 시 throw.
56
+ * @type {string}
57
+ */
58
+ static table = ''
59
+
60
+ /**
61
+ * 자동 lookup — `adapter` 키로 어댑터의 driver native handle 반환
62
+ * (pg Pool / MongoClient / mariadb Pool / better-sqlite3 Database, ADR-009).
63
+ *
64
+ * 어댑터가 아직 `connect()` 전이면 어댑터 베이스가 `adapter.not_connected` 를 throw 한다
65
+ * (08-class-specs §3.2 불변식 — native 는 연결 후에만 유효).
66
+ *
67
+ * @returns {any} driver native handle.
68
+ */
69
+ static get db() {
70
+ return this._resolveAdapter().native
71
+ }
72
+
73
+ /**
74
+ * 명시적 트랜잭션 경계 (ADR-010). 어댑터의 `withTransaction` 에 위임한다 — 성공 시 commit,
75
+ * `fn` 이 throw 하면 driver native rollback 후 원본 에러 re-throw. **자동 request-scoped
76
+ * 트랜잭션은 만들지 않는다** (ADR-010).
77
+ *
78
+ * `fn` 이 받는 인자는 **트랜잭션 컨텍스트의 native handle**(예: pg client) 이다. 같은 모델의
79
+ * 다른 메서드(`this.db.query`)는 트랜잭션 **밖** 글로벌 handle 을 쓰므로, 트랜잭션 안에서는
80
+ * `fn` 인자를 명시적으로 써야 격리가 유지된다 (08-class-specs §3.1 — 사용자 책임).
81
+ *
82
+ * nested 트랜잭션 정책(postgres SAVEPOINT / Mongo throw)은 **구체 어댑터 책임**이다.
83
+ * 베이스는 위임만 한다.
84
+ *
85
+ * `fn` 인자 수는 어댑터별로 다르다 — SQL 어댑터는 `(client)` 1개, Mongo 어댑터는 `(db, session)`
86
+ * 2개를 넘긴다(ADR-108). 따라서 가변 인자로 타입을 둔다(어댑터가 인자 형태의 정본).
87
+ *
88
+ * @template T
89
+ * @param {(...args: any[]) => Promise<T>} fn - 트랜잭션 컨텍스트 native handle(들)을 받는 콜백.
90
+ * @returns {Promise<T>}
91
+ */
92
+ static async withTransaction(fn) {
93
+ return this._resolveAdapter().withTransaction(fn)
94
+ }
95
+
96
+ /**
97
+ * **계측된 쿼리** — `this.db.query`(native handle 직접 호출, 트레이싱 미통과)와 달리 어댑터의
98
+ * `query` 도메인 메서드에 위임해 자동 span(`<driver>.query`)·메트릭·stats 에 잡힌다(ADR-138).
99
+ * 진행 중 트랜잭션이 있으면 어댑터가 같은 connection 으로 실행하므로, `withTransaction(async (tx) => …)`
100
+ * 콜백 안에서 `Model.query(sql, params)` 를 부르면 격리가 유지되고 span 도 트랜잭션 span 의 자식이 된다
101
+ * (native `tx.query`는 계측 미통과 escape hatch). SQL 어댑터(postgres/maria/sqlite) 전용 — Document
102
+ * DB(mongo)는 `adapter.not_implemented`.
103
+ *
104
+ * @param {string} sql - 파라미터화된 SQL(placeholder 보존 — 값 인터폴레이션 금지).
105
+ * @param {any[]} [params] - placeholder 바인딩 값.
106
+ * @returns {Promise<any>} driver native 쿼리 결과(어댑터별 형태 — ADR-009).
107
+ */
108
+ static async query(sql, params) {
109
+ return this._resolveAdapter().query(sql, params)
110
+ }
111
+
112
+ /**
113
+ * `adapter`/`table` 정합성을 검증하고 글로벌 매니저에서 어댑터(MegaDbAdapter)를 잡아 반환한다.
114
+ *
115
+ * 정본은 "부팅 시 검증"(08-class-specs §3.1 라이프사이클, ADR-067)을 말하지만, 중앙 부팅
116
+ * 오케스트레이터(`mega serve` CLI)가 아직 미배선이라(ADR-102 미결 항목) **접근 시점 lazy 검증**
117
+ * 으로 둔다 — `ctx.db(alias)` 가 미등록 별명을 즉시 throw 하는 것과 같은 패턴(ADR-102).
118
+ * 오케스트레이터가 생기면 부팅 시 일괄 검증을 추가한다(ADR-104).
119
+ *
120
+ * @protected
121
+ * @returns {import('../adapters/mega-db-adapter.js').MegaDbAdapter}
122
+ */
123
+ static _resolveAdapter() {
124
+ const model = this.name
125
+ if (!this.adapter) {
126
+ throw new MegaInternalError(
127
+ 'model.adapter_required',
128
+ `${model}.adapter is empty — set "static adapter = '<services.databases key>'" (ADR-061).`,
129
+ { details: { model } },
130
+ )
131
+ }
132
+ if (!this.table) {
133
+ throw new MegaInternalError(
134
+ 'model.table_required',
135
+ `${model}.table is empty — set "static table = '<table-or-collection>'" (ADR-081).`,
136
+ { details: { model, adapter: this.adapter } },
137
+ )
138
+ }
139
+ if (!MegaAdapterManager.has('db', this.adapter)) {
140
+ throw new MegaInternalError(
141
+ 'model.adapter_not_found',
142
+ `${model}.adapter "${this.adapter}" is not a registered database (services.databases). ` +
143
+ 'Define it in mega.config.js and boot the adapter manager first.',
144
+ { details: { model, adapter: this.adapter } },
145
+ )
146
+ }
147
+ return /** @type {import('../adapters/mega-db-adapter.js').MegaDbAdapter} */ (
148
+ MegaAdapterManager.get('db', this.adapter)
149
+ )
150
+ }
151
+ }
@@ -0,0 +1,288 @@
1
+ // @ts-check
2
+ /**
3
+ * `mega-framework/test` — 단위 테스트 fixture 헬퍼 `MegaTest` (ADR-144, 03-api-spec §12).
4
+ *
5
+ * 컨트롤러(정적 메서드)·WS 채널·서비스의 단위 테스트를 위한 가벼운 fixture 를 만든다. 실 Fastify·실
6
+ * 어댑터 없이 `(req, res, ctx)` / `(sock, ctx)` 모양만 충실히 흉내 내, 핸들러가 의존하는 표면(params·
7
+ * services·session·user 등)을 주입한다. zero-dep(node 빌트인만).
8
+ *
9
+ * 구현 범위(Phase 6): 단위 헬퍼 `makeHttp` · `makeWs` · `mockBus` · `mockCache`. 통합 fixture
10
+ * `makeApp` · `withDatabase`(03-api-spec §12)는 실 부팅·트랜잭션 격리가 필요해 별도 작업으로 연기한다
11
+ * (ADR-144 — 명시 추적, 우회 아님). 통합 테스트는 그동안 `bootApp`(core)을 직접 쓴다.
12
+ *
13
+ * @module test
14
+ */
15
+
16
+ /** 아무것도 출력하지 않는 로거 — 단위 테스트 소음 제거. pino child() 체이닝 호환. */
17
+ function silentLogger() {
18
+ /** @type {any} */
19
+ const log = {}
20
+ for (const level of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) log[level] = () => {}
21
+ log.child = () => log
22
+ return log
23
+ }
24
+
25
+ /** ctx.tracer no-op — `span(name, fn)` 이 noop span 으로 fn 을 실행하고 결과를 그대로 반환(옵트인 OFF 동형). */
26
+ const noopTracer = {
27
+ /** @param {string} _name @param {(span: any) => any} fn */
28
+ span(_name, fn) {
29
+ const span = { setAttribute() {}, setAttributes() {}, addEvent() {}, recordException() {}, end() {} }
30
+ return fn(span)
31
+ },
32
+ }
33
+
34
+ /**
35
+ * 어댑터 접근자(`ctx.db('alias')` 등)를 mock 맵에서 만든다. 맵에 없는 별명 접근은 명확히 throw
36
+ * (silent undefined 금지 — 테스트가 잘못된 별명을 조용히 통과시키지 않게).
37
+ * @param {'db'|'cache'|'bus'|'lock'} domain
38
+ * @param {Record<string, any>} map - { alias: adapterMock }
39
+ * @returns {(alias: string) => any}
40
+ */
41
+ function makeAccessor(domain, map) {
42
+ return (alias) => {
43
+ if (!(alias in map)) {
44
+ throw new Error(`MegaTest: ctx.${domain}('${alias}') — not provided. Pass { ${domain}: { '${alias}': mock } } to makeHttp/makeWs.`)
45
+ }
46
+ return map[alias]
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Fastify reply 를 흉내 내는 mock. 체이닝(`res.code(201)`)과 상태/헤더/전송 payload 기록을 지원한다.
52
+ * @returns {Record<string, any>}
53
+ */
54
+ function makeReply() {
55
+ /** @type {Record<string, any>} */
56
+ const reply = {
57
+ statusCode: 200,
58
+ sent: false,
59
+ payload: undefined,
60
+ headers: /** @type {Record<string, any>} */ ({}),
61
+ code(/** @type {number} */ n) {
62
+ this.statusCode = n
63
+ return this
64
+ },
65
+ status(/** @type {number} */ n) {
66
+ this.statusCode = n
67
+ return this
68
+ },
69
+ header(/** @type {string} */ k, /** @type {any} */ v) {
70
+ this.headers[String(k).toLowerCase()] = v
71
+ return this
72
+ },
73
+ getHeader(/** @type {string} */ k) {
74
+ return this.headers[String(k).toLowerCase()]
75
+ },
76
+ type(/** @type {string} */ t) {
77
+ this.headers['content-type'] = t
78
+ return this
79
+ },
80
+ send(/** @type {any} */ p) {
81
+ this.sent = true
82
+ this.payload = p
83
+ return this
84
+ },
85
+ }
86
+ return reply
87
+ }
88
+
89
+ /**
90
+ * WS 소켓을 흉내 내는 mock. `send` 한 메시지를 `sentMessages` 에 기록한다.
91
+ * @param {{ id?: string, metadata?: object }} [opts]
92
+ * @returns {Record<string, any>}
93
+ */
94
+ function makeSocket({ id = 'test-sock', metadata = {} } = {}) {
95
+ /** @type {any[]} */
96
+ const sentMessages = []
97
+ return {
98
+ id,
99
+ metadata: { ...metadata },
100
+ sentMessages,
101
+ send(/** @type {any} */ msg) {
102
+ sentMessages.push(msg)
103
+ },
104
+ close(/** @type {number} */ code, /** @type {string} */ reason) {
105
+ this.closed = { code, reason }
106
+ },
107
+ }
108
+ }
109
+
110
+ /**
111
+ * 채널 broadcast/directToUser 를 기록하는 mock. `ctx.channel` 로 주입.
112
+ * @returns {Record<string, any>}
113
+ */
114
+ function makeChannelStub() {
115
+ /** @type {any[]} */
116
+ const broadcasts = []
117
+ /** @type {any[]} */
118
+ const directs = []
119
+ return {
120
+ broadcasts,
121
+ directs,
122
+ broadcast(/** @type {any} */ msg) {
123
+ broadcasts.push(msg)
124
+ },
125
+ directToUser(/** @type {any} */ userId, /** @type {any} */ msg) {
126
+ directs.push({ userId, msg })
127
+ },
128
+ }
129
+ }
130
+
131
+ export class MegaTest {
132
+ /**
133
+ * HTTP 액션 단위 테스트용 fixture (03-api-spec §12). 정적 메서드 컨트롤러·인라인 핸들러를
134
+ * `(req, res, ctx)` 로 직접 호출해 검증할 수 있다.
135
+ *
136
+ * @param {object} [opts]
137
+ * @param {Record<string, any>} [opts.params] - 경로 파라미터(`req.params`).
138
+ * @param {Record<string, any>} [opts.query] - 쿼리스트링(`req.query`).
139
+ * @param {any} [opts.body] - 요청 본문(`req.body`).
140
+ * @param {Record<string, any>} [opts.headers] - 요청 헤더(`req.headers`).
141
+ * @param {Record<string, any>} [opts.services] - `ctx.services` 로 주입할 서비스 mock 맵.
142
+ * @param {any} [opts.user] - 인증 사용자(`req.user`/`ctx.user`). 가드 우회 단위 테스트용.
143
+ * @param {Record<string, any>|null} [opts.session] - `req.session`/`ctx.session` 객체.
144
+ * @param {string} [opts.method] - HTTP 메서드(기본 'GET').
145
+ * @param {string} [opts.url] - 요청 경로(기본 '/').
146
+ * @param {string} [opts.requestId] - `ctx.requestId`/`req.id`(기본 'test-req').
147
+ * @param {any} [opts.app] - `ctx.app`(기본 null).
148
+ * @param {any} [opts.log] - 로거(기본 silent).
149
+ * @param {Record<string, any>} [opts.db] - `ctx.db(alias)` mock 맵.
150
+ * @param {Record<string, any>} [opts.cache] - `ctx.cache(alias)` mock 맵.
151
+ * @param {Record<string, any>} [opts.bus] - `ctx.bus(alias)` mock 맵.
152
+ * @param {Record<string, any>} [opts.lock] - `ctx.lock(alias)` mock 맵.
153
+ * @returns {{ req: any, res: any, ctx: any }} 핸들러에 그대로 넘기는 fixture(느슨한 타입 — 테스트 편의).
154
+ */
155
+ static makeHttp({
156
+ params = {},
157
+ query = {},
158
+ body = undefined,
159
+ headers = {},
160
+ services = {},
161
+ user = null,
162
+ session = null,
163
+ method = 'GET',
164
+ url = '/',
165
+ requestId = 'test-req',
166
+ app = null,
167
+ log,
168
+ db = {},
169
+ cache = {},
170
+ bus = {},
171
+ lock = {},
172
+ } = {}) {
173
+ const logger = log ?? silentLogger()
174
+ const res = makeReply()
175
+ /** @type {Record<string, any>} */
176
+ const req = { params, query, body, headers, method, url, id: requestId, log: logger, session, user }
177
+
178
+ /** @type {Record<string, any>} */
179
+ const ctx = {
180
+ app,
181
+ log: logger,
182
+ requestId,
183
+ req,
184
+ reply: res,
185
+ services,
186
+ session,
187
+ lang: null,
188
+ t: (/** @type {string} */ key, /** @type {any} */ def) => (typeof def === 'string' ? def : key),
189
+ workers: {},
190
+ tracer: noopTracer,
191
+ db: makeAccessor('db', db),
192
+ cache: makeAccessor('cache', cache),
193
+ bus: makeAccessor('bus', bus),
194
+ lock: makeAccessor('lock', lock),
195
+ render: (/** @type {string} */ view) => {
196
+ throw new Error(`MegaTest: ctx.render('${view}') is not configured in unit fixtures.`)
197
+ },
198
+ }
199
+ // ctx.user — req.user 와 양방향(getter=read, setter=write) 동기화. 가드(ADR-143)·미들웨어(ADR-134)
200
+ // 두 주입 패턴 모두 호환.
201
+ Object.defineProperty(ctx, 'user', {
202
+ get: () => req.user ?? null,
203
+ set: (v) => {
204
+ req.user = v
205
+ },
206
+ enumerable: true,
207
+ configurable: true,
208
+ })
209
+ return { req, res, ctx }
210
+ }
211
+
212
+ /**
213
+ * WS 채널 단위 테스트용 fixture (03-api-spec §12). 채널 메서드(`async join(sock, payload, ctx)` 등)를
214
+ * 직접 호출해 검증한다. `ctx.channel` 은 broadcast/directToUser 를 기록하는 stub.
215
+ *
216
+ * @param {object} [opts]
217
+ * @param {any} [opts.user] - 인증 사용자(`ctx.user`).
218
+ * @param {Record<string, any>} [opts.services] - `ctx.services` mock 맵.
219
+ * @param {Record<string, any>|null} [opts.session] - `ctx.session`.
220
+ * @param {{ id?: string, metadata?: object }} [opts.socket] - 소켓 초기값.
221
+ * @param {any} [opts.log] - 로거(기본 silent).
222
+ * @returns {{ sock: any, ctx: any }} 채널 메서드에 넘기는 fixture(느슨한 타입 — 테스트 편의).
223
+ */
224
+ static makeWs({ user = null, services = {}, session = null, socket = {}, log } = {}) {
225
+ const logger = log ?? silentLogger()
226
+ const sock = makeSocket(socket)
227
+ /** @type {Record<string, any>} */
228
+ const ctx = {
229
+ log: logger,
230
+ services,
231
+ session,
232
+ user,
233
+ lang: null,
234
+ tracer: noopTracer,
235
+ channel: makeChannelStub(),
236
+ }
237
+ return { sock, ctx }
238
+ }
239
+
240
+ /**
241
+ * in-memory mock bus — `publish` 호출을 기록하고 `subscribe` 핸들러로 즉시 dispatch 한다. 실 NATS
242
+ * 없이 잡/이벤트 발행 검증용.
243
+ * @returns {Record<string, any>}
244
+ */
245
+ static mockBus() {
246
+ /** @type {Array<{ subject: string, payload: any }>} */
247
+ const published = []
248
+ /** @type {Map<string, Function[]>} */
249
+ const handlers = new Map()
250
+ return {
251
+ published,
252
+ async publish(/** @type {string} */ subject, /** @type {any} */ payload) {
253
+ published.push({ subject, payload })
254
+ for (const fn of handlers.get(subject) ?? []) await fn(payload)
255
+ },
256
+ async subscribe(/** @type {string} */ subject, /** @type {Function} */ handler) {
257
+ const list = handlers.get(subject) ?? []
258
+ list.push(handler)
259
+ handlers.set(subject, list)
260
+ },
261
+ }
262
+ }
263
+
264
+ /**
265
+ * in-memory mock cache — Map 백엔드 `get/set/del/has`. TTL 인자는 받지만 만료는 흉내 내지 않는다
266
+ * (단위 테스트는 만료 동작이 아니라 호출·값을 검증).
267
+ * @returns {Record<string, any>}
268
+ */
269
+ static mockCache() {
270
+ /** @type {Map<string, any>} */
271
+ const store = new Map()
272
+ return {
273
+ store,
274
+ async get(/** @type {string} */ key) {
275
+ return store.has(key) ? store.get(key) : null
276
+ },
277
+ async set(/** @type {string} */ key, /** @type {any} */ value) {
278
+ store.set(key, value)
279
+ },
280
+ async del(/** @type {string} */ key) {
281
+ return store.delete(key)
282
+ },
283
+ async has(/** @type {string} */ key) {
284
+ return store.has(key)
285
+ },
286
+ }
287
+ }
288
+ }
@@ -0,0 +1,40 @@
1
+ // @ts-check
2
+ import { {{Base}} } from 'mega-framework'
3
+
4
+ /**
5
+ * Mega{{Name}}Adapter — {{kind}} 어댑터(driver '{{driver}}'). 표준 lifecycle 5종 구현(ADR-045).
6
+ * 사용: `MegaAdapterRegistry.register('{{driver}}', Mega{{Name}}Adapter)` 등록 후 config 의
7
+ * `services.{{kind}}s.<key>.driver: '{{driver}}'` 로 인스턴스화된다(ADR-044).
8
+ */
9
+ export class Mega{{Name}}Adapter extends {{Base}} {
10
+ /** 자원 연결. 부팅 시 호출. */
11
+ async connect() {
12
+ throw new Error('Mega{{Name}}Adapter.connect: not implemented')
13
+ }
14
+
15
+ /** 자원 해제. 종료 시 호출. */
16
+ async disconnect() {
17
+ throw new Error('Mega{{Name}}Adapter.disconnect: not implemented')
18
+ }
19
+
20
+ /**
21
+ * 살아있는지 ping. `/health/ready` 등에서 활용.
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ async healthCheck() {
25
+ return false
26
+ }
27
+
28
+ /**
29
+ * 연결 풀·요청 수 등 운영 지표.
30
+ * @returns {object}
31
+ */
32
+ getStats() {
33
+ return {}
34
+ }
35
+
36
+ /** 어댑터별 native handle(모델·ctx 가 사용). */
37
+ get native() {
38
+ throw new Error('Mega{{Name}}Adapter.native: not implemented')
39
+ }
40
+ }
@@ -0,0 +1,13 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { {{Base}} } from 'mega-framework'
4
+ import { Mega{{Name}}Adapter } from '{{importPath}}'
5
+
6
+ describe('Mega{{Name}}Adapter', () => {
7
+ test('{{Base}} 를 상속하고 lifecycle 메서드를 가짐', () => {
8
+ expect(Object.getPrototypeOf(Mega{{Name}}Adapter)).toBe({{Base}})
9
+ for (const m of ['connect', 'disconnect', 'healthCheck', 'getStats']) {
10
+ expect(typeof Mega{{Name}}Adapter.prototype[m]).toBe('function')
11
+ }
12
+ })
13
+ })
@@ -0,0 +1,10 @@
1
+ // @ts-check
2
+ /**
3
+ * apps/{{name}}/app.config.js — App-only + Shared-Reference 스코프(ADR-061). 별명으로 전역 자원을
4
+ * 참조한다(예: `databases: { db: 'maindb' }` → ctx.db('db')). 활성화하려면 mega.config.js 의
5
+ * `apps` 배열에 '{{name}}' 을 추가한다(ADR-066).
6
+ */
7
+ export default {
8
+ name: '{{name}}',
9
+ hosts: ['{{name}}.localhost'],
10
+ }
@@ -0,0 +1,10 @@
1
+ // @ts-check
2
+ /**
3
+ * apps/{{name}} 기본 라우트 — 자동 로딩(loadRoutes) 대상.
4
+ */
5
+ export default (/** @type {any} */ router) => {
6
+ router.http.get('/', async (/** @type {any} */ _req, /** @type {any} */ _res, /** @type {any} */ _ctx) => {
7
+ // 핸들러는 raw data 만 반환한다 — 프레임워크가 `{ ok, data, meta }` envelope 로 자동 wrap(ADR-018).
8
+ return { app: '{{name}}' }
9
+ })
10
+ }
@@ -0,0 +1,13 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import routes from '{{importPath}}'
4
+
5
+ describe('{{name}} app index route', () => {
6
+ test('GET / 등록 + data 반환', async () => {
7
+ /** @type {Function|undefined} */
8
+ let handler
9
+ routes({ http: { get: (/** @type {string} */ _p, /** @type {Function} */ h) => void (handler = h) } })
10
+ expect(typeof handler).toBe('function')
11
+ expect(await /** @type {any} */ (handler)({}, {}, {})).toEqual({ app: '{{name}}' })
12
+ })
13
+ })
@@ -0,0 +1,38 @@
1
+ // @ts-check
2
+ import { MegaWebSocketController } from 'mega-framework'
3
+
4
+ /**
5
+ * {{Name}}Channel — WS 채널(ADR-074: WS 는 인스턴스+이벤트 디스패치라 베이스 유지). 메시지 `type` 이
6
+ * 점(`.`)을 포함하면 같은 이름의 메서드로 자동 디스패치된다(ADR-015) — 없으면 onMessage 폴백.
7
+ */
8
+ export class {{Name}}Channel extends MegaWebSocketController {
9
+ /**
10
+ * @param {any} _sock @param {any} ctx
11
+ */
12
+ async onConnect(_sock, ctx) {
13
+ ctx?.log?.debug?.({ user: ctx?.user?.id }, '{{camelName}}: connected')
14
+ }
15
+
16
+ /**
17
+ * @param {any} _sock @param {any} ctx
18
+ */
19
+ async onDisconnect(_sock, ctx) {
20
+ ctx?.log?.debug?.({ user: ctx?.user?.id }, '{{camelName}}: disconnected')
21
+ }
22
+
23
+ /**
24
+ * 메시지 type `{{camelName}}.message` 자동 디스패치. 핸들러는 `(sock, msg, ctx)` 를 받는다.
25
+ * @param {any} _sock @param {any} msg @param {any} ctx
26
+ */
27
+ async ['{{camelName}}.message'](_sock, msg, ctx) {
28
+ ctx.channel.broadcast({ type: '{{camelName}}.message', payload: msg.payload })
29
+ }
30
+
31
+ /**
32
+ * type 매칭 안 된 메시지 폴백.
33
+ * @param {any} _sock @param {any} msg @param {any} ctx
34
+ */
35
+ async onMessage(_sock, msg, ctx) {
36
+ ctx?.log?.warn?.({ type: msg?.type }, '{{camelName}}: unhandled message type')
37
+ }
38
+ }