@alloy-framework/core 0.3.0 → 0.14.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.
@@ -1,131 +0,0 @@
1
- import native from '@alloy-framework/rust';
2
-
3
- /** @type {object|null} 캐싱된 설정 객체 (alloy.conf 구조에 맞게 재매핑됨) */
4
- let cachedConfig = null;
5
-
6
- /**
7
- * 🔧 AlloyConfig — alloy.conf 설정값 접근 모듈 (싱글턴)
8
- *
9
- * Rust 네이티브 엔진이 파싱한 alloy.conf 설정을 JS에서 사용할 수 있도록 노출합니다.
10
- * alloy.conf에 쓴 키 구조 그대로 접근할 수 있습니다.
11
- * boot_system() 이후에 호출해야 합니다.
12
- *
13
- * @example
14
- * import { AlloyConfig } from '@alloy-framework/core';
15
- *
16
- * // alloy.conf 구조 그대로 접근
17
- * AlloyConfig.get('server.listen'); // 4100
18
- * AlloyConfig.get('server.workers'); // 4
19
- * AlloyConfig.get('logging.level'); // "info"
20
- * AlloyConfig.get('logging.dir'); // "logs"
21
- * AlloyConfig.get('websocket.path'); // "/ws-backend"
22
- * AlloyConfig.get('session.cookie_name'); // "alloy_starter_sess"
23
- * AlloyConfig.get('nats.prefix'); // "ws-backend"
24
- * AlloyConfig.get('i18n.default_locale'); // "ko"
25
- * AlloyConfig.get('security.master_secret'); // "alloy-starter-backend-secret-32ch"
26
- * AlloyConfig.get('custom.local_tz_offset'); // 9 (앱 전용 변수)
27
- *
28
- * // database는 이름으로 조회
29
- * AlloyConfig.getDatabase('session'); // { name, driver, url, max_pool_size }
30
- * AlloyConfig.getDatabase('maria_main'); // { name, driver, url, max_pool_size }
31
- */
32
- export class AlloyConfig {
33
- /**
34
- * Rust 구조체 JSON을 alloy.conf 구조에 맞게 재매핑합니다.
35
- * @param {object} raw - Rust에서 직렬화된 원본 설정 객체
36
- * @returns {object} alloy.conf 구조에 맞는 설정 객체
37
- * @private
38
- */
39
- static _remap(raw) {
40
- return {
41
- // server {} 블록 — logging/websocket 제외한 서버 설정
42
- server: {
43
- listen: raw.server?.port,
44
- port: raw.server?.port,
45
- workers: raw.server?.workers,
46
- templates_dir: raw.server?.templates_dir,
47
- template_cache_enabled: raw.server?.template_cache_enabled,
48
- request_timeout_seconds: raw.server?.request_timeout_seconds,
49
- },
50
- // logging {} 블록 — alloy.conf에서 독립 블록
51
- logging: raw.server?.logging,
52
- // websocket — alloy.conf에서 set $alloy_ws_* 로 설정
53
- websocket: raw.server?.websocket,
54
- // model — alloy.conf에서 server {} 내 model_schema_sync/strict
55
- model: raw.model,
56
- // security — set $alloy_cors_*, $alloy_encrypt_traffic 등
57
- security: raw.security,
58
- // session — set $alloy_session_*
59
- session: raw.session,
60
- // databases — database <name> {} 블록들 (배열)
61
- databases: raw.databases,
62
- // nats {} 블록
63
- nats: raw.nats,
64
- // i18n {} 블록
65
- i18n: raw.i18n,
66
- // upload {} 블록
67
- upload: raw.upload,
68
- // location 블록들
69
- static_files: raw.static_files,
70
- // rate_limit — set $alloy_rate_limit_*
71
- rate_limit: raw.rate_limit,
72
- // limits — client_max_body_size 등
73
- limits: raw.limits,
74
- // custom {} 블록 — 앱 전용 변수 (Rust 빌드 없이 추가/변경 가능)
75
- custom: raw.custom || {},
76
- };
77
- }
78
-
79
- /**
80
- * 설정 전체를 로드하여 캐싱합니다.
81
- * boot_system() 이후에 호출해야 합니다.
82
- * @returns {object} alloy.conf 구조에 맞게 재매핑된 설정 객체
83
- */
84
- static load() {
85
- if (!cachedConfig) {
86
- const json = native.getConfig();
87
- const raw = JSON.parse(json);
88
- cachedConfig = AlloyConfig._remap(raw);
89
- }
90
- return cachedConfig;
91
- }
92
-
93
- /**
94
- * 캐시를 무효화하고 설정을 다시 로드합니다.
95
- * @returns {object} 새로 로드된 설정 객체
96
- */
97
- static reload() {
98
- cachedConfig = null;
99
- return AlloyConfig.load();
100
- }
101
-
102
- /**
103
- * 특정 키의 설정값을 반환합니다.
104
- * alloy.conf에 쓴 구조 그대로 dot notation으로 접근합니다.
105
- * @param {string} key - dot notation 키 (예: 'logging.level', 'session.cookie_name')
106
- * @param {*} defaultValue - 키가 없을 때 반환할 기본값
107
- * @returns {*} 설정값
108
- */
109
- static get(key, defaultValue = undefined) {
110
- const config = AlloyConfig.load();
111
- // dot notation 경로 탐색 (예: 'logging.level' → config.logging.level)
112
- const parts = key.split('.');
113
- let current = config;
114
- for (const part of parts) {
115
- if (current == null || typeof current !== 'object') return defaultValue;
116
- current = current[part];
117
- }
118
- return current !== undefined ? current : defaultValue;
119
- }
120
-
121
- /**
122
- * 이름으로 데이터베이스 설정을 조회합니다.
123
- * alloy.conf의 `database <name> {}` 블록에 대응합니다.
124
- * @param {string} name - 데이터베이스 이름 (예: 'maria_main', 'session', 'cache')
125
- * @returns {object|undefined} { name, driver, url, max_pool_size } 또는 undefined
126
- */
127
- static getDatabase(name) {
128
- const config = AlloyConfig.load();
129
- return config.databases?.find(db => db.name === name);
130
- }
131
- }
@@ -1,405 +0,0 @@
1
- import native from '@alloy-framework/rust';
2
- import { validateForSave, validateForUpdate, applyMongoDefaults } from './validationHelper.js';
3
-
4
- /**
5
- * 📊 Alloy SQL 모델 (MySQL / MariaDB / PostgreSQL)
6
- * Rust SeaORM 기반 체이닝 쿼리 빌더와 CRUD 메서드를 제공합니다.
7
- *
8
- * @example
9
- * // 모델 정의
10
- * class Users extends AlloyModel {
11
- * constructor() { super('users'); }
12
- * static schema = Joi.object({ username: Joi.string().required() });
13
- * }
14
- *
15
- * // 체이닝 쿼리
16
- * const users = await new Users().select(['id', 'username']).where('id', '>', '10').limit(20).get();
17
- */
18
- export class AlloyModel {
19
- /**
20
- * @param {string} table - 테이블 이름
21
- * @param {string} [store='primary'] - alloy.conf에 정의된 SQL 스토어 이름
22
- */
23
- constructor(table, store = 'primary') {
24
- /** @type {string} 테이블 이름 */
25
- this.table = table;
26
- /** @type {string} 스토어 이름 */
27
- this.store = store;
28
- /** @type {import('@alloy/rust').AlloyModel} 네이티브 모델 */
29
- this._native = new native.AlloyModel(table, store);
30
- }
31
-
32
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
33
- // 🔗 체이닝 쿼리 빌더
34
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
35
-
36
- /**
37
- * 조회할 필드 지정
38
- * @param {string[]} fields - 필드 배열
39
- * @returns {this}
40
- */
41
- select(fields) {
42
- this._native.select(fields);
43
- return this;
44
- }
45
-
46
- /**
47
- * WHERE 조건 추가 (AND)
48
- * @param {string} field - 필드명
49
- * @param {string} opOrValue - 연산자 또는 값 (2인수 시 값, 3인수 시 연산자)
50
- * @param {string|number|boolean} [value] - 비교 값 (3인수 시)
51
- * @returns {this}
52
- */
53
- where(field, opOrValue, value) {
54
- if (value === undefined) {
55
- // 2인수: .where('field', value) → op = '='
56
- this._native.where(field, '=', JSON.stringify(opOrValue));
57
- } else {
58
- this._native.where(field, opOrValue, JSON.stringify(value));
59
- }
60
- return this;
61
- }
62
-
63
- /**
64
- * OR WHERE 조건 추가 (이전 조건과 OR로 결합)
65
- * @param {string} field - 필드명
66
- * @param {string} opOrValue - 연산자 또는 값
67
- * @param {string|number|boolean} [value] - 비교 값 (3인수 시)
68
- * @returns {this}
69
- */
70
- orWhere(field, opOrValue, value) {
71
- if (value === undefined) {
72
- this._native.orWhere(field, '=', JSON.stringify(opOrValue));
73
- } else {
74
- this._native.orWhere(field, opOrValue, JSON.stringify(value));
75
- }
76
- return this;
77
- }
78
-
79
- /**
80
- * Raw SQL WHERE 조건 추가 (플레이스홀더: ?)
81
- * SQL 백엔드 전용 — MongoDB에서는 무시됨
82
- * @param {string} rawSql - Raw SQL 조건문 (예: '(col1 LIKE ? OR col2 LIKE ?)')
83
- * @param {Array<string>} [params=[]] - 바인딩 파라미터
84
- * @returns {this}
85
- */
86
- whereRaw(rawSql, params = []) {
87
- this._native.whereRaw(rawSql, JSON.stringify(params));
88
- return this;
89
- }
90
-
91
- /**
92
- * WHERE field IN (values) 조건
93
- * @param {string} field - 필드명
94
- * @param {Array<string|number>} values - 값 배열
95
- * @returns {this}
96
- */
97
- whereIn(field, values) {
98
- this._native.whereIn(field, JSON.stringify(values));
99
- return this;
100
- }
101
-
102
- /**
103
- * WHERE field NOT IN (values) 조건
104
- * @param {string} field - 필드명
105
- * @param {Array<string|number>} values - 값 배열
106
- * @returns {this}
107
- */
108
- whereNotIn(field, values) {
109
- this._native.whereNotIn(field, JSON.stringify(values));
110
- return this;
111
- }
112
-
113
- /**
114
- * WHERE field IS NULL 조건
115
- * @param {string} field - 필드명
116
- * @returns {this}
117
- */
118
- whereNull(field) {
119
- this._native.whereNull(field);
120
- return this;
121
- }
122
-
123
- /**
124
- * WHERE field IS NOT NULL 조건
125
- * @param {string} field - 필드명
126
- * @returns {this}
127
- */
128
- whereNotNull(field) {
129
- this._native.whereNotNull(field);
130
- return this;
131
- }
132
-
133
- /**
134
- * WHERE field BETWEEN min AND max 조건
135
- * @param {string} field - 필드명
136
- * @param {string|number} min - 최솟값
137
- * @param {string|number} max - 최댓값
138
- * @returns {this}
139
- */
140
- whereBetween(field, min, max) {
141
- this._native.whereBetween(field, JSON.stringify(min), JSON.stringify(max));
142
- return this;
143
- }
144
-
145
- /**
146
- * WHERE field IN (SELECT subColumn FROM subTable WHERE ...conditions) 서브쿼리 조건
147
- * @param {string} field - 메인 테이블 필드명
148
- * @param {string} subTable - 서브쿼리 테이블명
149
- * @param {string} subColumn - 서브쿼리 SELECT 컬럼명
150
- * @param {Array<{field: string, op: string, value?: any}>} conditions - 서브쿼리 WHERE 조건 배열
151
- * @returns {this}
152
- * @example
153
- * // _id IN (SELECT notification_id FROM notification_reads WHERE user_id = 5)
154
- * .whereInSub('_id', 'notification_reads', 'notification_id', [
155
- * { field: 'user_id', op: '=', value: 5 }
156
- * ])
157
- */
158
- whereInSub(field, subTable, subColumn, conditions = []) {
159
- this._native.whereInSub(field, subTable, subColumn, JSON.stringify(conditions));
160
- return this;
161
- }
162
-
163
- /**
164
- * WHERE field NOT IN (SELECT subColumn FROM subTable WHERE ...conditions) 서브쿼리 조건
165
- * @param {string} field - 메인 테이블 필드명
166
- * @param {string} subTable - 서브쿼리 테이블명
167
- * @param {string} subColumn - 서브쿼리 SELECT 컬럼명
168
- * @param {Array<{field: string, op: string, value?: any}>} conditions - 서브쿼리 WHERE 조건 배열
169
- * @returns {this}
170
- * @example
171
- * // _id NOT IN (SELECT notification_id FROM notification_reads WHERE user_id = 5 AND deleted_at IS NOT NULL)
172
- * .whereNotInSub('_id', 'notification_reads', 'notification_id', [
173
- * { field: 'user_id', op: '=', value: 5 },
174
- * { field: 'deleted_at', op: 'IS NOT NULL' }
175
- * ])
176
- */
177
- whereNotInSub(field, subTable, subColumn, conditions = []) {
178
- this._native.whereNotInSub(field, subTable, subColumn, JSON.stringify(conditions));
179
- return this;
180
- }
181
-
182
- /**
183
- * SELECT DISTINCT
184
- * @returns {this}
185
- */
186
- distinct() {
187
- this._native.distinct();
188
- return this;
189
- }
190
-
191
- /**
192
- * GROUP BY 필드
193
- * @param {string} field - 그룹핑 필드
194
- * @returns {this}
195
- */
196
- groupBy(field) {
197
- this._native.groupBy(field);
198
- return this;
199
- }
200
-
201
- /**
202
- * HAVING 조건 (GROUP BY 후 필터)
203
- * @param {string} field - 필드명
204
- * @param {string} op - 연산자
205
- * @param {string|number} value - 비교 값
206
- * @returns {this}
207
- */
208
- having(field, op, value) {
209
- this._native.having(field, op, JSON.stringify(value));
210
- return this;
211
- }
212
-
213
- /**
214
- * 결과 제한
215
- * @param {number} count - 제한 수
216
- * @returns {this}
217
- */
218
- limit(count) {
219
- this._native.limit(count);
220
- return this;
221
- }
222
-
223
- /**
224
- * 결과 오프셋 (페이지네이션용)
225
- * @param {number} count - 건너뛸 행 수
226
- * @returns {this}
227
- */
228
- offset(count) {
229
- this._native.offset(count);
230
- return this;
231
- }
232
-
233
- /**
234
- * 정렬
235
- * @param {string} field - 정렬 필드
236
- * @param {'ASC'|'DESC'} [direction='ASC'] - 정렬 방향
237
- * @returns {this}
238
- */
239
- orderBy(field, direction = 'ASC') {
240
- this._native.orderBy(field, direction);
241
- return this;
242
- }
243
-
244
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
245
- // 📝 CRUD
246
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
247
-
248
- /**
249
- * SELECT 쿼리 실행
250
- * @returns {Promise<any[]>} 결과 배열
251
- */
252
- async get() {
253
- try {
254
- return await this._native.get();
255
- } finally {
256
- this._native.reset();
257
- }
258
- }
259
-
260
- /**
261
- * INSERT 쿼리 실행
262
- * @param {object} data - 삽입할 데이터
263
- * @returns {Promise<object>} { status: 'ok', last_insert_id: ... }
264
- */
265
- async save(data) {
266
- const schema = this.constructor.schema;
267
- data = await validateForSave(schema, data);
268
-
269
- // MongoDB 기본값 주입 (MariaDB는 DDL DEFAULT를 DB 엔진이 자동 처리)
270
- if (this.store.startsWith('mongo')) {
271
- applyMongoDefaults(schema, data);
272
- }
273
-
274
- try {
275
- return await this._native.save(JSON.stringify(data));
276
- } finally {
277
- this._native.reset();
278
- }
279
- }
280
-
281
- /**
282
- * UPDATE 쿼리 실행 (where 조건 필수)
283
- * @param {object} data - 수정할 데이터
284
- * @returns {Promise<object>} { status: 'ok', rows_affected: ... }
285
- */
286
- async update(data) {
287
- const schema = this.constructor.schema;
288
- data = await validateForUpdate(schema, data);
289
-
290
- try {
291
- return await this._native.update(JSON.stringify(data));
292
- } finally {
293
- this._native.reset();
294
- }
295
- }
296
-
297
- /**
298
- * DELETE 쿼리 실행 (where 조건 필수)
299
- * @returns {Promise<object>} { status: 'ok', rows_affected: ... }
300
- */
301
- async delete() {
302
- try {
303
- return await this._native.delete();
304
- } finally {
305
- this._native.reset();
306
- }
307
- }
308
-
309
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
310
- // 🔧 편의 메서드 (Convenience)
311
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
312
-
313
- /**
314
- * 첫 번째 행만 반환 (limit 1 + 배열 해제)
315
- * @returns {Promise<object|null>} 첫 번째 결과 또는 null
316
- */
317
- async first() {
318
- this.limit(1);
319
- const rows = await this.get();
320
- return rows[0] || null;
321
- }
322
-
323
- /**
324
- * 집계 공통 헬퍼 (내부용)
325
- * @param {string} fn - 집계 함수 ('count' | 'sum' | 'avg' | 'min' | 'max')
326
- * @param {string} field - 대상 필드명 ('*' for count)
327
- * @returns {Promise<number>} 집계 결과
328
- * @private
329
- */
330
- async _aggregate(fn, field) {
331
- try {
332
- const row = await this._native.aggregate(fn, field);
333
- return row?.result || 0;
334
- } finally {
335
- this._native.reset();
336
- }
337
- }
338
-
339
- /**
340
- * 행 수 카운트 (SQL / MongoDB 모두 지원)
341
- * @returns {Promise<number>} 전체 행 수
342
- */
343
- count() { return this._aggregate('count', '*'); }
344
-
345
- /**
346
- * SUM 집계 (SQL / MongoDB 모두 지원)
347
- * @param {string} field - 합산할 필드명
348
- * @returns {Promise<number>} 합계
349
- */
350
- sum(field) { return this._aggregate('sum', field); }
351
-
352
- /**
353
- * AVG 집계 (SQL / MongoDB 모두 지원)
354
- * @param {string} field - 평균낼 필드명
355
- * @returns {Promise<number>} 평균값
356
- */
357
- avg(field) { return this._aggregate('avg', field); }
358
-
359
- /**
360
- * MIN 집계 (SQL / MongoDB 모두 지원)
361
- * @param {string} field - 최솟값 필드명
362
- * @returns {Promise<number>} 최솟값
363
- */
364
- min(field) { return this._aggregate('min', field); }
365
-
366
- /**
367
- * MAX 집계 (SQL / MongoDB 모두 지원)
368
- * @param {string} field - 최댓값 필드명
369
- * @returns {Promise<number>} 최댓값
370
- */
371
- max(field) { return this._aggregate('max', field); }
372
-
373
- /**
374
- * Raw SQL 쿼리 실행
375
- * @param {string} sql - SQL 문자열 (placeholder: ?)
376
- * @param {Array} [params=[]] - 바인딩 파라미터
377
- * @returns {Promise<object[]>} 결과 배열
378
- */
379
- async query(sql, params = []) {
380
- try {
381
- return await this._native.query(sql, JSON.stringify(params));
382
- } finally {
383
- this._native.reset();
384
- }
385
- }
386
-
387
- /**
388
- * 스토어드 프로시저 호출
389
- * @param {string} name - 프로시저 이름
390
- * @param {Array} [params=[]] - 프로시저 인자
391
- * @returns {Promise<object[]>} 결과 배열
392
- */
393
- async callProcedure(name, params = []) {
394
- try {
395
- return await this._native.callProcedure(name, JSON.stringify(params));
396
- } finally {
397
- this._native.reset();
398
- }
399
- }
400
- }
401
-
402
- /**
403
- * AlloyMariaModel — AlloyModel의 명시적 별칭
404
- */
405
- export const AlloyMariaModel = AlloyModel;