@fuzionx/framework 0.1.43 → 0.1.44

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 (80) hide show
  1. package/README.md +501 -501
  2. package/bin/fx.js +12 -12
  3. package/cli/db-sync.js +100 -100
  4. package/cli/index.js +494 -494
  5. package/cli/templates/make/app/controllers/HomeController.js +14 -14
  6. package/cli/templates/make/app/routes/api.js +7 -7
  7. package/cli/templates/make/app/routes/web.js +5 -5
  8. package/cli/templates/make/app/views/default/errors/404.html +11 -11
  9. package/cli/templates/make/app/views/default/errors/500.html +14 -14
  10. package/cli/templates/make/app/views/default/layouts/main.html +22 -22
  11. package/cli/templates/make/app/views/default/pages/home.html +11 -11
  12. package/cli/templates/make/controller.js.tpl +40 -40
  13. package/cli/templates/make/event.js.tpl +8 -8
  14. package/cli/templates/make/job.js.tpl +10 -10
  15. package/cli/templates/make/middleware.js.tpl +10 -10
  16. package/cli/templates/make/model.js.tpl +15 -15
  17. package/cli/templates/make/service.js.tpl +15 -15
  18. package/cli/templates/make/task.js.tpl +15 -15
  19. package/cli/templates/make/test.js.tpl +7 -7
  20. package/cli/templates/make/worker.js.tpl +14 -14
  21. package/cli/templates/make/ws.js.tpl +18 -18
  22. package/index.js +67 -67
  23. package/lib/core/AppError.js +46 -46
  24. package/lib/core/Application.js +1006 -1006
  25. package/lib/core/AutoLoader.js +227 -227
  26. package/lib/core/Base.js +64 -64
  27. package/lib/core/Config.js +331 -331
  28. package/lib/core/Context.js +484 -484
  29. package/lib/database/ConnectionManager.js +208 -208
  30. package/lib/database/MariaModel.js +29 -29
  31. package/lib/database/Model.js +247 -247
  32. package/lib/database/ModelRegistry.js +72 -72
  33. package/lib/database/MongoModel.js +232 -232
  34. package/lib/database/Pagination.js +37 -37
  35. package/lib/database/PostgreModel.js +29 -29
  36. package/lib/database/QueryBuilder.js +172 -172
  37. package/lib/database/SQLiteModel.js +27 -27
  38. package/lib/database/SqlModel.js +257 -257
  39. package/lib/database/SqlQueryBuilder.js +332 -332
  40. package/lib/helpers/CryptoHelper.js +48 -48
  41. package/lib/helpers/FileHelper.js +61 -61
  42. package/lib/helpers/HashHelper.js +39 -39
  43. package/lib/helpers/I18nHelper.js +174 -174
  44. package/lib/helpers/Logger.js +108 -108
  45. package/lib/helpers/MediaHelper.js +84 -84
  46. package/lib/http/Controller.js +34 -34
  47. package/lib/http/ErrorHandler.js +136 -136
  48. package/lib/http/Middleware.js +43 -43
  49. package/lib/http/Router.js +109 -109
  50. package/lib/http/Validation.js +125 -125
  51. package/lib/middleware/apiAuth.js +79 -79
  52. package/lib/middleware/auth.js +42 -42
  53. package/lib/middleware/bodyParser.js +19 -19
  54. package/lib/middleware/cors.js +47 -47
  55. package/lib/middleware/csrf.js +32 -32
  56. package/lib/middleware/index.js +13 -13
  57. package/lib/middleware/session.js +27 -27
  58. package/lib/middleware/theme.js +20 -20
  59. package/lib/realtime/RoomManager.js +85 -85
  60. package/lib/realtime/WsHandler.js +107 -107
  61. package/lib/schedule/Job.js +38 -38
  62. package/lib/schedule/Queue.js +103 -103
  63. package/lib/schedule/Scheduler.js +171 -171
  64. package/lib/schedule/Task.js +39 -39
  65. package/lib/schedule/WorkerPool.js +225 -225
  66. package/lib/services/EventBus.js +94 -94
  67. package/lib/services/Service.js +261 -261
  68. package/lib/services/Storage.js +112 -112
  69. package/lib/utilities/ArrUtil.js +112 -112
  70. package/lib/utilities/DateUtil.js +98 -98
  71. package/lib/utilities/FunctionUtil.js +119 -119
  72. package/lib/utilities/NumUtil.js +75 -75
  73. package/lib/utilities/ObjectUtil.js +170 -170
  74. package/lib/utilities/PaginationUtil.js +81 -81
  75. package/lib/utilities/StrUtil.js +105 -105
  76. package/lib/utilities/index.js +18 -18
  77. package/lib/view/OpenAPI.js +231 -231
  78. package/lib/view/View.js +83 -83
  79. package/package.json +2 -2
  80. package/testing/index.js +232 -232
@@ -1,247 +1,247 @@
1
- /**
2
- * Model — 데이터베이스 테이블/컬렉션 추상화
3
- *
4
- * @see docs/framework/02-database-orm.md
5
- * @see docs/framework/class-design.mm.md (Model)
6
- */
7
- import QueryBuilder from './QueryBuilder.js';
8
- import { ServiceError } from '../core/AppError.js';
9
-
10
- export default class Model {
11
- // ── Static 멤버 (서브클래스에서 오버라이드) ──
12
- static table = ''; // 테이블명 / 컬렉션명
13
- static primaryKey = 'id'; // 기본키
14
- static connection = 'main'; // DB 연결명
15
- static timestamps = true; // created_at, updated_at 자동
16
- static softDelete = false; // deleted_at 소프트 삭제
17
- static hidden = []; // JSON 직렬화 시 제외 필드
18
- static columns = {}; // SQL 스키마 정의 (fx db:sync 용)
19
- static indexes = []; // 인덱스 정의 [{ columns: ['role'], unique? }]
20
- static schema = {}; // MongoDB 스키마
21
-
22
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
- // 인스턴스 (레코드 래퍼)
24
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
25
-
26
- constructor(attributes = {}) {
27
- this._attributes = { ...attributes };
28
- this._original = { ...attributes };
29
- // 속성을 인스턴스 프로퍼티로도 노출 (backward compat)
30
- Object.assign(this, attributes);
31
- }
32
-
33
- /** 속성 접근 */
34
- get(key) { return key ? this._attributes[key] : { ...this._attributes }; }
35
- set(key, value) { this._attributes[key] = value; }
36
-
37
- /**
38
- * 뮤테이터용 내부 설정 (02-database-orm.md)
39
- * set password(v) { this._set('password', app.hash.argon2(v)); }
40
- */
41
- _set(key, value) {
42
- this._attributes[key] = value;
43
- this[key] = value;
44
- }
45
-
46
- /**
47
- * 인스턴스 수정 — DB update + 로컬 반영
48
- * @param {object} data
49
- * @returns {Promise<this>}
50
- */
51
- async update(data) {
52
- Object.assign(this._attributes, data);
53
- // Model 서브클래스(Maria/Postgre/Mongo)에서 오버라이드하여 실제 DB 쿼리 실행
54
- return this;
55
- }
56
-
57
- /**
58
- * 인스턴스 삭제
59
- * softDelete가 true면 deleted_at 설정, 아니면 실제 삭제.
60
- * @returns {Promise<void>}
61
- */
62
- async delete() {
63
- if (this.constructor.softDelete) {
64
- const now = new Date().toISOString();
65
- this._attributes.deleted_at = now;
66
- this.deleted_at = now;
67
- await this.update({ deleted_at: now });
68
- return;
69
- }
70
- // 서브클래스에서 오버라이드하여 실제 DB 삭제
71
- }
72
-
73
- /**
74
- * 소프트 삭제 복원 — deleted_at = null
75
- * @returns {Promise<this>}
76
- */
77
- async restore() {
78
- this._attributes.deleted_at = null;
79
- this.deleted_at = null;
80
- await this.update({ deleted_at: null });
81
- return this;
82
- }
83
-
84
- /**
85
- * 강제 삭제 — softDelete 무시하고 실제 삭제
86
- * @returns {Promise<void>}
87
- */
88
- async forceDelete() {
89
- // 서브클래스에서 오버라이드하여 실제 DB 삭제
90
- }
91
-
92
- /**
93
- * JSON 직렬화 — hidden 필드 제외
94
- * @returns {object}
95
- */
96
- toJSON() {
97
- const obj = { ...this._attributes };
98
- const hidden = this.constructor.hidden || [];
99
- for (const key of hidden) {
100
- delete obj[key];
101
- }
102
- return obj;
103
- }
104
-
105
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
106
- // 관계 (스텁 — 서브클래스에서 오버라이드)
107
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
108
-
109
- /** 1:N 관계 */
110
- hasMany(model, fk) {
111
- return { type: 'hasMany', model, fk: fk || `${this.constructor.table.replace(/s$/, '')}_id` };
112
- }
113
-
114
- /** N:1 관계 */
115
- belongsTo(model, fk) {
116
- return { type: 'belongsTo', model, fk: fk || `${model.toLowerCase()}_id` };
117
- }
118
-
119
- /** 1:1 관계 */
120
- hasOne(model, fk) {
121
- return { type: 'hasOne', model, fk: fk || `${this.constructor.table.replace(/s$/, '')}_id` };
122
- }
123
-
124
- /** N:N 관계 */
125
- belongsToMany(model, pivot) {
126
- return { type: 'belongsToMany', model, pivot };
127
- }
128
-
129
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
130
- // Static 메서드 (쿼리 진입점)
131
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
132
-
133
- /** 쿼리 빌더 시작 */
134
- static query() {
135
- return new QueryBuilder(this);
136
- }
137
-
138
- /** where 체이닝 시작 */
139
- static where(key, op, value) {
140
- return this.query().where(key, op, value);
141
- }
142
-
143
- /** limit 체이닝 시작 */
144
- static limit(n) {
145
- return this.query().limit(n);
146
- }
147
-
148
- /** 관계 eager-load 진입점 */
149
- static with(...relations) {
150
- return this.query().with(...relations);
151
- }
152
-
153
- /** 페이지네이션 진입점 */
154
- static paginate(page, perPage) {
155
- return this.query().paginate(page, perPage);
156
- }
157
-
158
- /**
159
- * ID로 조회
160
- * @param {*} id
161
- * @returns {Promise<Model|null>}
162
- */
163
- static async find(id) {
164
- return this.query().where(this.primaryKey, id).first();
165
- }
166
-
167
- /**
168
- * ID로 조회 (없으면 404 에러)
169
- * @param {*} id
170
- * @returns {Promise<Model>}
171
- */
172
- static async findOrFail(id) {
173
- const result = await this.find(id);
174
- if (!result) {
175
- throw new ServiceError(`${this.name} not found`, 404);
176
- }
177
- return result;
178
- }
179
-
180
- /**
181
- * 전체 조회
182
- * @returns {Promise<Array>}
183
- */
184
- static async all() {
185
- return this.query().get();
186
- }
187
-
188
- /** all() alias */
189
- static async findAll() {
190
- return this.all();
191
- }
192
-
193
- /**
194
- * 여러 ID 조회
195
- * @param {Array} ids
196
- * @returns {Promise<Array>}
197
- */
198
- static async findMany(ids) {
199
- return this.query().whereIn(this.primaryKey, ids).get();
200
- }
201
-
202
- /**
203
- * 카운트
204
- * @param {object} [where] - 간단 조건
205
- * @returns {Promise<number>}
206
- */
207
- static async count(where) {
208
- let qb = this.query();
209
- if (where) {
210
- for (const [key, value] of Object.entries(where)) {
211
- qb = qb.where(key, value);
212
- }
213
- }
214
- return qb.count();
215
- }
216
-
217
- /**
218
- * 생성
219
- * @param {object} data
220
- * @returns {Promise<Model>}
221
- */
222
- static async create(data) {
223
- // Model 서브클래스에서 오버라이드하여 실제 DB insert
224
- const instance = new this({ ...data, [this.primaryKey]: Date.now() });
225
- return instance;
226
- }
227
-
228
- /**
229
- * Raw 쿼리 (DB 고유 기능)
230
- * @param {string|Array} query - SQL 문자열 또는 MongoDB pipeline
231
- * @param {Array} [bindings] - SQL 바인딩
232
- * @returns {Promise<Array>}
233
- */
234
- static async raw(query, bindings) {
235
- // 서브클래스에서 오버라이드 (Knex.raw / Mongoose.aggregate)
236
- return [];
237
- }
238
-
239
- /**
240
- * 네이티브 드라이버 접근
241
- * @returns {object} - Knex 인스턴스 | Mongoose 인스턴스
242
- */
243
- static nativeConnection() {
244
- // 서브클래스에서 오버라이드
245
- return null;
246
- }
247
- }
1
+ /**
2
+ * Model — 데이터베이스 테이블/컬렉션 추상화
3
+ *
4
+ * @see docs/framework/02-database-orm.md
5
+ * @see docs/framework/class-design.mm.md (Model)
6
+ */
7
+ import QueryBuilder from './QueryBuilder.js';
8
+ import { ServiceError } from '../core/AppError.js';
9
+
10
+ export default class Model {
11
+ // ── Static 멤버 (서브클래스에서 오버라이드) ──
12
+ static table = ''; // 테이블명 / 컬렉션명
13
+ static primaryKey = 'id'; // 기본키
14
+ static connection = 'main'; // DB 연결명
15
+ static timestamps = true; // created_at, updated_at 자동
16
+ static softDelete = false; // deleted_at 소프트 삭제
17
+ static hidden = []; // JSON 직렬화 시 제외 필드
18
+ static columns = {}; // SQL 스키마 정의 (fx db:sync 용)
19
+ static indexes = []; // 인덱스 정의 [{ columns: ['role'], unique? }]
20
+ static schema = {}; // MongoDB 스키마
21
+
22
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
+ // 인스턴스 (레코드 래퍼)
24
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
25
+
26
+ constructor(attributes = {}) {
27
+ this._attributes = { ...attributes };
28
+ this._original = { ...attributes };
29
+ // 속성을 인스턴스 프로퍼티로도 노출 (backward compat)
30
+ Object.assign(this, attributes);
31
+ }
32
+
33
+ /** 속성 접근 */
34
+ get(key) { return key ? this._attributes[key] : { ...this._attributes }; }
35
+ set(key, value) { this._attributes[key] = value; }
36
+
37
+ /**
38
+ * 뮤테이터용 내부 설정 (02-database-orm.md)
39
+ * set password(v) { this._set('password', app.hash.argon2(v)); }
40
+ */
41
+ _set(key, value) {
42
+ this._attributes[key] = value;
43
+ this[key] = value;
44
+ }
45
+
46
+ /**
47
+ * 인스턴스 수정 — DB update + 로컬 반영
48
+ * @param {object} data
49
+ * @returns {Promise<this>}
50
+ */
51
+ async update(data) {
52
+ Object.assign(this._attributes, data);
53
+ // Model 서브클래스(Maria/Postgre/Mongo)에서 오버라이드하여 실제 DB 쿼리 실행
54
+ return this;
55
+ }
56
+
57
+ /**
58
+ * 인스턴스 삭제
59
+ * softDelete가 true면 deleted_at 설정, 아니면 실제 삭제.
60
+ * @returns {Promise<void>}
61
+ */
62
+ async delete() {
63
+ if (this.constructor.softDelete) {
64
+ const now = new Date().toISOString();
65
+ this._attributes.deleted_at = now;
66
+ this.deleted_at = now;
67
+ await this.update({ deleted_at: now });
68
+ return;
69
+ }
70
+ // 서브클래스에서 오버라이드하여 실제 DB 삭제
71
+ }
72
+
73
+ /**
74
+ * 소프트 삭제 복원 — deleted_at = null
75
+ * @returns {Promise<this>}
76
+ */
77
+ async restore() {
78
+ this._attributes.deleted_at = null;
79
+ this.deleted_at = null;
80
+ await this.update({ deleted_at: null });
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * 강제 삭제 — softDelete 무시하고 실제 삭제
86
+ * @returns {Promise<void>}
87
+ */
88
+ async forceDelete() {
89
+ // 서브클래스에서 오버라이드하여 실제 DB 삭제
90
+ }
91
+
92
+ /**
93
+ * JSON 직렬화 — hidden 필드 제외
94
+ * @returns {object}
95
+ */
96
+ toJSON() {
97
+ const obj = { ...this._attributes };
98
+ const hidden = this.constructor.hidden || [];
99
+ for (const key of hidden) {
100
+ delete obj[key];
101
+ }
102
+ return obj;
103
+ }
104
+
105
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
106
+ // 관계 (스텁 — 서브클래스에서 오버라이드)
107
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
108
+
109
+ /** 1:N 관계 */
110
+ hasMany(model, fk) {
111
+ return { type: 'hasMany', model, fk: fk || `${this.constructor.table.replace(/s$/, '')}_id` };
112
+ }
113
+
114
+ /** N:1 관계 */
115
+ belongsTo(model, fk) {
116
+ return { type: 'belongsTo', model, fk: fk || `${model.toLowerCase()}_id` };
117
+ }
118
+
119
+ /** 1:1 관계 */
120
+ hasOne(model, fk) {
121
+ return { type: 'hasOne', model, fk: fk || `${this.constructor.table.replace(/s$/, '')}_id` };
122
+ }
123
+
124
+ /** N:N 관계 */
125
+ belongsToMany(model, pivot) {
126
+ return { type: 'belongsToMany', model, pivot };
127
+ }
128
+
129
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
130
+ // Static 메서드 (쿼리 진입점)
131
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
132
+
133
+ /** 쿼리 빌더 시작 */
134
+ static query() {
135
+ return new QueryBuilder(this);
136
+ }
137
+
138
+ /** where 체이닝 시작 */
139
+ static where(key, op, value) {
140
+ return this.query().where(key, op, value);
141
+ }
142
+
143
+ /** limit 체이닝 시작 */
144
+ static limit(n) {
145
+ return this.query().limit(n);
146
+ }
147
+
148
+ /** 관계 eager-load 진입점 */
149
+ static with(...relations) {
150
+ return this.query().with(...relations);
151
+ }
152
+
153
+ /** 페이지네이션 진입점 */
154
+ static paginate(page, perPage) {
155
+ return this.query().paginate(page, perPage);
156
+ }
157
+
158
+ /**
159
+ * ID로 조회
160
+ * @param {*} id
161
+ * @returns {Promise<Model|null>}
162
+ */
163
+ static async find(id) {
164
+ return this.query().where(this.primaryKey, id).first();
165
+ }
166
+
167
+ /**
168
+ * ID로 조회 (없으면 404 에러)
169
+ * @param {*} id
170
+ * @returns {Promise<Model>}
171
+ */
172
+ static async findOrFail(id) {
173
+ const result = await this.find(id);
174
+ if (!result) {
175
+ throw new ServiceError(`${this.name} not found`, 404);
176
+ }
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * 전체 조회
182
+ * @returns {Promise<Array>}
183
+ */
184
+ static async all() {
185
+ return this.query().get();
186
+ }
187
+
188
+ /** all() alias */
189
+ static async findAll() {
190
+ return this.all();
191
+ }
192
+
193
+ /**
194
+ * 여러 ID 조회
195
+ * @param {Array} ids
196
+ * @returns {Promise<Array>}
197
+ */
198
+ static async findMany(ids) {
199
+ return this.query().whereIn(this.primaryKey, ids).get();
200
+ }
201
+
202
+ /**
203
+ * 카운트
204
+ * @param {object} [where] - 간단 조건
205
+ * @returns {Promise<number>}
206
+ */
207
+ static async count(where) {
208
+ let qb = this.query();
209
+ if (where) {
210
+ for (const [key, value] of Object.entries(where)) {
211
+ qb = qb.where(key, value);
212
+ }
213
+ }
214
+ return qb.count();
215
+ }
216
+
217
+ /**
218
+ * 생성
219
+ * @param {object} data
220
+ * @returns {Promise<Model>}
221
+ */
222
+ static async create(data) {
223
+ // Model 서브클래스에서 오버라이드하여 실제 DB insert
224
+ const instance = new this({ ...data, [this.primaryKey]: Date.now() });
225
+ return instance;
226
+ }
227
+
228
+ /**
229
+ * Raw 쿼리 (DB 고유 기능)
230
+ * @param {string|Array} query - SQL 문자열 또는 MongoDB pipeline
231
+ * @param {Array} [bindings] - SQL 바인딩
232
+ * @returns {Promise<Array>}
233
+ */
234
+ static async raw(query, bindings) {
235
+ // 서브클래스에서 오버라이드 (Knex.raw / Mongoose.aggregate)
236
+ return [];
237
+ }
238
+
239
+ /**
240
+ * 네이티브 드라이버 접근
241
+ * @returns {object} - Knex 인스턴스 | Mongoose 인스턴스
242
+ */
243
+ static nativeConnection() {
244
+ // 서브클래스에서 오버라이드
245
+ return null;
246
+ }
247
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * ModelRegistry — Proxy 기반 db.User 접근
3
- *
4
- * app.db.User → ModelRegistry에서 'User' 모델 반환.
5
- * Proxy로 동적 프로퍼티 접근 지원.
6
- *
7
- * @see docs/framework/class-design.mm.md (ModelRegistry)
8
- */
9
- export default class ModelRegistry {
10
- constructor() {
11
- this._models = new Map();
12
- this._knex = null;
13
- this._mongoConnections = null;
14
-
15
- // Proxy로 동적 접근 지원 (db.User → _models.get('User'))
16
- return new Proxy(this, {
17
- get(target, prop) {
18
- // 내부 메서드/프로퍼티는 직접 접근
19
- if (prop in target || typeof prop === 'symbol') {
20
- return target[prop];
21
- }
22
- // 모델 이름으로 접근
23
- if (target._models.has(prop)) {
24
- return target._models.get(prop);
25
- }
26
- return undefined;
27
- },
28
- });
29
- }
30
-
31
- /**
32
- * 모델 등록
33
- * @param {string} name - 모델 이름 (e.g. 'User')
34
- * @param {typeof import('./Model.js').default} ModelClass
35
- */
36
- register(name, ModelClass) {
37
- this._models.set(name, ModelClass);
38
- }
39
-
40
- /**
41
- * 모델 조회
42
- * @param {string} name
43
- * @returns {typeof import('./Model.js').default}
44
- */
45
- get(name) {
46
- return this._models.get(name);
47
- }
48
-
49
- /**
50
- * 등록된 모델 이름 목록
51
- * @returns {string[]}
52
- */
53
- names() {
54
- return [...this._models.keys()];
55
- }
56
-
57
- /**
58
- * Knex 연결 설정 (SQL)
59
- * @param {object} knex
60
- */
61
- setKnex(knex) {
62
- this._knex = knex;
63
- }
64
-
65
- /**
66
- * MongoDB 연결 설정
67
- * @param {object} connections
68
- */
69
- setMongoConnections(connections) {
70
- this._mongoConnections = connections;
71
- }
72
- }
1
+ /**
2
+ * ModelRegistry — Proxy 기반 db.User 접근
3
+ *
4
+ * app.db.User → ModelRegistry에서 'User' 모델 반환.
5
+ * Proxy로 동적 프로퍼티 접근 지원.
6
+ *
7
+ * @see docs/framework/class-design.mm.md (ModelRegistry)
8
+ */
9
+ export default class ModelRegistry {
10
+ constructor() {
11
+ this._models = new Map();
12
+ this._knex = null;
13
+ this._mongoConnections = null;
14
+
15
+ // Proxy로 동적 접근 지원 (db.User → _models.get('User'))
16
+ return new Proxy(this, {
17
+ get(target, prop) {
18
+ // 내부 메서드/프로퍼티는 직접 접근
19
+ if (prop in target || typeof prop === 'symbol') {
20
+ return target[prop];
21
+ }
22
+ // 모델 이름으로 접근
23
+ if (target._models.has(prop)) {
24
+ return target._models.get(prop);
25
+ }
26
+ return undefined;
27
+ },
28
+ });
29
+ }
30
+
31
+ /**
32
+ * 모델 등록
33
+ * @param {string} name - 모델 이름 (e.g. 'User')
34
+ * @param {typeof import('./Model.js').default} ModelClass
35
+ */
36
+ register(name, ModelClass) {
37
+ this._models.set(name, ModelClass);
38
+ }
39
+
40
+ /**
41
+ * 모델 조회
42
+ * @param {string} name
43
+ * @returns {typeof import('./Model.js').default}
44
+ */
45
+ get(name) {
46
+ return this._models.get(name);
47
+ }
48
+
49
+ /**
50
+ * 등록된 모델 이름 목록
51
+ * @returns {string[]}
52
+ */
53
+ names() {
54
+ return [...this._models.keys()];
55
+ }
56
+
57
+ /**
58
+ * Knex 연결 설정 (SQL)
59
+ * @param {object} knex
60
+ */
61
+ setKnex(knex) {
62
+ this._knex = knex;
63
+ }
64
+
65
+ /**
66
+ * MongoDB 연결 설정
67
+ * @param {object} connections
68
+ */
69
+ setMongoConnections(connections) {
70
+ this._mongoConnections = connections;
71
+ }
72
+ }