mega-framework 0.1.5 → 0.1.7

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 (236) hide show
  1. package/bin/mega-ws-hub.js +2 -2
  2. package/package.json +32 -8
  3. package/sample/crud/.env +156 -8
  4. package/sample/crud/.env.example +153 -28
  5. package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
  6. package/sample/crud/.mega/journal/snapshot.json +261 -0
  7. package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
  8. package/sample/crud/apps/main/controllers/web-controller.js +7 -5
  9. package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
  10. package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
  11. package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
  12. package/sample/crud/apps/main/models/log-partition-model.js +105 -0
  13. package/sample/crud/apps/main/models/note-model.js +79 -0
  14. package/sample/crud/apps/main/models/user-level-model.js +24 -0
  15. package/sample/crud/apps/main/models/user-model.js +146 -0
  16. package/sample/crud/apps/main/models/user-type-model.js +21 -0
  17. package/sample/crud/apps/main/models/wallet-model.js +24 -0
  18. package/sample/crud/apps/main/routes/users.js +55 -10
  19. package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
  20. package/sample/crud/apps/main/services/auth-service.js +39 -24
  21. package/sample/crud/apps/main/services/log-partition-service.js +101 -0
  22. package/sample/crud/apps/main/services/note-service.js +6 -6
  23. package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
  24. package/sample/crud/apps/main/services/user-service.js +62 -21
  25. package/sample/crud/apps/main/views/auth/login.ejs +6 -6
  26. package/sample/crud/apps/main/views/auth/register.ejs +46 -5
  27. package/sample/crud/apps/main/views/users/edit.ejs +42 -5
  28. package/sample/crud/apps/main/views/users/list.ejs +6 -2
  29. package/sample/crud/apps/main/views/users/new.ejs +56 -4
  30. package/sample/crud/docs/log_partition_design.mm.md +23 -0
  31. package/sample/crud/mega.config.js +63 -3
  32. package/sample/crud/package.json +3 -3
  33. package/sample/crud/scripts/start-ws-hub.sh +2 -2
  34. package/sample/simple/package.json +2 -2
  35. package/src/adapters/adapter-manager.js +2 -1
  36. package/src/adapters/adapter-options.js +30 -0
  37. package/src/adapters/maria-adapter.js +26 -3
  38. package/src/adapters/mega-db-adapter.js +7 -1
  39. package/src/adapters/mongo-adapter.js +19 -1
  40. package/src/adapters/postgres-adapter.js +25 -2
  41. package/src/adapters/sqlite-adapter.js +20 -1
  42. package/src/cli/commands/new.js +13 -3
  43. package/src/cli/commands/scaffold.js +137 -33
  44. package/src/cli/generators/index.js +82 -2
  45. package/src/cli/index.js +478 -104
  46. package/src/core/ajv-mapper.js +27 -2
  47. package/src/core/boot.js +485 -237
  48. package/src/core/cluster-metrics.js +13 -4
  49. package/src/core/config-validator.js +25 -0
  50. package/src/core/ctx-builder.js +6 -2
  51. package/src/core/envelope.js +112 -12
  52. package/src/core/hub-link.js +65 -4
  53. package/src/core/i18n.js +11 -1
  54. package/src/core/index.js +6 -2
  55. package/src/core/mega-app.js +223 -481
  56. package/src/core/mega-cluster.js +54 -13
  57. package/src/core/mega-server.js +40 -9
  58. package/src/core/migration/dialect-registry.js +107 -0
  59. package/src/core/migration/dialects/README.md +62 -0
  60. package/src/core/migration/dialects/maria.js +496 -0
  61. package/src/core/migration/dialects/mongo.js +824 -0
  62. package/src/core/migration/dialects/postgres.js +563 -0
  63. package/src/core/migration/dialects/sqlite.js +476 -0
  64. package/src/core/migration/differ.js +456 -0
  65. package/src/core/migration/generate.js +508 -0
  66. package/src/core/migration/journal.js +167 -0
  67. package/src/core/migration/model-scan.js +84 -0
  68. package/src/core/migration/mongo-migration-db.js +97 -0
  69. package/src/core/migration/schema-builder.js +400 -0
  70. package/src/core/migration/schema-validator.js +315 -0
  71. package/src/core/migration-lock.js +205 -0
  72. package/src/core/migration-runner.js +166 -38
  73. package/src/core/multipart.js +28 -5
  74. package/src/core/pipeline.js +129 -0
  75. package/src/core/router.js +70 -65
  76. package/src/core/scope-registry.js +0 -1
  77. package/src/core/security.js +67 -9
  78. package/src/core/workers-manager.js +12 -1
  79. package/src/core/ws-cluster.js +10 -3
  80. package/src/core/ws-message.js +48 -4
  81. package/src/core/ws-presence.js +624 -0
  82. package/src/core/ws-roster.js +4 -1
  83. package/src/core/ws-upgrade.js +118 -12
  84. package/src/index.js +1 -1
  85. package/src/lib/hub-protocol.js +29 -0
  86. package/src/lib/mega-health.js +25 -4
  87. package/src/lib/mega-job-queue.js +98 -21
  88. package/src/lib/mega-job.js +29 -0
  89. package/src/lib/mega-logger.js +1 -1
  90. package/src/lib/mega-metrics.js +3 -12
  91. package/src/lib/mega-plugin.js +34 -3
  92. package/src/lib/mega-schedule.js +40 -22
  93. package/src/lib/mega-shutdown.js +162 -49
  94. package/src/lib/mega-tracing.js +66 -19
  95. package/src/lib/mega-worker.js +5 -1
  96. package/src/lib/otel-resource.js +36 -0
  97. package/src/{cli → lib}/ws-hub.js +51 -8
  98. package/src/models/crud-sql-builder.js +133 -0
  99. package/src/models/mega-model.js +82 -2
  100. package/src/models/model-crud.js +483 -0
  101. package/src/models/mongo-crud.js +285 -0
  102. package/templates/model/code-mongo.tpl +35 -0
  103. package/templates/model/code.tpl +15 -1
  104. package/templates/model/test-mongo.tpl +38 -0
  105. package/templates/model/test.tpl +4 -0
  106. package/types/adapters/adapter-manager.d.ts +95 -0
  107. package/types/adapters/adapter-options.d.ts +91 -0
  108. package/types/adapters/file-adapter.d.ts +94 -0
  109. package/types/adapters/file-session-adapter.d.ts +101 -0
  110. package/types/adapters/index.d.ts +20 -0
  111. package/types/adapters/maria-adapter.d.ts +115 -0
  112. package/types/adapters/mega-adapter.d.ts +215 -0
  113. package/types/adapters/mega-bus-adapter.d.ts +45 -0
  114. package/types/adapters/mega-cache-adapter.d.ts +47 -0
  115. package/types/adapters/mega-db-adapter.d.ts +47 -0
  116. package/types/adapters/mega-lock-adapter.d.ts +62 -0
  117. package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
  118. package/types/adapters/mega-session-adapter.d.ts +32 -0
  119. package/types/adapters/mongo-adapter.d.ts +139 -0
  120. package/types/adapters/nats-adapter.d.ts +108 -0
  121. package/types/adapters/postgres-adapter.d.ts +139 -0
  122. package/types/adapters/redis-adapter.d.ts +70 -0
  123. package/types/adapters/redis-session-adapter.d.ts +82 -0
  124. package/types/adapters/redlock-adapter.d.ts +149 -0
  125. package/types/adapters/registry.d.ts +46 -0
  126. package/types/adapters/sqlite-adapter.d.ts +106 -0
  127. package/types/auth/index.d.ts +24 -0
  128. package/types/cli/commands/console-cmd.d.ts +37 -0
  129. package/types/cli/commands/new.d.ts +16 -0
  130. package/types/cli/commands/routes.d.ts +36 -0
  131. package/types/cli/commands/scaffold.d.ts +78 -0
  132. package/types/cli/commands/test-cmd.d.ts +14 -0
  133. package/types/cli/generators/index.d.ts +112 -0
  134. package/types/cli/index.d.ts +249 -0
  135. package/types/cli/template-engine.d.ts +40 -0
  136. package/types/core/ajv-mapper.d.ts +27 -0
  137. package/types/core/boot.d.ts +233 -0
  138. package/types/core/cluster-metrics.d.ts +52 -0
  139. package/types/core/config-loader.d.ts +13 -0
  140. package/types/core/config-validator.d.ts +30 -0
  141. package/types/core/ctx-builder.d.ts +80 -0
  142. package/types/core/envelope.d.ts +79 -0
  143. package/types/core/error-mapper.d.ts +17 -0
  144. package/types/core/formbody.d.ts +41 -0
  145. package/types/core/hub-link.d.ts +264 -0
  146. package/types/core/i18n.d.ts +178 -0
  147. package/types/core/index.d.ts +28 -0
  148. package/types/core/mega-app.d.ts +529 -0
  149. package/types/core/mega-cluster.d.ts +104 -0
  150. package/types/core/mega-server.d.ts +91 -0
  151. package/types/core/mega-service.d.ts +31 -0
  152. package/types/core/migration/dialect-registry.d.ts +22 -0
  153. package/types/core/migration/dialects/maria.d.ts +99 -0
  154. package/types/core/migration/dialects/mongo.d.ts +89 -0
  155. package/types/core/migration/dialects/postgres.d.ts +117 -0
  156. package/types/core/migration/dialects/sqlite.d.ts +111 -0
  157. package/types/core/migration/differ.d.ts +47 -0
  158. package/types/core/migration/generate.d.ts +56 -0
  159. package/types/core/migration/journal.d.ts +52 -0
  160. package/types/core/migration/model-scan.d.ts +19 -0
  161. package/types/core/migration/mongo-migration-db.d.ts +7 -0
  162. package/types/core/migration/schema-builder.d.ts +197 -0
  163. package/types/core/migration/schema-validator.d.ts +20 -0
  164. package/types/core/migration-lock.d.ts +33 -0
  165. package/types/core/migration-runner.d.ts +101 -0
  166. package/types/core/multipart.d.ts +86 -0
  167. package/types/core/openapi.d.ts +62 -0
  168. package/types/core/pipeline.d.ts +92 -0
  169. package/types/core/router.d.ts +159 -0
  170. package/types/core/routes-loader.d.ts +21 -0
  171. package/types/core/scope-registry.d.ts +14 -0
  172. package/types/core/security.d.ts +77 -0
  173. package/types/core/services-loader.d.ts +27 -0
  174. package/types/core/session-cleanup-schedule.d.ts +19 -0
  175. package/types/core/session-store.d.ts +18 -0
  176. package/types/core/session.d.ts +77 -0
  177. package/types/core/static-assets.d.ts +73 -0
  178. package/types/core/template.d.ts +106 -0
  179. package/types/core/workers-manager.d.ts +79 -0
  180. package/types/core/ws-cluster.d.ts +208 -0
  181. package/types/core/ws-compression.d.ts +112 -0
  182. package/types/core/ws-controller.d.ts +65 -0
  183. package/types/core/ws-message.d.ts +106 -0
  184. package/types/core/ws-presence.d.ts +273 -0
  185. package/types/core/ws-roster.d.ts +96 -0
  186. package/types/core/ws-upgrade.d.ts +231 -0
  187. package/types/errors/config-error.d.ts +10 -0
  188. package/types/errors/http-errors.d.ts +120 -0
  189. package/types/errors/index.d.ts +3 -0
  190. package/types/errors/mega-error.d.ts +32 -0
  191. package/types/index.d.ts +39 -0
  192. package/types/lib/asp/config.d.ts +49 -0
  193. package/types/lib/asp/crypto.d.ts +43 -0
  194. package/types/lib/asp/errors.d.ts +30 -0
  195. package/types/lib/asp/nonce-cache.d.ts +52 -0
  196. package/types/lib/asp/plugin.d.ts +30 -0
  197. package/types/lib/asp/ws-terminator.d.ts +45 -0
  198. package/types/lib/env-mapper.d.ts +14 -0
  199. package/types/lib/hub-protocol.d.ts +106 -0
  200. package/types/lib/index.d.ts +22 -0
  201. package/types/lib/logger/telegram-core.d.ts +104 -0
  202. package/types/lib/logger/telegram-transport.d.ts +45 -0
  203. package/types/lib/mega-brute-force.d.ts +66 -0
  204. package/types/lib/mega-circuit-breaker.d.ts +241 -0
  205. package/types/lib/mega-cron.d.ts +66 -0
  206. package/types/lib/mega-hash.d.ts +32 -0
  207. package/types/lib/mega-health.d.ts +41 -0
  208. package/types/lib/mega-job-queue.d.ts +176 -0
  209. package/types/lib/mega-job-worker.d.ts +130 -0
  210. package/types/lib/mega-job.d.ts +138 -0
  211. package/types/lib/mega-logger.d.ts +45 -0
  212. package/types/lib/mega-metrics.d.ts +285 -0
  213. package/types/lib/mega-plugin.d.ts +245 -0
  214. package/types/lib/mega-retry.d.ts +85 -0
  215. package/types/lib/mega-schedule.d.ts +260 -0
  216. package/types/lib/mega-shutdown.d.ts +135 -0
  217. package/types/lib/mega-tracing.d.ts +224 -0
  218. package/types/lib/mega-worker.d.ts +127 -0
  219. package/types/lib/otel-resource.d.ts +16 -0
  220. package/types/lib/worker-runner/process-entry.d.ts +1 -0
  221. package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
  222. package/types/lib/worker-runner/thread-entry.d.ts +1 -0
  223. package/types/lib/ws-hub.d.ts +234 -0
  224. package/types/models/crud-sql-builder.d.ts +48 -0
  225. package/types/models/index.d.ts +1 -0
  226. package/types/models/mega-model.d.ts +138 -0
  227. package/types/models/model-crud.d.ts +82 -0
  228. package/types/models/mongo-crud.d.ts +59 -0
  229. package/types/test/index.d.ts +84 -0
  230. package/.env +0 -127
  231. package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
  232. package/sample/crud/apps/main/models/note.js +0 -71
  233. package/sample/crud/apps/main/models/user.js +0 -86
  234. package/sample/crud/package-lock.json +0 -5665
  235. package/sample/crud/yarn.lock +0 -2142
  236. package/sample/simple/package-lock.json +0 -1851
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 모델의 schema record(컬럼·PK·unique)를 1회 평가·메모이즈한다(ADR-204 `buildModelRecord` 재사용).
3
+ * `static schema` 없으면 `model.crud_requires_schema`.
4
+ * @param {any} Model @returns {ModelMeta}
5
+ */
6
+ export function getModelMeta(Model: any): ModelMeta;
7
+ /**
8
+ * Model 의 어댑터 driver 로 SQL dialect(DML facet)를 해석한다. mongo·DML facet 미구현 driver 는 throw.
9
+ * @param {any} Model @returns {{ dialect: any, driver: string }}
10
+ */
11
+ export function getCrudDialect(Model: any): {
12
+ dialect: any;
13
+ driver: string;
14
+ };
15
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
16
+ export function findOne(Model: any, filter: Record<string, any>, opts?: {
17
+ select?: string[];
18
+ }): Promise<any | null>;
19
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[], orderBy?: any, limit?: number, offset?: number }} [opts] @returns {Promise<any[]>} */
20
+ export function findMany(Model: any, filter: Record<string, any>, opts?: {
21
+ select?: string[];
22
+ orderBy?: any;
23
+ limit?: number;
24
+ offset?: number;
25
+ }): Promise<any[]>;
26
+ /** @param {any} Model @param {any} id @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
27
+ export function findById(Model: any, id: any, opts?: {
28
+ select?: string[];
29
+ }): Promise<any | null>;
30
+ /** @param {any} Model @param {Record<string, any>} [filter] @returns {Promise<number>} */
31
+ export function count(Model: any, filter?: Record<string, any>): Promise<number>;
32
+ /** @param {any} Model @param {Record<string, any>} filter @returns {Promise<boolean>} */
33
+ export function exists(Model: any, filter: Record<string, any>): Promise<boolean>;
34
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[], orderBy?: any, limit: number, offset?: number, withTotal?: boolean }} opts @returns {Promise<{ rows: any[], limit: number, offset: number, total?: number }>} */
35
+ export function paginate(Model: any, filter: Record<string, any>, opts: {
36
+ select?: string[];
37
+ orderBy?: any;
38
+ limit: number;
39
+ offset?: number;
40
+ withTotal?: boolean;
41
+ }): Promise<{
42
+ rows: any[];
43
+ limit: number;
44
+ offset: number;
45
+ total?: number;
46
+ }>;
47
+ /** @param {any} Model @param {Record<string, any>} data @param {{ returning?: boolean }} [opts] @returns {Promise<any>} */
48
+ export function insertOne(Model: any, data: Record<string, any>, opts?: {
49
+ returning?: boolean;
50
+ }): Promise<any>;
51
+ /** @param {any} Model @param {Record<string, any>[]} rows @param {{ returning?: boolean }} [opts] @returns {Promise<{ count: number } | any[]>} */
52
+ export function insertMany(Model: any, rows: Record<string, any>[], opts?: {
53
+ returning?: boolean;
54
+ }): Promise<{
55
+ count: number;
56
+ } | any[]>;
57
+ /** @param {any} Model @param {Record<string, any>} filter @param {Record<string, any>} patch @returns {Promise<number>} */
58
+ export function updateOne(Model: any, filter: Record<string, any>, patch: Record<string, any>): Promise<number>;
59
+ /** @param {any} Model @param {Record<string, any>} filter @param {Record<string, any>} patch @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
60
+ export function updateMany(Model: any, filter: Record<string, any>, patch: Record<string, any>, opts?: {
61
+ all?: boolean;
62
+ }): Promise<number>;
63
+ /** @param {any} Model @param {Record<string, any>} filter @returns {Promise<number>} */
64
+ export function deleteOne(Model: any, filter: Record<string, any>): Promise<number>;
65
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
66
+ export function deleteMany(Model: any, filter: Record<string, any>, opts?: {
67
+ all?: boolean;
68
+ }): Promise<number>;
69
+ /** @param {any} Model @param {Record<string, any>} data @param {{ conflict: string[], update?: string[], returning?: boolean }} opts @returns {Promise<any>} */
70
+ export function upsert(Model: any, data: Record<string, any>, opts: {
71
+ conflict: string[];
72
+ update?: string[];
73
+ returning?: boolean;
74
+ }): Promise<any>;
75
+ export type ModelMeta = {
76
+ record: any;
77
+ cols: Set<string>;
78
+ pk: string | null;
79
+ pkCols: string[];
80
+ uniques: Set<string>;
81
+ table: string;
82
+ };
@@ -0,0 +1,59 @@
1
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
2
+ export function findOne(Model: any, filter: Record<string, any>, opts?: {
3
+ select?: string[];
4
+ }): Promise<any | null>;
5
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[], orderBy?: any, limit?: number, offset?: number }} [opts] @returns {Promise<any[]>} */
6
+ export function findMany(Model: any, filter: Record<string, any>, opts?: {
7
+ select?: string[];
8
+ orderBy?: any;
9
+ limit?: number;
10
+ offset?: number;
11
+ }): Promise<any[]>;
12
+ /** @param {any} Model @param {any} id @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
13
+ export function findById(Model: any, id: any, opts?: {
14
+ select?: string[];
15
+ }): Promise<any | null>;
16
+ /** @param {any} Model @param {Record<string, any>} [filter] @returns {Promise<number>} */
17
+ export function count(Model: any, filter?: Record<string, any>): Promise<number>;
18
+ /** @param {any} Model @param {Record<string, any>} filter @returns {Promise<boolean>} */
19
+ export function exists(Model: any, filter: Record<string, any>): Promise<boolean>;
20
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ select?: string[], orderBy?: any, limit: number, offset?: number, withTotal?: boolean }} opts @returns {Promise<{ rows: any[], limit: number, offset: number, total?: number }>} */
21
+ export function paginate(Model: any, filter: Record<string, any>, opts: {
22
+ select?: string[];
23
+ orderBy?: any;
24
+ limit: number;
25
+ offset?: number;
26
+ withTotal?: boolean;
27
+ }): Promise<{
28
+ rows: any[];
29
+ limit: number;
30
+ offset: number;
31
+ total?: number;
32
+ }>;
33
+ /** @param {any} Model @param {Record<string, any>} data @param {{ returning?: boolean }} [opts] @returns {Promise<any>} */
34
+ export function insertOne(Model: any, data: Record<string, any>, opts?: {
35
+ returning?: boolean;
36
+ }): Promise<any>;
37
+ /** @param {any} Model @param {Record<string, any>[]} rows @param {{ returning?: boolean }} [opts] @returns {Promise<{ count: number } | any[]>} */
38
+ export function insertMany(Model: any, rows: Record<string, any>[], opts?: {
39
+ returning?: boolean;
40
+ }): Promise<{
41
+ count: number;
42
+ } | any[]>;
43
+ /** @param {any} Model @param {Record<string, any>} filter @param {Record<string, any>} patch @returns {Promise<number>} */
44
+ export function updateOne(Model: any, filter: Record<string, any>, patch: Record<string, any>): Promise<number>;
45
+ /** @param {any} Model @param {Record<string, any>} filter @param {Record<string, any>} patch @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
46
+ export function updateMany(Model: any, filter: Record<string, any>, patch: Record<string, any>, opts?: {
47
+ all?: boolean;
48
+ }): Promise<number>;
49
+ /** @param {any} Model @param {Record<string, any>} filter @returns {Promise<number>} */
50
+ export function deleteOne(Model: any, filter: Record<string, any>): Promise<number>;
51
+ /** @param {any} Model @param {Record<string, any>} filter @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
52
+ export function deleteMany(Model: any, filter: Record<string, any>, opts?: {
53
+ all?: boolean;
54
+ }): Promise<number>;
55
+ /** @param {any} Model @param {Record<string, any>} data @param {{ conflict: string[], returning?: boolean }} opts @returns {Promise<any>} */
56
+ export function upsert(Model: any, data: Record<string, any>, opts: {
57
+ conflict: string[];
58
+ returning?: boolean;
59
+ }): Promise<any>;
@@ -0,0 +1,84 @@
1
+ export class MegaTest {
2
+ /**
3
+ * HTTP 액션 단위 테스트용 fixture (03-api-spec §12). 정적 메서드 컨트롤러·인라인 핸들러를
4
+ * `(req, res, ctx)` 로 직접 호출해 검증할 수 있다.
5
+ *
6
+ * @param {object} [opts]
7
+ * @param {Record<string, any>} [opts.params] - 경로 파라미터(`req.params`).
8
+ * @param {Record<string, any>} [opts.query] - 쿼리스트링(`req.query`).
9
+ * @param {any} [opts.body] - 요청 본문(`req.body`).
10
+ * @param {Record<string, any>} [opts.headers] - 요청 헤더(`req.headers`).
11
+ * @param {Record<string, any>} [opts.services] - `ctx.services` 로 주입할 서비스 mock 맵.
12
+ * @param {any} [opts.user] - 인증 사용자(`req.user`/`ctx.user`). 가드 우회 단위 테스트용.
13
+ * @param {Record<string, any>|null} [opts.session] - `req.session`/`ctx.session` 객체.
14
+ * @param {string} [opts.method] - HTTP 메서드(기본 'GET').
15
+ * @param {string} [opts.url] - 요청 경로(기본 '/').
16
+ * @param {string} [opts.requestId] - `ctx.requestId`/`req.id`(기본 'test-req').
17
+ * @param {any} [opts.app] - `ctx.app`(기본 null).
18
+ * @param {any} [opts.log] - 로거(기본 silent).
19
+ * @param {Record<string, any>} [opts.db] - `ctx.db(alias)` mock 맵.
20
+ * @param {Record<string, any>} [opts.cache] - `ctx.cache(alias)` mock 맵.
21
+ * @param {Record<string, any>} [opts.bus] - `ctx.bus(alias)` mock 맵.
22
+ * @param {Record<string, any>} [opts.lock] - `ctx.lock(alias)` mock 맵.
23
+ * @returns {{ req: any, res: any, ctx: any }} 핸들러에 그대로 넘기는 fixture(느슨한 타입 — 테스트 편의).
24
+ */
25
+ static makeHttp({ params, query, body, headers, services, user, session, method, url, requestId, app, log, db, cache, bus, lock, }?: {
26
+ params?: Record<string, any>;
27
+ query?: Record<string, any>;
28
+ body?: any;
29
+ headers?: Record<string, any>;
30
+ services?: Record<string, any>;
31
+ user?: any;
32
+ session?: Record<string, any> | null;
33
+ method?: string;
34
+ url?: string;
35
+ requestId?: string;
36
+ app?: any;
37
+ log?: any;
38
+ db?: Record<string, any>;
39
+ cache?: Record<string, any>;
40
+ bus?: Record<string, any>;
41
+ lock?: Record<string, any>;
42
+ }): {
43
+ req: any;
44
+ res: any;
45
+ ctx: any;
46
+ };
47
+ /**
48
+ * WS 채널 단위 테스트용 fixture (03-api-spec §12). 채널 메서드(`async join(sock, payload, ctx)` 등)를
49
+ * 직접 호출해 검증한다. `ctx.channel` 은 broadcast/directToUser 를 기록하는 stub.
50
+ *
51
+ * @param {object} [opts]
52
+ * @param {any} [opts.user] - 인증 사용자(`ctx.user`).
53
+ * @param {Record<string, any>} [opts.services] - `ctx.services` mock 맵.
54
+ * @param {Record<string, any>|null} [opts.session] - `ctx.session`.
55
+ * @param {{ id?: string, metadata?: object }} [opts.socket] - 소켓 초기값.
56
+ * @param {any} [opts.log] - 로거(기본 silent).
57
+ * @returns {{ sock: any, ctx: any }} 채널 메서드에 넘기는 fixture(느슨한 타입 — 테스트 편의).
58
+ */
59
+ static makeWs({ user, services, session, socket, log }?: {
60
+ user?: any;
61
+ services?: Record<string, any>;
62
+ session?: Record<string, any> | null;
63
+ socket?: {
64
+ id?: string;
65
+ metadata?: object;
66
+ };
67
+ log?: any;
68
+ }): {
69
+ sock: any;
70
+ ctx: any;
71
+ };
72
+ /**
73
+ * in-memory mock bus — `publish` 호출을 기록하고 `subscribe` 핸들러로 즉시 dispatch 한다. 실 NATS
74
+ * 없이 잡/이벤트 발행 검증용.
75
+ * @returns {Record<string, any>}
76
+ */
77
+ static mockBus(): Record<string, any>;
78
+ /**
79
+ * in-memory mock cache — Map 백엔드 `get/set/del/has`. TTL 인자는 받지만 만료는 흉내 내지 않는다
80
+ * (단위 테스트는 만료 동작이 아니라 호출·값을 검증).
81
+ * @returns {Record<string, any>}
82
+ */
83
+ static mockCache(): Record<string, any>;
84
+ }
package/.env DELETED
@@ -1,127 +0,0 @@
1
- # Environment
2
- NODE_ENV=development
3
-
4
- # Server
5
- # 세션 쿠키 HMAC 키 — ≥32자 필수(config-validator M-1). dev/test 전용. ⚠️ 운영은 별도 시크릿으로 교체.
6
- SESSION_SECRET=Zz4VoSzf0sYMEoqASu8G_wx5l3uKi2MlHsxDK3MSkoE
7
- # cluster 워커 프로세스 수(ADR-154) — 정수 N 또는 max(CPU 코어 수). 미설정/1=단일 프로세스.
8
- # MEGA_CLUSTER_WORKERS=max
9
-
10
- # ─────────────────────────────────────────────────────────────────────
11
- # Docker 통합 테스트 인프라 제어 (ADR-103) — docker-compose.yml 이 읽음
12
- # 모두 dev/test 전용. ⚠️ 운영 환경 사용 금지.
13
- # 미설정 시 docker-compose.yml 의 인라인 디폴트가 그대로 적용됨.
14
- # 네이밍: MEGA_<SERVICE>_<FIELD> (코드의 MEGA_WSHUB_* 패턴과 정합)
15
- # ─────────────────────────────────────────────────────────────────────
16
- # Postgres (Step 4)
17
- MEGA_PG_PORT=5432
18
- MEGA_PG_USER=mega
19
- MEGA_PG_PASSWORD=dkTkqkfl12
20
- MEGA_PG_DB=mega_test
21
- # MariaDB (Step 4)
22
- MEGA_MARIA_PORT=3306
23
- MEGA_MARIA_ROOT_PASSWORD=dkTkqkfl12
24
- MEGA_MARIA_USER=mega
25
- MEGA_MARIA_PASSWORD=dkTkqkfl12
26
- MEGA_MARIA_DB=mega_test
27
- # MongoDB (Step 5)
28
- MEGA_MONGO_PORT=27017
29
- MEGA_MONGO_USER=mega
30
- MEGA_MONGO_PASSWORD=dkTkqkfl12
31
- MEGA_MONGO_DB=mega_test
32
- # Redis (Step 6)
33
- MEGA_REDIS_PORT=6379
34
- MEGA_REDIS_PASSWORD=dkTkqkfl12
35
- # NATS (Step 6)
36
- MEGA_NATS_PORT=4222
37
- MEGA_NATS_MONITOR_PORT=8222
38
- # 컨테이너 restart 정책: 개발=unless-stopped / CI 일회성=no
39
- MEGA_INFRA_RESTART=unless-stopped
40
-
41
- # ─────────────────────────────────────────────────────────────────────
42
- # 연결 문자열 (services config 가 읽음) — 위 docker 디폴트와 정합
43
- # 네이밍: <SERVICE>_URL (prefix 없는 services-키 패턴)
44
- # ─────────────────────────────────────────────────────────────────────
45
- # Database (services.databases.*)
46
- PG_URL=postgres://mega:dkTkqkfl12@localhost:5432/mega_test
47
- MARIA_URL=mariadb://mega:dkTkqkfl12@localhost:3306/mega_test
48
- MONGO_URL=mongodb://mega:dkTkqkfl12@localhost:27017/mega_test?authSource=admin
49
-
50
- # Redis (services.caches.*)
51
- REDIS_SESSION_URL=redis://:dkTkqkfl12@localhost:6379/0
52
- REDIS_CACHE_URL=redis://:dkTkqkfl12@localhost:6379/1
53
- REDIS_RATE_URL=redis://:dkTkqkfl12@localhost:6379/2
54
-
55
- # NATS (services.buses.*)
56
- NATS_EVENTS_URL=nats://localhost:4222
57
- NATS_JOBS_URL=nats://localhost:4222
58
-
59
- # ─────────────────────────────────────────────────────────────────────
60
- # 어댑터 옵션 자동 매핑 (ADR-109, 12-factor) — services.<domain>.<key>.envPrefix 지정 시
61
- # MegaAdapterManager 가 buildAdapterEnvConfig 로 읽어 어댑터 옵션에 병합(env 우선).
62
- # grammar: MEGA_<SERVICE>_<KEY>
63
- # URL → url
64
- # HOST/PORT/USER/PASSWORD → 연결필드 (PORT 만 정수, 나머지 항상 문자열)
65
- # DATABASE | DB → database / DBNAME → dbName (Mongo)
66
- # POOL_<X> → pool.{camelCase(X)} (driver 무관 공통 풀 인터페이스, 값 자동 타입변환)
67
- # OPTIONS_<X> → options.{driver별 키} (driver-aware, 아래 표 — 값 자동 타입변환)
68
- # ⚠️ 위 MEGA_<SERVICE>_PORT/USER/PASSWORD/DB 는 docker-compose(ADR-103)와 namespace 공유.
69
- # grammar 불일치 키(예: MEGA_MARIA_ROOT_PASSWORD)는 어댑터 매핑에서 무시됨.
70
- # # OPTIONS_* driver별 키 표기 (ADR-109 보강, Step 6 QA H-1 — .env 는 UPPER_SNAKE 로 통일해 적되
71
- # # driver 가 인식하는 표기로 자동 변환됨):
72
- # # redis/nats/mongodb/mariadb = camelCase (KEY_PREFIX→keyPrefix, MAX_RECONNECT_ATTEMPTS→maxReconnectAttempts,
73
- # # SERVER_SELECTION_TIMEOUT_MS→serverSelectionTimeoutMS[MS 보존])
74
- # # postgres/sqlite/file = snake_case (STATEMENT_TIMEOUT→statement_timeout)
75
- # ─────────────────────────────────────────────────────────────────────
76
- # 공통 풀 인터페이스 (드라이버 키로 자동 매핑 — 단위: idleTimeoutMs/acquireTimeoutMs/maxLifetimeMs=ms)
77
- # MEGA_PG_POOL_MIN=0
78
- # MEGA_PG_POOL_MAX=10
79
- # MEGA_PG_POOL_IDLE_TIMEOUT_MS=10000
80
- # MEGA_PG_POOL_ACQUIRE_TIMEOUT_MS=0
81
- # MEGA_PG_POOL_MAX_LIFETIME_MS=0 # pg 전용 (maria/mongo 미지원→throw)
82
- # 드라이버 특화 옵션 (passthrough — driver별 키 변환)
83
- # MEGA_PG_OPTIONS_SSL=true # → options.ssl (pg=snake_case)
84
- # MEGA_PG_OPTIONS_STATEMENT_TIMEOUT=30000 # → options.statement_timeout
85
- # MEGA_MARIA_OPTIONS_BIG_INT_STRATEGY=number # → options.bigIntStrategy (maria=camel) 'number'(디폴트,2^53 초과 정밀도 손실)|'bigint'|'string'
86
- # MEGA_MARIA_OPTIONS_CHARSET=utf8mb4 # → options.charset
87
- # MEGA_MARIA_OPTIONS_MULTIPLE_STATEMENTS=false # → options.multipleStatements (camel 변환)
88
- # MEGA_MONGO_DBNAME=mega_test # Mongo dbName (url path 로도 가능)
89
- # MEGA_MONGO_OPTIONS_AUTH_SOURCE=admin # → options.authSource (mongo=camel)
90
- # MEGA_MONGO_OPTIONS_SERVER_SELECTION_TIMEOUT_MS=5000 # → options.serverSelectionTimeoutMS (MS 대문자 보존, 이제 env 로 가능)
91
-
92
- # WS Hub (mega ws-hub CLI — src/cli/ws-hub.js, runWsHubCli)
93
- # 코드가 읽는 키는 MEGA_WSHUB_* (언더스코어 없는 단일 service 토큰). TOKENS 만 필수.
94
- # 비밀 토큰은 운영에서 교체. hub 는 `mega ws-hub` 실행 시에만 기동(자동 기동 없음).
95
- MEGA_WSHUB_TOKENS=dev-bridge-token-change-me
96
- MEGA_WSHUB_PORT=3100
97
- MEGA_WSHUB_HOST=0.0.0.0
98
- MEGA_WSHUB_HEARTBEAT_MS=25000
99
- MEGA_WSHUB_MAX_PAYLOAD=1048576
100
- # 압축(ADR-078) — 'true' 일 때만 ON. THRESHOLD 는 압축 ON 시에만 적용(byte).
101
- MEGA_WSHUB_COMPRESSION=false
102
- MEGA_WSHUB_COMPRESSION_THRESHOLD=1024
103
-
104
- # Telegram (옵션 — 로거 sink)
105
- # TG_BOT_TOKEN= ← 실토큰은 .env.local 에. npm publish 노출 방지로 .env 에선 비움(2026-06-08).
106
- # TG_CHAT_ID=
107
-
108
- # OpenTelemetry 분산 트레이싱 (옵션 — Phase 3 Step 8 / Phase 5 Step 1, MegaTracing)
109
- # MegaTracing.fromEnv() 가 읽는 런타임 키(ADR-114/126). 옵트인 OFF(디폴트)면 0 비용.
110
- # ⚠️ 구 OTEL_ENABLED/OTEL_ENDPOINT(prefix 없는)는 코드가 안 읽는 죽은 키라 제거 — 반드시 MEGA_OTEL_*.
111
- MEGA_OTEL_ENABLED=false
112
- MEGA_OTEL_SERVICE_NAME=mega-app
113
- MEGA_OTEL_ENDPOINT=http://localhost:4318/v1/traces
114
- MEGA_OTEL_EXPORTER=otlp
115
- MEGA_OTEL_SAMPLING_RATIO=1.0
116
- MEGA_OTEL_VERSION=
117
- MEGA_OTEL_ENVIRONMENT=
118
-
119
- # ── 실 OTel collector + Zipkin docker 백엔드 (ADR-128) — 통합 테스트(tracing E2E)가 읽음 ──
120
- # docker-compose 의 otel-collector(:4318 OTLP HTTP)·zipkin(:9411) 가동 시 실 OTLP→collector→zipkin 검증.
121
- # 미설정 시 tracing E2E(otel-collector-e2e)만 자동 skip(CI 일관성). OTLP HTTP traces path 는 /v1/traces.
122
- MEGA_OTEL_OTLP_ENDPOINT=http://localhost:4318/v1/traces
123
- MEGA_OTEL_ZIPKIN_API=http://localhost:9411/api/v2
124
- MEGA_OTEL_HEALTH_URL=http://localhost:13133
125
-
126
- # Log level
127
- LOG_LEVEL=info
@@ -1,30 +0,0 @@
1
- // @ts-check
2
- /**
3
- * 마이그레이션 add-auth-to-users (20260606000002). 로그인을 위해 users 에 인증 컬럼을 더한다(ADR-155).
4
- * `mega migrate`(up)·`mega migrate:down` 로 실행하며, 각 마이그레이션은 트랜잭션으로 감싸 적용된다.
5
- *
6
- * 컬럼은 모두 nullable — 기존 row(name+email 만 있는 사용자)를 깨지 않는다. password_hash 가 비어 있는
7
- * 계정은 로그인 대상이 아니며, /register 로 만든 계정만 해시를 가진다.
8
- *
9
- * - password_hash: scrypt 해시 문자열(`$scrypt$…`, src/lib/mega-hash.js). 평문 비밀번호는 저장하지 않는다.
10
- * - last_login_at: 마지막 로그인 시각(성공 시 갱신). 운영 관측·"최근 로그인" 표시에 쓴다.
11
- *
12
- * brute-force 실패 카운트·잠금은 DB 컬럼이 아니라 redis 에 둔다(원자적 INCR, src/lib/mega-brute-force.js,
13
- * ADR-049/130) — 여기 failed_login_count 같은 컬럼을 두지 않는 이유다.
14
- *
15
- * @param {{ query: (sql: string, params?: any[]) => Promise<any> }} db - 대상 DB 어댑터(트랜잭션 내 query).
16
- * @returns {Promise<void>}
17
- */
18
- export async function up(db) {
19
- await db.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT')
20
- await db.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ')
21
- }
22
-
23
- /**
24
- * @param {{ query: (sql: string, params?: any[]) => Promise<any> }} db
25
- * @returns {Promise<void>}
26
- */
27
- export async function down(db) {
28
- await db.query('ALTER TABLE users DROP COLUMN IF EXISTS last_login_at')
29
- await db.query('ALTER TABLE users DROP COLUMN IF EXISTS password_hash')
30
- }
@@ -1,71 +0,0 @@
1
- // @ts-check
2
- import { randomUUID } from 'node:crypto'
3
- import { MegaModel } from 'mega-framework'
4
-
5
- /**
6
- * Note 모델 — `notes` 컬렉션(globalKey 'mongo' 의 MongoDB, ADR-108). SQL 모델(User)과 달리 `this.db` 가
7
- * MongoClient 의 `Db` 핸들이라, ORM 없이 도큐먼트 API(`collection().find/insertOne/...`)로 도메인 메서드를
8
- * 직접 작성한다(ADR-009). 모델은 서비스만 import 한다(라우트·컨트롤러 직접 import 는 `mega/no-direct-model-import` 차단, ADR-022).
9
- *
10
- * 식별자는 mongo `_id`(ObjectId) 대신 자체 `id`(UUID v4) 필드를 쓴다 — 라우트 파라미터/폼에서 다루기 쉽고,
11
- * `mongodb` 드라이버의 `ObjectId` 를 샘플에서 import 하지 않아도 돼 driver 결합을 피한다. 읽기에서는
12
- * `_id` 를 projection 으로 제외해 도메인 형태만 노출한다.
13
- *
14
- * @typedef {{ id: string, title: string, body: string, created_at: string }} NoteDoc
15
- */
16
- export class Note extends MegaModel {
17
- static adapter = 'mongo'
18
- static table = 'notes'
19
-
20
- /** 읽기 공통 projection — 내부 `_id` 를 빼고 도메인 필드만 돌려준다. */
21
- static #projection = { projection: { _id: 0 } }
22
-
23
- /** @returns {import('mongodb').Collection} `notes` 컬렉션 핸들. */
24
- static get collection() {
25
- return this.db.collection(this.table)
26
- }
27
-
28
- /** 최신순 전체 목록. @returns {Promise<NoteDoc[]>} */
29
- static async list() {
30
- return /** @type {Promise<NoteDoc[]>} */ (
31
- this.collection.find({}, Note.#projection).sort({ created_at: -1 }).toArray()
32
- )
33
- }
34
-
35
- /** 단건 조회(없으면 null). @param {string} id @returns {Promise<NoteDoc | null>} */
36
- static async findById(id) {
37
- return /** @type {Promise<NoteDoc | null>} */ (this.collection.findOne({ id }, Note.#projection))
38
- }
39
-
40
- /** 생성 — UUID·생성시각을 부여해 삽입한다. @param {{ title: string, body: string }} input @returns {Promise<NoteDoc>} */
41
- static async create({ title, body }) {
42
- /** @type {NoteDoc} */
43
- const doc = { id: randomUUID(), title, body, created_at: new Date().toISOString() }
44
- await this.collection.insertOne({ ...doc })
45
- return doc
46
- }
47
-
48
- /**
49
- * 수정 — 주어진 필드만 $set 한다(undefined 는 제외해 기존 값 보존). 대상이 없으면 null.
50
- * @param {string} id @param {{ title?: string, body?: string }} patch @returns {Promise<NoteDoc | null>}
51
- */
52
- static async update(id, { title, body }) {
53
- /** @type {Record<string, string>} */
54
- const set = {}
55
- if (title !== undefined) set.title = title
56
- if (body !== undefined) set.body = body
57
- if (Object.keys(set).length > 0) await this.collection.updateOne({ id }, { $set: set })
58
- return this.findById(id)
59
- }
60
-
61
- /** 삭제 — 삭제 건수가 1 이상이면 true. @param {string} id @returns {Promise<boolean>} */
62
- static async remove(id) {
63
- const { deletedCount } = await this.collection.deleteOne({ id })
64
- return deletedCount > 0
65
- }
66
-
67
- /** 전체 도큐먼트 수(캐시 데모용 카운트 쿼리). @returns {Promise<number>} */
68
- static async count() {
69
- return this.collection.countDocuments({})
70
- }
71
- }
@@ -1,86 +0,0 @@
1
- // @ts-check
2
- import { MegaModel } from 'mega-framework'
3
-
4
- /**
5
- * User 모델 — `users` 테이블(globalKey 'primary' 의 postgres). ORM 없이 계측 SQL(`this.query`, ADR-138)로
6
- * 도메인 메서드를 직접 작성한다(ADR-009). 모델은 서비스만 import 한다(라우트·컨트롤러 직접 import 는
7
- * `mega/no-direct-model-import` 가 차단, ADR-022).
8
- *
9
- * @typedef {{ id: number, name: string, email: string, created_at: string }} UserRow
10
- * @typedef {{ id: number, name: string, email: string, password_hash: string | null }} UserAuthRow
11
- */
12
- export class User extends MegaModel {
13
- static adapter = 'primary'
14
- static table = 'users'
15
-
16
- /** @returns {Promise<UserRow[]>} */
17
- static async list() {
18
- const { rows } = await this.query('SELECT id, name, email, created_at FROM users ORDER BY id ASC')
19
- return rows
20
- }
21
-
22
- /** 전체 사용자 수 — /demo/redis 캐시 데모가 캐싱하는 SQL 카운트 쿼리. @returns {Promise<number>} */
23
- static async count() {
24
- const { rows } = await this.query('SELECT count(*)::int AS n FROM users')
25
- return rows[0].n
26
- }
27
-
28
- /** @param {number} id @returns {Promise<UserRow | null>} */
29
- static async findById(id) {
30
- const { rows } = await this.query('SELECT id, name, email, created_at FROM users WHERE id = $1', [id])
31
- return rows[0] ?? null
32
- }
33
-
34
- /** @param {{ name: string, email: string }} input @returns {Promise<UserRow>} */
35
- static async create({ name, email }) {
36
- const { rows } = await this.query(
37
- 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at',
38
- [name, email],
39
- )
40
- return rows[0]
41
- }
42
-
43
- /** @param {number} id @param {{ name?: string, email?: string }} patch @returns {Promise<UserRow | null>} */
44
- static async update(id, { name, email }) {
45
- const { rows } = await this.query(
46
- 'UPDATE users SET name = COALESCE($2, name), email = COALESCE($3, email) WHERE id = $1 RETURNING id, name, email, created_at',
47
- [id, name ?? null, email ?? null],
48
- )
49
- return rows[0] ?? null
50
- }
51
-
52
- /** @param {number} id @returns {Promise<boolean>} 삭제 여부. */
53
- static async remove(id) {
54
- const { rowCount } = await this.query('DELETE FROM users WHERE id = $1', [id])
55
- return rowCount > 0
56
- }
57
-
58
- /**
59
- * 비밀번호 해시를 포함해 새 사용자를 만든다(/register). 반환에는 해시를 포함하지 않는다(노출 최소화).
60
- * @param {{ name: string, email: string, passwordHash: string }} input
61
- * @returns {Promise<UserRow>}
62
- */
63
- static async register({ name, email, passwordHash }) {
64
- const { rows } = await this.query(
65
- 'INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3) RETURNING id, name, email, created_at',
66
- [name, email, passwordHash],
67
- )
68
- return rows[0]
69
- }
70
-
71
- /**
72
- * 로그인 검증용 — 이메일로 사용자를 찾아 password_hash 까지 돌려준다. 해시가 필요한 인증 경로에서만 쓴다
73
- * (일반 조회 list/findById 는 해시를 SELECT 하지 않아 노출되지 않는다).
74
- * @param {string} email
75
- * @returns {Promise<UserAuthRow | null>}
76
- */
77
- static async findByEmailWithHash(email) {
78
- const { rows } = await this.query('SELECT id, name, email, password_hash FROM users WHERE email = $1', [email])
79
- return rows[0] ?? null
80
- }
81
-
82
- /** 로그인 성공 시각 갱신. @param {number} id @returns {Promise<void>} */
83
- static async touchLastLogin(id) {
84
- await this.query('UPDATE users SET last_login_at = now() WHERE id = $1', [id])
85
- }
86
- }