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,19 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { MegaTest } from 'mega-framework/test'
4
+ import { {{Name}}Channel } from '{{importPath}}'
5
+
6
+ describe('{{Name}}Channel', () => {
7
+ test('{{camelName}}.message 디스패치 → broadcast', async () => {
8
+ const ch = new {{Name}}Channel()
9
+ const { sock, ctx } = MegaTest.makeWs({ user: { id: 1 } })
10
+ await ch.dispatch(sock, { type: '{{camelName}}.message', payload: { text: 'hi' } }, ctx)
11
+ expect(ctx.channel.broadcasts).toContainEqual({ type: '{{camelName}}.message', payload: { text: 'hi' } })
12
+ })
13
+
14
+ test('알 수 없는 type → onMessage 폴백(throw 없음)', async () => {
15
+ const ch = new {{Name}}Channel()
16
+ const { sock, ctx } = MegaTest.makeWs({})
17
+ await expect(ch.dispatch(sock, { type: 'nope' }, ctx)).resolves.toBeUndefined()
18
+ })
19
+ })
@@ -0,0 +1,16 @@
1
+ // @ts-check
2
+ /**
3
+ * {{Name}}Controller — HTTP 컨트롤러는 코어 베이스 클래스 없음(ADR-074). 모든 메서드 static,
4
+ * 단순 namespace 역할(this 미사용). 라우트에서 `router.http.get(path, {{Name}}Controller.index)` 로 참조.
5
+ */
6
+ export class {{Name}}Controller {
7
+ /**
8
+ * 목록 — ctx.services.{{camelName}}.list 위임. 반환값은 자동 envelope 로 감싸진다(ADR-018).
9
+ * @param {import('fastify').FastifyRequest} req
10
+ * @param {import('fastify').FastifyReply} _res
11
+ * @param {Record<string, any>} ctx
12
+ */
13
+ static async index(req, _res, ctx) {
14
+ return ctx.services.{{camelName}}.list(req.query)
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ // @ts-check
2
+ import { {{Name}}Controller } from '{{controllerImport}}'
3
+
4
+ /**
5
+ * apps/{{app}} {{version}} 라우트 — {{Name}}Controller 정적 메서드 참조 등록(ADR-069/074).
6
+ */
7
+ export default (/** @type {any} */ router) => {
8
+ router.http.get('/api/{{version}}/{{name}}', {{Name}}Controller.index)
9
+ }
@@ -0,0 +1,14 @@
1
+ // @ts-check
2
+ import { describe, test, expect, vi } from 'vitest'
3
+ import { MegaTest } from 'mega-framework/test'
4
+ import { {{Name}}Controller } from '{{importPath}}'
5
+
6
+ describe('{{Name}}Controller', () => {
7
+ test('index — ctx.services.{{camelName}}.list 에 위임', async () => {
8
+ const list = vi.fn(async () => [{ id: 1 }])
9
+ const { req, res, ctx } = MegaTest.makeHttp({ query: { page: '1' }, services: { {{camelName}}: { list } } })
10
+ const result = await {{Name}}Controller.index(req, res, ctx)
11
+ expect(result).toEqual([{ id: 1 }])
12
+ expect(list).toHaveBeenCalledWith({ page: '1' })
13
+ })
14
+ })
@@ -0,0 +1,23 @@
1
+ // @ts-check
2
+ import { MegaJob } from 'mega-framework'
3
+
4
+ /**
5
+ * {{Name}}Job — NATS subject 구독 잡(ADR-028). `mega worker` 프로세스가 소비한다. `run` 이 throw 하면
6
+ * `static retries`·backoff 로 재시도하고 한도 초과 시 DLQ 로 이동한다.
7
+ */
8
+ export class {{Name}}Job extends MegaJob {
9
+ static subject = '{{subject}}'
10
+ static bus = 'jobs'
11
+ static concurrency = 1
12
+ static retries = 3
13
+
14
+ /**
15
+ * @param {Record<string, any>} payload
16
+ * @param {Record<string, any>} ctx
17
+ * @returns {Promise<object>}
18
+ */
19
+ async run(payload, ctx) {
20
+ ctx.log?.info?.({ payload }, '{{camelName}}: run')
21
+ return { ok: true }
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { MegaJob } from 'mega-framework'
4
+ import { {{Name}}Job } from '{{importPath}}'
5
+
6
+ describe('{{Name}}Job', () => {
7
+ test('MegaJob 상속 + static subject/bus', () => {
8
+ expect(Object.getPrototypeOf({{Name}}Job)).toBe(MegaJob)
9
+ expect({{Name}}Job.subject).toBe('{{subject}}')
10
+ expect(typeof {{Name}}Job.bus).toBe('string')
11
+ })
12
+
13
+ test('run — payload 처리 후 결과 반환', async () => {
14
+ const job = new {{Name}}Job()
15
+ expect(await job.run({ a: 1 }, { log: { info() {} } })).toEqual({ ok: true })
16
+ })
17
+ })
@@ -0,0 +1,3 @@
1
+ {
2
+ "greeting": "{{Name}}"
3
+ }
@@ -0,0 +1,13 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { readFileSync } from 'node:fs'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ describe('{{ns}} locale ({{lng}})', () => {
7
+ test('유효한 JSON 객체', () => {
8
+ const path = fileURLToPath(new URL('{{importPath}}', import.meta.url))
9
+ const data = JSON.parse(readFileSync(path, 'utf8'))
10
+ expect(typeof data).toBe('object')
11
+ expect(data).not.toBeNull()
12
+ })
13
+ })
@@ -0,0 +1,13 @@
1
+ // @ts-check
2
+ /**
3
+ * {{camelName}} 미들웨어 (ADR-091 — before/transform/after 중 before 단계). 라우트 `before` 로 쓰면
4
+ * `(req, reply)`, 글로벌 미들웨어로 쓰면 `(req, reply, ctx)` 를 받는다(ADR-134). 양쪽 호환.
5
+ * @param {import('fastify').FastifyRequest} req
6
+ * @param {import('fastify').FastifyReply} _res
7
+ * @param {Record<string, any>} [ctx]
8
+ * @returns {Promise<void>}
9
+ */
10
+ export async function {{camelName}}(req, _res, ctx) {
11
+ const log = ctx?.log ?? req.log
12
+ log?.debug?.({ url: req.url }, '{{camelName}}: enter')
13
+ }
@@ -0,0 +1,11 @@
1
+ // @ts-check
2
+ import { describe, test, expect, vi } from 'vitest'
3
+ import { {{camelName}} } from '{{importPath}}'
4
+
5
+ describe('{{camelName}} 미들웨어', () => {
6
+ test('debug 로그 후 통과(throw 없음)', async () => {
7
+ const debug = vi.fn()
8
+ await {{camelName}}(/** @type {any} */ ({ url: '/x', log: { debug } }), /** @type {any} */ ({}), undefined)
9
+ expect(debug).toHaveBeenCalled()
10
+ })
11
+ })
@@ -0,0 +1,20 @@
1
+ // @ts-check
2
+ /**
3
+ * 마이그레이션 {{name}} ({{timestamp}}). up = 적용, down = 롤백. `mega migrate`(up 일괄)·
4
+ * `mega migrate:down`(마지막 1개 롤백)·`mega migrate:status`(목록)로 실행한다(ADR-149). 적용 이력은
5
+ * 대상 DB 의 `mega_migrations` 테이블이 추적하며, 각 마이그레이션은 트랜잭션으로 감싸 적용된다.
6
+ *
7
+ * @param {{ query: (sql: string, params?: any[]) => Promise<any> }} db - 대상 DB 어댑터(트랜잭션 내 query).
8
+ * @returns {Promise<void>}
9
+ */
10
+ export async function up(db) {
11
+ await db.query('-- TODO: {{name}} 적용(up) SQL')
12
+ }
13
+
14
+ /**
15
+ * @param {{ query: (sql: string, params?: any[]) => Promise<any> }} db
16
+ * @returns {Promise<void>}
17
+ */
18
+ export async function down(db) {
19
+ await db.query('-- TODO: {{name}} 롤백(down) SQL')
20
+ }
@@ -0,0 +1,14 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { up, down } from '{{importPath}}'
4
+
5
+ describe('migration {{name}}', () => {
6
+ test('up/down — db.query 호출', async () => {
7
+ /** @type {string[]} */
8
+ const sqls = []
9
+ const db = { query: async (/** @type {string} */ sql) => void sqls.push(sql) }
10
+ await up(db)
11
+ await down(db)
12
+ expect(sqls).toHaveLength(2)
13
+ })
14
+ })
@@ -0,0 +1,21 @@
1
+ // @ts-check
2
+ import { MegaModel } from 'mega-framework'
3
+
4
+ /**
5
+ * {{Name}} 모델 — 데이터 소스는 `static table` 로 통일(SQL table / Mongo collection, ADR-081).
6
+ * `static adapter` 는 mega.config.js 의 services.databases 키를 가리킨다(ADR-061).
7
+ */
8
+ export class {{Name}} extends MegaModel {
9
+ static adapter = 'primary'
10
+ static table = '{{table}}'
11
+
12
+ /**
13
+ * id 로 1건 조회. `this.query` 는 계측된 어댑터 query 위임(ADR-138).
14
+ * @param {string|number} id
15
+ * @returns {Promise<object|null>}
16
+ */
17
+ static async findById(id) {
18
+ const { rows } = await this.query('select * from {{table}} where id = $1', [id])
19
+ return rows[0] ?? null
20
+ }
21
+ }
@@ -0,0 +1,29 @@
1
+ // @ts-check
2
+ import { describe, test, expect } from 'vitest'
3
+ import { MegaModel } from 'mega-framework'
4
+ import { {{Name}} } from '{{importPath}}'
5
+
6
+ describe('{{Name}} model', () => {
7
+ test('MegaModel 을 상속하고 static table/adapter 설정됨', () => {
8
+ expect(Object.getPrototypeOf({{Name}})).toBe(MegaModel)
9
+ expect({{Name}}.table).toBe('{{table}}')
10
+ expect(typeof {{Name}}.adapter).toBe('string')
11
+ })
12
+
13
+ test('findById — 어댑터 query 에 위임', async () => {
14
+ const orig = {{Name}}.query
15
+ /** @type {any[]} */
16
+ const calls = []
17
+ {{Name}}.query = async (/** @type {string} */ sql, /** @type {any[]} */ params) => {
18
+ calls.push({ sql, params })
19
+ return { rows: [{ id: params[0] }] }
20
+ }
21
+ try {
22
+ const row = await {{Name}}.findById(7)
23
+ expect(row).toEqual({ id: 7 })
24
+ expect(calls[0].params).toEqual([7])
25
+ } finally {
26
+ {{Name}}.query = orig
27
+ }
28
+ })
29
+ })
@@ -0,0 +1,8 @@
1
+ // @ts-check
2
+ /**
3
+ * apps/main/app.config.js — App-only + Shared-Reference 스코프(ADR-061). 기본 앱 `main`.
4
+ */
5
+ export default {
6
+ name: 'main',
7
+ hosts: ['localhost', 'main.localhost'],
8
+ }
@@ -0,0 +1,37 @@
1
+ // @ts-check
2
+ /**
3
+ * apps/main/app.config.js — App-only + Shared-Reference 스코프(ADR-061). 기본 앱 `main`.
4
+ * EJS + ejs-mate 서버사이드 템플릿(ADR-011/136) + i18next 다국어(ADR-037/038/039) +
5
+ * @fastify/static 정적 자산(ADR-071) + Bootstrap 5 뷰(ADR-151)를 함께 켠 골격.
6
+ */
7
+ export default {
8
+ name: 'main',
9
+ hosts: ['localhost', 'main.localhost'],
10
+
11
+ // EJS + ejs-mate — `views.dir` 기준 레이아웃/파셜 lookup. 렌더 시 req.t/req.lang 자동 병합.
12
+ // views 옵트인이라 HTML 폼 제출(urlencoded)도 자동 파싱된다(ADR-151, @fastify/formbody).
13
+ views: {
14
+ dir: 'apps/main/views',
15
+ layoutDir: 'layouts',
16
+ partialsDir: 'partials',
17
+ },
18
+
19
+ // i18next 다국어 — locale 은 쿠키(`mega.lang`)로만 결정(ADR-038). `<localesDir>/<scope>/<lng>.json`
20
+ // 레이아웃(scope=server/client, ADR-039). `ctx.t()`/뷰 `t()` 는 server scope. navbar 의 언어 메뉴가
21
+ // 쿠키를 굽고 새로고침한다.
22
+ i18n: {
23
+ default: 'ko',
24
+ available: ['ko', 'en'],
25
+ fallback: 'en',
26
+ localesDir: 'apps/main/locales',
27
+ exposeTranslations: true,
28
+ },
29
+
30
+ // 정적 자산 — `${prefix}/<파일>` 로 디스크 서빙(prefix 디폴트 `/static`). Bootstrap 5 vendored +
31
+ // 브랜드 CSS/JS 가 `apps/main/public/` 아래에 있다. dotfiles 기본 차단.
32
+ staticAssets: {
33
+ enabled: true,
34
+ dir: 'apps/main/public',
35
+ prefix: '/static',
36
+ },
37
+ }
@@ -0,0 +1,10 @@
1
+ // PM2 ecosystem — server / scheduler / worker / ws-hub 프로세스 정의.
2
+ // 사용: pm2 start ecosystem.config.cjs (PM2 는 별도 설치: npm i -g pm2)
3
+ module.exports = {
4
+ apps: [
5
+ { name: '{{name}}-server', script: 'node_modules/.bin/mega', args: 'start', instances: 1, autorestart: true },
6
+ { name: '{{name}}-scheduler', script: 'node_modules/.bin/mega', args: 'scheduler', instances: 1, autorestart: true },
7
+ { name: '{{name}}-worker', script: 'node_modules/.bin/mega', args: 'worker', instances: 2, autorestart: true },
8
+ { name: '{{name}}-ws-hub', script: 'node_modules/.bin/mega-ws-hub', instances: 1, autorestart: true },
9
+ ],
10
+ }
@@ -0,0 +1,12 @@
1
+ # {{name}} 환경변수 (.env.example) — 복사해서 .env 로 쓰고 실제 값 채우기. .env 는 git 에 안 올림.
2
+
3
+ # HTTP 포트
4
+ PORT=3000
5
+
6
+ # 세션 서명 시크릿(세션 사용 시 필수) — 32바이트 이상 랜덤
7
+ # SESSION_SECRET=change-me
8
+
9
+ # 데이터베이스/캐시/버스 연결 (어댑터 사용 시)
10
+ # DATABASE_URL=postgres://user:pass@localhost:5432/{{name}}
11
+ # REDIS_URL=redis://localhost:6379
12
+ # NATS_URL=nats://localhost:4222
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ .env
3
+ .env.local
4
+ logs/
5
+ *.log
6
+ coverage/
7
+ storage/
8
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ {
2
+ "app_title": "{{name}}"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "app_title": "{{name}}"
3
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "welcome": "Welcome",
3
+ "nav_home": "Home",
4
+ "theme_toggle": "Toggle theme",
5
+ "footer_built": "Built with MEGA-FRAMEWORK · Bootstrap 5",
6
+ "hero_title": "Welcome to {{name}}",
7
+ "hero_subtitle": "A project built with MEGA-FRAMEWORK. EJS + ejs-mate server-side views, cookie-based i18n, and a Bootstrap 5 design come built in.",
8
+ "hero_cta_primary": "Framework docs",
9
+ "hero_cta_secondary": "Bootstrap docs",
10
+ "features_heading": "Built in",
11
+ "feature1_title": "Polished views",
12
+ "feature1_desc": "A Bootstrap 5 navbar, hero, and card grid make up the first screen.",
13
+ "feature2_title": "Language toggle",
14
+ "feature2_desc": "Switch Korean / English instantly from the navbar menu (cookie-based).",
15
+ "feature3_title": "Light & dark mode",
16
+ "feature3_desc": "Bootstrap 5.3 color modes toggle the theme and remember it in the browser."
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "welcome": "환영합니다",
3
+ "nav_home": "홈",
4
+ "theme_toggle": "테마 전환",
5
+ "footer_built": "MEGA-FRAMEWORK 로 제작 · Bootstrap 5",
6
+ "hero_title": "{{name}} 에 오신 걸 환영합니다",
7
+ "hero_subtitle": "MEGA-FRAMEWORK 로 만든 프로젝트입니다. EJS + ejs-mate 서버사이드 뷰, 쿠키 기반 다국어, Bootstrap 5 디자인이 기본 탑재되어 있습니다.",
8
+ "hero_cta_primary": "프레임워크 문서",
9
+ "hero_cta_secondary": "Bootstrap 문서",
10
+ "features_heading": "기본 탑재 기능",
11
+ "feature1_title": "세련된 뷰",
12
+ "feature1_desc": "Bootstrap 5 navbar · hero · 카드 그리드로 첫 화면이 구성됩니다.",
13
+ "feature2_title": "다국어 토글",
14
+ "feature2_desc": "navbar 의 언어 메뉴로 한국어/English 를 즉시 전환합니다(쿠키 기반).",
15
+ "feature3_title": "라이트·다크 모드",
16
+ "feature3_desc": "Bootstrap 5.3 color modes 로 테마를 전환하고 브라우저에 저장합니다."
17
+ }
@@ -0,0 +1,11 @@
1
+ // @ts-check
2
+ /**
3
+ * mega.config.js — Global-only 스코프(ADR-061). 활성 앱 whitelist·전역 자원(databases/caches/buses)·
4
+ * 서버 설정만 둔다. App-only 키(cors/helmet 등)는 apps/<name>/app.config.js 로.
5
+ */
6
+ export default {
7
+ apps: ['main'],
8
+ server: {
9
+ port: Number(process.env.PORT ?? 3000),
10
+ },
11
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "scripts": {
10
+ "dev": "mega start",
11
+ "start": "mega start",
12
+ "scheduler": "mega scheduler",
13
+ "worker": "mega worker",
14
+ "ws-hub": "mega-ws-hub",
15
+ "dev:all": "concurrently -n server,scheduler,worker -c blue,green,magenta \"mega start\" \"mega scheduler\" \"mega worker\"",
16
+ "test": "mega test"
17
+ },
18
+ "dependencies": {
19
+ "mega-framework": "^0.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "concurrently": "^9.0.0",
23
+ "vitest": "^4.0.0"
24
+ }
25
+ }
@@ -0,0 +1,101 @@
1
+ /*
2
+ * app.css — MEGA-FRAMEWORK 브랜드 소폭 커스텀(Bootstrap 5.3 위에 얹는다).
3
+ * Bootstrap 컴포넌트는 인스턴스 CSS 변수(--bs-btn-*, --bs-link-* 등)로 색을 잡으므로
4
+ * Sass 재컴파일 없이 변수만 덮어써 브랜드 색을 입힌다. per Bootstrap 5.3 CSS variables 문서.
5
+ */
6
+
7
+ :root {
8
+ --brand: #5b4bff;
9
+ --brand-rgb: 91, 75, 255;
10
+ --brand-dark: #4a3fd6;
11
+ --brand-light: #efedff;
12
+ }
13
+
14
+ /* 링크·primary 강조를 브랜드 색으로(라이트/다크 공통) */
15
+ a {
16
+ --bs-link-color-rgb: var(--brand-rgb);
17
+ --bs-link-hover-color-rgb: 74, 63, 214;
18
+ }
19
+
20
+ .btn-primary {
21
+ --bs-btn-bg: var(--brand);
22
+ --bs-btn-border-color: var(--brand);
23
+ --bs-btn-hover-bg: var(--brand-dark);
24
+ --bs-btn-hover-border-color: var(--brand-dark);
25
+ --bs-btn-active-bg: var(--brand-dark);
26
+ --bs-btn-active-border-color: var(--brand-dark);
27
+ --bs-btn-disabled-bg: var(--brand);
28
+ --bs-btn-disabled-border-color: var(--brand);
29
+ }
30
+
31
+ .btn-outline-primary {
32
+ --bs-btn-color: var(--brand);
33
+ --bs-btn-border-color: var(--brand);
34
+ --bs-btn-hover-bg: var(--brand);
35
+ --bs-btn-hover-border-color: var(--brand);
36
+ --bs-btn-active-bg: var(--brand);
37
+ --bs-btn-active-border-color: var(--brand);
38
+ }
39
+
40
+ .text-brand {
41
+ color: var(--brand) !important;
42
+ }
43
+
44
+ .navbar-brand {
45
+ font-weight: 700;
46
+ letter-spacing: -0.02em;
47
+ }
48
+
49
+ .navbar-brand .brand-dot {
50
+ color: var(--brand);
51
+ }
52
+
53
+ /* hero — 브랜드 그라데이션 배경 + 넉넉한 여백 */
54
+ .hero {
55
+ background: radial-gradient(120% 120% at 0% 0%, rgba(var(--brand-rgb), 0.12), transparent 60%),
56
+ radial-gradient(120% 120% at 100% 0%, rgba(var(--brand-rgb), 0.08), transparent 55%);
57
+ border-radius: 1rem;
58
+ }
59
+
60
+ .hero h1 {
61
+ letter-spacing: -0.03em;
62
+ }
63
+
64
+ /* feature 카드 — hover 살짝 떠오르는 인터랙션 */
65
+ .feature-card {
66
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
67
+ }
68
+
69
+ .feature-card:hover {
70
+ transform: translateY(-3px);
71
+ box-shadow: 0 0.75rem 1.5rem rgba(var(--brand-rgb), 0.12);
72
+ }
73
+
74
+ .feature-icon {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ width: 3rem;
79
+ height: 3rem;
80
+ border-radius: 0.75rem;
81
+ background: var(--brand-light);
82
+ color: var(--brand);
83
+ font-size: 1.5rem;
84
+ }
85
+
86
+ [data-bs-theme='dark'] .feature-icon {
87
+ background: rgba(var(--brand-rgb), 0.18);
88
+ }
89
+
90
+ footer.site-footer {
91
+ border-top: 1px solid var(--bs-border-color);
92
+ }
93
+
94
+ /* 테마 토글 아이콘 — 현재 테마에서 "전환 대상"을 보여준다(라이트면 🌙, 다크면 ☀️). */
95
+ [data-bs-theme='light'] .theme-icon-light {
96
+ display: none;
97
+ }
98
+
99
+ [data-bs-theme='dark'] .theme-icon-dark {
100
+ display: none;
101
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * app.js — 다크모드 토글 + 언어 토글 + 삭제 확인 모달 wiring(클라이언트 동작).
3
+ *
4
+ * helmet CSP(script-src 'self')가 인라인 스크립트를 막으므로 모든 클라 동작은 외부 파일로 둔다(ADR-153).
5
+ * 다크모드: 페인트 전 적용은 theme-init.js(<head>), 토글은 여기서. localStorage('mega.theme') 저장.
6
+ * 언어: 프레임워크가 locale 을 쿠키(mega.lang)로만 감지하므로(ADR-038), 토글은 쿠키를 굽고 새로고침.
7
+ * 삭제모달: 트리거 버튼의 data-action/data-name 으로 form action·문구를 채운다(#deleteModal 있을 때만).
8
+ */
9
+ ;(function () {
10
+ 'use strict'
11
+
12
+ /** @param {string} theme */
13
+ function applyTheme(theme) {
14
+ document.documentElement.setAttribute('data-bs-theme', theme)
15
+ try {
16
+ localStorage.setItem('mega.theme', theme)
17
+ } catch (e) {
18
+ // localStorage 차단(사생활 모드 등)은 비치명적 — 이번 페이지에서만 테마가 안 남을 뿐.
19
+ console.debug('theme persist skipped:', e && e.message)
20
+ }
21
+ }
22
+
23
+ document.querySelectorAll('[data-theme-toggle]').forEach(function (btn) {
24
+ btn.addEventListener('click', function () {
25
+ var current = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light'
26
+ applyTheme(current === 'dark' ? 'light' : 'dark')
27
+ })
28
+ })
29
+
30
+ document.querySelectorAll('[data-lang]').forEach(function (el) {
31
+ el.addEventListener('click', function (e) {
32
+ e.preventDefault()
33
+ var lang = el.getAttribute('data-lang')
34
+ // 1년 유지, 같은 사이트 요청에만 전송(samesite=lax).
35
+ document.cookie = 'mega.lang=' + encodeURIComponent(lang) + '; path=/; max-age=31536000; samesite=lax'
36
+ location.reload()
37
+ })
38
+ })
39
+
40
+ // 삭제 확인 모달 — 클릭된 행 버튼의 data-action(삭제 URL)·data-name 으로 form·문구를 채운다.
41
+ // #deleteModal 이 있는 페이지(예: 목록)에서만 동작하고, 없으면 no-op.
42
+ var deleteModal = document.getElementById('deleteModal')
43
+ if (deleteModal) {
44
+ deleteModal.addEventListener('show.bs.modal', function (event) {
45
+ var btn = event.relatedTarget
46
+ if (!btn) return
47
+ var form = document.getElementById('deleteForm')
48
+ var action = btn.getAttribute('data-action')
49
+ if (form && action) form.setAttribute('action', action)
50
+ var nameEl = document.getElementById('deleteModalName')
51
+ if (nameEl) nameEl.textContent = btn.getAttribute('data-name') || ''
52
+ })
53
+ }
54
+ })()
@@ -0,0 +1,12 @@
1
+ /*
2
+ * theme-init.js — 저장된 테마를 페인트 전에 적용(FOUC 방지). layout <head> 에서 동기 로드한다.
3
+ * helmet CSP(script-src 'self')가 인라인 스크립트를 막으므로 외부 파일로 둔다(ADR-153).
4
+ */
5
+ ;(function () {
6
+ try {
7
+ var t = localStorage.getItem('mega.theme')
8
+ if (t) document.documentElement.setAttribute('data-bs-theme', t)
9
+ } catch (e) {
10
+ // localStorage 차단(사생활 모드 등)은 비치명적 — 기본 테마로 렌더된다.
11
+ }
12
+ })()