@simplysm/orm-common 14.0.16 → 14.0.17

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 (75) hide show
  1. package/README.md +25 -19
  2. package/dist/db-context.d.ts +133 -0
  3. package/dist/db-context.d.ts.map +1 -0
  4. package/dist/db-context.js +325 -0
  5. package/dist/db-context.js.map +1 -0
  6. package/dist/ddl/initialize.d.ts +5 -3
  7. package/dist/ddl/initialize.d.ts.map +1 -1
  8. package/dist/ddl/initialize.js +39 -32
  9. package/dist/ddl/initialize.js.map +1 -1
  10. package/dist/exec/executable.js +1 -1
  11. package/dist/exec/executable.js.map +1 -1
  12. package/dist/exec/queryable.js +2 -2
  13. package/dist/exec/queryable.js.map +1 -1
  14. package/dist/exec/search-parser.d.ts.map +1 -1
  15. package/dist/exec/search-parser.js +2 -1
  16. package/dist/exec/search-parser.js.map +1 -1
  17. package/dist/expr/expr.d.ts +2 -2
  18. package/dist/expr/expr.js +2 -2
  19. package/dist/index.d.ts +1 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/query-builder/mssql/mssql-expr-renderer.js +1 -1
  24. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  25. package/dist/query-builder/mysql/mysql-expr-renderer.js +1 -1
  26. package/dist/query-builder/mysql/mysql-query-builder.js +1 -1
  27. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  28. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +7 -0
  29. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  30. package/dist/query-builder/postgresql/postgresql-query-builder.js +86 -16
  31. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  32. package/dist/schema/factory/relation-builder.d.ts +44 -67
  33. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  34. package/dist/schema/factory/relation-builder.js +37 -78
  35. package/dist/schema/factory/relation-builder.js.map +1 -1
  36. package/dist/schema/view-builder.d.ts +1 -1
  37. package/dist/schema/view-builder.d.ts.map +1 -1
  38. package/dist/schema/view-builder.js +0 -2
  39. package/dist/schema/view-builder.js.map +1 -1
  40. package/dist/types/column.js +1 -1
  41. package/dist/types/column.js.map +1 -1
  42. package/dist/types/db-context-def.d.ts +2 -42
  43. package/dist/types/db-context-def.d.ts.map +1 -1
  44. package/dist/utils/result-parser.js +31 -23
  45. package/dist/utils/result-parser.js.map +1 -1
  46. package/docs/core.md +110 -50
  47. package/docs/schema-builders.md +13 -19
  48. package/docs/types.md +2 -41
  49. package/package.json +3 -3
  50. package/src/db-context.ts +455 -0
  51. package/src/ddl/initialize.ts +49 -37
  52. package/src/exec/executable.ts +1 -1
  53. package/src/exec/queryable.ts +2 -2
  54. package/src/exec/search-parser.ts +2 -1
  55. package/src/expr/expr.ts +2 -2
  56. package/src/index.ts +2 -3
  57. package/src/query-builder/mssql/mssql-expr-renderer.ts +1 -1
  58. package/src/query-builder/mysql/mysql-expr-renderer.ts +1 -1
  59. package/src/query-builder/mysql/mysql-query-builder.ts +1 -1
  60. package/src/query-builder/postgresql/postgresql-query-builder.ts +93 -14
  61. package/src/schema/factory/relation-builder.ts +56 -87
  62. package/src/schema/view-builder.ts +1 -3
  63. package/src/types/column.ts +1 -1
  64. package/src/types/db-context-def.ts +2 -60
  65. package/src/utils/result-parser.ts +29 -22
  66. package/dist/create-db-context.d.ts +0 -34
  67. package/dist/create-db-context.d.ts.map +0 -1
  68. package/dist/create-db-context.js +0 -329
  69. package/dist/create-db-context.js.map +0 -1
  70. package/dist/define-db-context.d.ts +0 -15
  71. package/dist/define-db-context.d.ts.map +0 -1
  72. package/dist/define-db-context.js +0 -12
  73. package/dist/define-db-context.js.map +0 -1
  74. package/src/create-db-context.ts +0 -409
  75. package/src/define-db-context.ts +0 -28
@@ -0,0 +1,455 @@
1
+ import type { DbContextBase, DbContextStatus } from "./types/db-context-def";
2
+ import type { DataRecord, DbContextExecutor, IsolationLevel, Migration, ResultMeta } from "./types/db";
3
+ import type { QueryDef, QueryDefObjectName } from "./types/query-def";
4
+ import { DDL_TYPES } from "./types/query-def";
5
+
6
+ const DDL_TYPE_SET: ReadonlySet<string> = new Set(DDL_TYPES);
7
+ import { DbErrorCode, DbTransactionError } from "./errors/db-transaction-error";
8
+ import { TableBuilder } from "./schema/table-builder";
9
+ import { ViewBuilder } from "./schema/view-builder";
10
+ import type { ProcedureBuilder } from "./schema/procedure-builder";
11
+ import { queryable as createQueryable } from "./exec/queryable";
12
+ import type { Queryable } from "./exec/queryable";
13
+ import { executable as createExecutable } from "./exec/executable";
14
+ import type { Executable } from "./exec/executable";
15
+
16
+ // DDL import
17
+ import * as tableDdl from "./ddl/table-ddl";
18
+ import { getQueryDefObjectName as getQueryDefObjectNameImpl } from "./ddl/table-ddl";
19
+ import * as columnDdl from "./ddl/column-ddl";
20
+ import * as relationDdl from "./ddl/relation-ddl";
21
+ import * as schemaDdl from "./ddl/schema-ddl";
22
+ import {
23
+ initialize as initializeImpl,
24
+ validateRelations as validateRelationsImpl,
25
+ } from "./ddl/initialize";
26
+
27
+ import type { ColumnBuilder } from "./schema/factory/column-builder";
28
+ import type { ForeignKeyBuilder } from "./schema/factory/relation-builder";
29
+ import type { IndexBuilder } from "./schema/factory/index-builder";
30
+ import { _Migration } from "./models/system-migration";
31
+
32
+ export const SD_BUILDER = Symbol("sdBuilder");
33
+
34
+ /**
35
+ * DbContext base class
36
+ *
37
+ * 테이블/뷰/프로시저를 class 프로퍼티로 등록하고,
38
+ * 연결/트랜잭션/DDL/초기화를 제공한다.
39
+ *
40
+ * defineDbContext/createDbContext의 class 기반 대체.
41
+ * 각 프로퍼티가 독립 직렬화되어 40+ 테이블에서도 TS7056이 발생하지 않는다.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * class MainDb extends DbContext {
46
+ * user = this.queryable(User);
47
+ * post = this.queryable(Post);
48
+ * activeUsers = this.queryable(ActiveUsers);
49
+ * getUserById = this.executable(GetUserById);
50
+ *
51
+ * migrations = [{ name: "001", up: async (db) => { ... } }];
52
+ * }
53
+ *
54
+ * const db = new MainDb(executor, { database: "mydb" });
55
+ * await db.connect(async () => {
56
+ * const users = await db.user().execute();
57
+ * });
58
+ * ```
59
+ */
60
+ export abstract class DbContext implements DbContextBase {
61
+ // ── 상태 ──
62
+ status: DbContextStatus = "ready";
63
+ private _aliasCounter = 0;
64
+ private _relationsValidated = false;
65
+
66
+ // ── 시스템 테이블 ──
67
+ _migration = this.queryable(_Migration);
68
+
69
+ constructor(
70
+ private readonly _executor: DbContextExecutor,
71
+ private readonly _opt: { database: string; schema?: string },
72
+ ) {}
73
+
74
+ // ── DbContextBase 구현 ──
75
+
76
+ get database(): string | undefined {
77
+ return this._opt.database;
78
+ }
79
+
80
+ get schema(): string | undefined {
81
+ return this._opt.schema;
82
+ }
83
+
84
+ getNextAlias(): string {
85
+ return `T${++this._aliasCounter}`;
86
+ }
87
+
88
+ resetAliasCounter(): void {
89
+ this._aliasCounter = 0;
90
+ }
91
+
92
+ executeDefs<T = DataRecord>(
93
+ defs: QueryDef[],
94
+ resultMetas?: (ResultMeta | undefined)[],
95
+ ): Promise<T[][]> {
96
+ if (this.status === "transact" && defs.some((d) => DDL_TYPE_SET.has(d.type))) {
97
+ throw new Error("TRANSACTION 상태에서는 DDL을 실행할 수 없습니다.");
98
+ }
99
+ return this._executor.executeDefs(defs, resultMetas);
100
+ }
101
+
102
+ getQueryDefObjectName(
103
+ tableOrView: TableBuilder<any, any> | ViewBuilder<any, any, any>,
104
+ ): QueryDefObjectName {
105
+ return getQueryDefObjectNameImpl(this, tableOrView);
106
+ }
107
+
108
+ async switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void> {
109
+ await this.executeDefs([schemaDdl.getSwitchFkQueryDef(table, enabled)]);
110
+ }
111
+
112
+ // ── 등록 메서드 ──
113
+
114
+ protected queryable<T extends TableBuilder<any, any> | ViewBuilder<any, any, any>>(
115
+ builder: T,
116
+ ): () => Queryable<T["$inferSelect"], T extends TableBuilder<any, any> ? T : never> {
117
+ const fn = createQueryable(this, builder);
118
+ Object.defineProperty(fn, SD_BUILDER, { value: builder });
119
+ return fn;
120
+ }
121
+
122
+ protected executable<T extends ProcedureBuilder<any, any>>(
123
+ builder: T,
124
+ ): () => Executable<T["$params"], T["$returns"]> {
125
+ const fn = createExecutable(this, builder);
126
+ Object.defineProperty(fn, SD_BUILDER, { value: builder });
127
+ return fn;
128
+ }
129
+
130
+ // ── 연결 관리 ──
131
+
132
+ async connect<TResult>(
133
+ fn: () => Promise<TResult>,
134
+ isolationLevel?: IsolationLevel,
135
+ ): Promise<TResult> {
136
+ if (this.status !== "ready") {
137
+ throw new Error(
138
+ `이미 ${this.status === "connect" ? "CONNECT" : "TRANSACTION"} 상태입니다.`,
139
+ );
140
+ }
141
+ if (!this._relationsValidated) {
142
+ validateRelationsImpl(this);
143
+ this._relationsValidated = true;
144
+ }
145
+ this.resetAliasCounter();
146
+
147
+ await this._executor.connect();
148
+ this.status = "connect";
149
+
150
+ try {
151
+ await this._executor.beginTransaction(isolationLevel);
152
+ this.status = "transact";
153
+
154
+ let result: TResult;
155
+ try {
156
+ result = await fn();
157
+
158
+ await this._executor.commitTransaction();
159
+ this.status = "connect";
160
+ } catch (err) {
161
+ try {
162
+ await this._executor.rollbackTransaction();
163
+ } catch (err1) {
164
+ if (
165
+ !(err1 instanceof DbTransactionError) ||
166
+ err1.code !== DbErrorCode.NO_ACTIVE_TRANSACTION
167
+ ) {
168
+ (err as Error).cause = err1;
169
+ }
170
+ }
171
+ this.status = "connect";
172
+ throw err;
173
+ }
174
+
175
+ return result;
176
+ } finally {
177
+ try {
178
+ await this._executor.close();
179
+ } finally {
180
+ this.status = "ready";
181
+ }
182
+ }
183
+ }
184
+
185
+ async connectWithoutTransaction<TResult>(callback: () => Promise<TResult>): Promise<TResult> {
186
+ if (this.status !== "ready") {
187
+ throw new Error(
188
+ `이미 ${this.status === "connect" ? "CONNECT" : "TRANSACTION"} 상태입니다.`,
189
+ );
190
+ }
191
+ if (!this._relationsValidated) {
192
+ validateRelationsImpl(this);
193
+ this._relationsValidated = true;
194
+ }
195
+ this.resetAliasCounter();
196
+
197
+ await this._executor.connect();
198
+ this.status = "connect";
199
+
200
+ try {
201
+ return await callback();
202
+ } finally {
203
+ try {
204
+ await this._executor.close();
205
+ } finally {
206
+ this.status = "ready";
207
+ }
208
+ }
209
+ }
210
+
211
+ async transaction<TResult>(
212
+ fn: () => Promise<TResult>,
213
+ isolationLevel?: IsolationLevel,
214
+ ): Promise<TResult> {
215
+ if (this.status === "transact") {
216
+ throw new Error("이미 TRANSACTION 상태입니다.");
217
+ }
218
+
219
+ await this._executor.beginTransaction(isolationLevel);
220
+ this.status = "transact";
221
+
222
+ let result: TResult;
223
+ try {
224
+ result = await fn();
225
+
226
+ await this._executor.commitTransaction();
227
+ this.status = "connect";
228
+ } catch (err) {
229
+ try {
230
+ await this._executor.rollbackTransaction();
231
+ } catch (err1) {
232
+ if (
233
+ !(err1 instanceof DbTransactionError) ||
234
+ err1.code !== DbErrorCode.NO_ACTIVE_TRANSACTION
235
+ ) {
236
+ (err as Error).cause = err1;
237
+ }
238
+ }
239
+ this.status = "connect";
240
+ throw err;
241
+ }
242
+
243
+ return result;
244
+ }
245
+
246
+ // ── DDL 실행 메서드 ──
247
+
248
+ async createTable(table: TableBuilder<any, any>): Promise<void> {
249
+ await this.executeDefs([tableDdl.getCreateTableQueryDef(this, table)]);
250
+ }
251
+
252
+ async dropTable(table: QueryDefObjectName): Promise<void> {
253
+ await this.executeDefs([tableDdl.getDropTableQueryDef(table)]);
254
+ }
255
+
256
+ async renameTable(table: QueryDefObjectName, newName: string): Promise<void> {
257
+ await this.executeDefs([tableDdl.getRenameTableQueryDef(table, newName)]);
258
+ }
259
+
260
+ async createView(view: ViewBuilder<any, any, any>): Promise<void> {
261
+ await this.executeDefs([tableDdl.getCreateViewQueryDef(this as any, view)]);
262
+ }
263
+
264
+ async dropView(view: QueryDefObjectName): Promise<void> {
265
+ await this.executeDefs([tableDdl.getDropViewQueryDef(view)]);
266
+ }
267
+
268
+ async createProc(procedure: ProcedureBuilder<any, any>): Promise<void> {
269
+ await this.executeDefs([tableDdl.getCreateProcQueryDef(this, procedure)]);
270
+ }
271
+
272
+ async dropProc(procedure: QueryDefObjectName): Promise<void> {
273
+ await this.executeDefs([tableDdl.getDropProcQueryDef(procedure)]);
274
+ }
275
+
276
+ async addColumn(
277
+ table: QueryDefObjectName,
278
+ columnName: string,
279
+ column: ColumnBuilder<any, any>,
280
+ ): Promise<void> {
281
+ await this.executeDefs([columnDdl.getAddColumnQueryDef(table, columnName, column)]);
282
+ }
283
+
284
+ async dropColumn(table: QueryDefObjectName, column: string): Promise<void> {
285
+ await this.executeDefs([columnDdl.getDropColumnQueryDef(table, column)]);
286
+ }
287
+
288
+ async modifyColumn(
289
+ table: QueryDefObjectName,
290
+ columnName: string,
291
+ column: ColumnBuilder<any, any>,
292
+ ): Promise<void> {
293
+ await this.executeDefs([columnDdl.getModifyColumnQueryDef(table, columnName, column)]);
294
+ }
295
+
296
+ async renameColumn(table: QueryDefObjectName, column: string, newName: string): Promise<void> {
297
+ await this.executeDefs([columnDdl.getRenameColumnQueryDef(table, column, newName)]);
298
+ }
299
+
300
+ async addPrimaryKey(table: QueryDefObjectName, columns: string[]): Promise<void> {
301
+ await this.executeDefs([relationDdl.getAddPrimaryKeyQueryDef(table, columns)]);
302
+ }
303
+
304
+ async dropPrimaryKey(table: QueryDefObjectName): Promise<void> {
305
+ await this.executeDefs([relationDdl.getDropPrimaryKeyQueryDef(table)]);
306
+ }
307
+
308
+ async addForeignKey(
309
+ table: QueryDefObjectName,
310
+ relationName: string,
311
+ relationDef: ForeignKeyBuilder<any, any>,
312
+ ): Promise<void> {
313
+ await this.executeDefs([
314
+ relationDdl.getAddForeignKeyQueryDef(this, table, relationName, relationDef),
315
+ ]);
316
+ }
317
+
318
+ async addIndex(table: QueryDefObjectName, indexBuilder: IndexBuilder<string[]>): Promise<void> {
319
+ await this.executeDefs([relationDdl.getAddIndexQueryDef(table, indexBuilder)]);
320
+ }
321
+
322
+ async dropForeignKey(table: QueryDefObjectName, relationName: string): Promise<void> {
323
+ await this.executeDefs([relationDdl.getDropForeignKeyQueryDef(table, relationName)]);
324
+ }
325
+
326
+ async dropIndex(table: QueryDefObjectName, columns: string[]): Promise<void> {
327
+ await this.executeDefs([relationDdl.getDropIndexQueryDef(table, columns)]);
328
+ }
329
+
330
+ async clearSchema(params: { database: string; schema?: string }): Promise<void> {
331
+ await this.executeDefs([schemaDdl.getClearSchemaQueryDef(params)]);
332
+ }
333
+
334
+ async schemaExists(database: string, schema?: string): Promise<boolean> {
335
+ const result = await this.executeDefs([schemaDdl.getSchemaExistsQueryDef(database, schema)]);
336
+ return result[0].length > 0;
337
+ }
338
+
339
+ async truncate(table: QueryDefObjectName): Promise<void> {
340
+ await this.executeDefs([schemaDdl.getTruncateQueryDef(table)]);
341
+ }
342
+
343
+ // ── DDL QueryDef 생성기 ──
344
+
345
+ getCreateTableQueryDef(table: TableBuilder<any, any>): QueryDef {
346
+ return tableDdl.getCreateTableQueryDef(this, table);
347
+ }
348
+
349
+ getCreateViewQueryDef(view: ViewBuilder<any, any, any>): QueryDef {
350
+ return tableDdl.getCreateViewQueryDef(this as any, view);
351
+ }
352
+
353
+ getCreateProcQueryDef(procedure: ProcedureBuilder<any, any>): QueryDef {
354
+ return tableDdl.getCreateProcQueryDef(this, procedure);
355
+ }
356
+
357
+ getCreateObjectQueryDef(
358
+ builder: TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>,
359
+ ): QueryDef {
360
+ return tableDdl.getCreateObjectQueryDef(this as any, builder);
361
+ }
362
+
363
+ getDropTableQueryDef(table: QueryDefObjectName): QueryDef {
364
+ return tableDdl.getDropTableQueryDef(table);
365
+ }
366
+
367
+ getRenameTableQueryDef(table: QueryDefObjectName, newName: string): QueryDef {
368
+ return tableDdl.getRenameTableQueryDef(table, newName);
369
+ }
370
+
371
+ getDropViewQueryDef(view: QueryDefObjectName): QueryDef {
372
+ return tableDdl.getDropViewQueryDef(view);
373
+ }
374
+
375
+ getDropProcQueryDef(procedure: QueryDefObjectName): QueryDef {
376
+ return tableDdl.getDropProcQueryDef(procedure);
377
+ }
378
+
379
+ getAddColumnQueryDef(
380
+ table: QueryDefObjectName,
381
+ columnName: string,
382
+ column: ColumnBuilder<any, any>,
383
+ ): QueryDef {
384
+ return columnDdl.getAddColumnQueryDef(table, columnName, column);
385
+ }
386
+
387
+ getDropColumnQueryDef(table: QueryDefObjectName, column: string): QueryDef {
388
+ return columnDdl.getDropColumnQueryDef(table, column);
389
+ }
390
+
391
+ getModifyColumnQueryDef(
392
+ table: QueryDefObjectName,
393
+ columnName: string,
394
+ column: ColumnBuilder<any, any>,
395
+ ): QueryDef {
396
+ return columnDdl.getModifyColumnQueryDef(table, columnName, column);
397
+ }
398
+
399
+ getRenameColumnQueryDef(table: QueryDefObjectName, column: string, newName: string): QueryDef {
400
+ return columnDdl.getRenameColumnQueryDef(table, column, newName);
401
+ }
402
+
403
+ getAddPrimaryKeyQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef {
404
+ return relationDdl.getAddPrimaryKeyQueryDef(table, columns);
405
+ }
406
+
407
+ getDropPrimaryKeyQueryDef(table: QueryDefObjectName): QueryDef {
408
+ return relationDdl.getDropPrimaryKeyQueryDef(table);
409
+ }
410
+
411
+ getAddForeignKeyQueryDef(
412
+ table: QueryDefObjectName,
413
+ relationName: string,
414
+ relationDef: ForeignKeyBuilder<any, any>,
415
+ ): QueryDef {
416
+ return relationDdl.getAddForeignKeyQueryDef(this, table, relationName, relationDef);
417
+ }
418
+
419
+ getAddIndexQueryDef(table: QueryDefObjectName, indexBuilder: IndexBuilder<string[]>): QueryDef {
420
+ return relationDdl.getAddIndexQueryDef(table, indexBuilder);
421
+ }
422
+
423
+ getDropForeignKeyQueryDef(table: QueryDefObjectName, relationName: string): QueryDef {
424
+ return relationDdl.getDropForeignKeyQueryDef(table, relationName);
425
+ }
426
+
427
+ getDropIndexQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef {
428
+ return relationDdl.getDropIndexQueryDef(table, columns);
429
+ }
430
+
431
+ getClearSchemaQueryDef(params: { database: string; schema?: string }): QueryDef {
432
+ return schemaDdl.getClearSchemaQueryDef(params);
433
+ }
434
+
435
+ getSchemaExistsQueryDef(database: string, schema?: string): QueryDef {
436
+ return schemaDdl.getSchemaExistsQueryDef(database, schema);
437
+ }
438
+
439
+ getTruncateQueryDef(table: QueryDefObjectName): QueryDef {
440
+ return schemaDdl.getTruncateQueryDef(table);
441
+ }
442
+
443
+ getSwitchFkQueryDef(table: QueryDefObjectName, enabled: boolean): QueryDef {
444
+ return schemaDdl.getSwitchFkQueryDef(table, enabled);
445
+ }
446
+
447
+ // ── 초기화 ──
448
+
449
+ async initialize(options?: { dbs?: string[]; force?: boolean }): Promise<void> {
450
+ await initializeImpl(this, options);
451
+ }
452
+
453
+ /** 마이그레이션 정의 — 서브클래스에서 오버라이드 */
454
+ migrations: Migration[] = [];
455
+ }
@@ -1,6 +1,7 @@
1
- import type { DbContextBase, DbContextDef, DbContextDdlMethods } from "../types/db-context-def";
1
+ import type { DbContextBase, DbContextDdlMethods } from "../types/db-context-def";
2
2
  import type { Queryable } from "../exec/queryable";
3
3
  import type { QueryDef } from "../types/query-def";
4
+ import type { Migration } from "../types/db";
4
5
  import { TableBuilder } from "../schema/table-builder";
5
6
  import { ViewBuilder } from "../schema/view-builder";
6
7
  import { ProcedureBuilder } from "../schema/procedure-builder";
@@ -13,6 +14,7 @@ import {
13
14
  import { getCreateObjectQueryDef } from "./table-ddl";
14
15
  import { getAddForeignKeyQueryDef, getAddIndexQueryDef } from "./relation-ddl";
15
16
  import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
17
+ import { SD_BUILDER } from "../db-context";
16
18
 
17
19
  /**
18
20
  * Code First 데이터베이스 초기화
@@ -35,8 +37,11 @@ import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
35
37
  * - _Migration 테이블 있음: 미적용 migration만 실행
36
38
  */
37
39
  export async function initialize(
38
- db: DbContextBase & DbContextDdlMethods & { _migration: () => Queryable<{ code: string }, any> },
39
- def: DbContextDef<any, any, any>,
40
+ db: DbContextBase &
41
+ DbContextDdlMethods & {
42
+ _migration: () => Queryable<{ code: string }, any>;
43
+ migrations: Migration[];
44
+ },
40
45
  options?: { dbs?: string[]; force?: boolean },
41
46
  ): Promise<void> {
42
47
  const dbNames = options?.dbs ?? (db.database !== undefined ? [db.database] : []);
@@ -45,6 +50,8 @@ export async function initialize(
45
50
  }
46
51
 
47
52
  const force = options?.force ?? false;
53
+ const builders = collectBuilders(db);
54
+ const migrations = db.migrations;
48
55
 
49
56
  // 1. DB 존재 여부 확인
50
57
  for (const dbName of dbNames) {
@@ -62,14 +69,18 @@ export async function initialize(
62
69
  const clearDef = getClearSchemaQueryDef({ database: dbName, schema: db.schema });
63
70
  await db.executeDefs([clearDef]);
64
71
  }
65
- await createAllObjects(db, def);
72
+
73
+ // 각 대상 DB에 객체 생성
74
+ for (const dbName of dbNames) {
75
+ await createAllObjects(db, builders, dbName);
76
+ }
66
77
 
67
78
  // 모든 migration을 "적용됨"으로 등록
68
- if (def.meta.migrations.length > 0) {
69
- await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
79
+ if (migrations.length > 0) {
80
+ await db._migration().insert(migrations.map((m) => ({ code: m.name })));
70
81
  }
71
82
  } else {
72
- // 3. Migration 기반 초기화
83
+ // 3. Migration 기반 초기화 — 각 대상 DB에 대해 수행
73
84
  let appliedMigrations: { code: string }[] | undefined;
74
85
  try {
75
86
  appliedMigrations = await db._migration().execute();
@@ -81,17 +92,19 @@ export async function initialize(
81
92
  }
82
93
 
83
94
  if (appliedMigrations == null) {
84
- // 새 환경: 전체 생성
85
- await createAllObjects(db, def);
95
+ // 새 환경: 각 대상 DB에 전체 생성
96
+ for (const dbName of dbNames) {
97
+ await createAllObjects(db, builders, dbName);
98
+ }
86
99
 
87
100
  // 모든 migration을 "적용됨"으로 등록
88
- if (def.meta.migrations.length > 0) {
89
- await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
101
+ if (migrations.length > 0) {
102
+ await db._migration().insert(migrations.map((m) => ({ code: m.name })));
90
103
  }
91
104
  } else {
92
105
  // 기존 환경: 미적용 migration만 실행
93
106
  const appliedCodes = new Set(appliedMigrations.map((m) => m.code));
94
- const pendingMigrations = def.meta.migrations.filter((m) => !appliedCodes.has(m.name));
107
+ const pendingMigrations = migrations.filter((m) => !appliedCodes.has(m.name));
95
108
 
96
109
  for (const migration of pendingMigrations) {
97
110
  await migration.up(db);
@@ -103,15 +116,26 @@ export async function initialize(
103
116
 
104
117
  /**
105
118
  * 모든 객체 생성 (table/view/procedure/FK/index)
119
+ *
120
+ * @param db - DbContext 인스턴스
121
+ * @param builders - 생성할 builder 목록
122
+ * @param targetDatabase - 대상 데이터베이스. builder에 database가 지정된 경우 해당 builder의 database가 targetDatabase와
123
+ * 일치할 때만 생성한다. 미지정 builder는 targetDatabase에 생성한다.
106
124
  */
107
125
  async function createAllObjects(
108
126
  db: DbContextBase,
109
- def: DbContextDef<any, any, any>,
127
+ builders: (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[],
128
+ targetDatabase: string,
110
129
  ): Promise<void> {
130
+ // targetDatabase에 해당하는 builder만 필터링
131
+ const targetBuilders = builders.filter((b) => {
132
+ const builderDb = b.meta.database;
133
+ return builderDb == null || builderDb === targetDatabase;
134
+ });
135
+
111
136
  // 1. Table/View/Procedure 생성
112
- const builders = getBuilders(def);
113
137
  const createDefs: QueryDef[] = [];
114
- for (const builder of builders) {
138
+ for (const builder of targetBuilders) {
115
139
  createDefs.push(getCreateObjectQueryDef(db, builder));
116
140
  }
117
141
  if (createDefs.length > 0) {
@@ -119,7 +143,7 @@ async function createAllObjects(
119
143
  }
120
144
 
121
145
  // 2. FK 생성 (TableBuilder만)
122
- const tables = builders.filter((b) => b instanceof TableBuilder);
146
+ const tables = targetBuilders.filter((b) => b instanceof TableBuilder);
123
147
  const addFkDefs: QueryDef[] = [];
124
148
  for (const table of tables) {
125
149
  const relations = table.meta.relations;
@@ -153,10 +177,10 @@ async function createAllObjects(
153
177
  }
154
178
 
155
179
  /**
156
- * DbContext에서 모든 builder 수집 (Table/View/Procedure)
180
+ * DbContext 인스턴스에서 SD_BUILDER 태그가 붙은 builder 수집
157
181
  */
158
- function getBuilders(
159
- def: DbContextDef<any, any, any>,
182
+ function collectBuilders(
183
+ dbContext: object,
160
184
  ): (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[] {
161
185
  const builders: (
162
186
  | TableBuilder<any, any>
@@ -164,22 +188,10 @@ function getBuilders(
164
188
  | ProcedureBuilder<any, any>
165
189
  )[] = [];
166
190
 
167
- // Tables
168
- const tables: TableBuilder<any, any>[] = Object.values(def.meta.tables);
169
- for (const table of tables) {
170
- builders.push(table);
171
- }
172
-
173
- // Views
174
- const views: ViewBuilder<any, any, any>[] = Object.values(def.meta.views);
175
- for (const view of views) {
176
- builders.push(view);
177
- }
178
-
179
- // Procedures
180
- const procs: ProcedureBuilder<any, any>[] = Object.values(def.meta.procedures);
181
- for (const proc of procs) {
182
- builders.push(proc);
191
+ for (const value of Object.values(dbContext)) {
192
+ if (typeof value === "function" && SD_BUILDER in value) {
193
+ builders.push(value[SD_BUILDER as keyof typeof value] as TableBuilder<any, any>);
194
+ }
183
195
  }
184
196
 
185
197
  return builders;
@@ -189,8 +201,8 @@ function getBuilders(
189
201
  * ForeignKeyTarget/RelationKeyTarget 관계 검증
190
202
  * - targetTableFn()이 반환하는 테이블에 relationName과 일치하는 FK/RelationKey가 있는지 확인
191
203
  */
192
- export function validateRelations(def: DbContextDef<any, any, any>): void {
193
- const builders = getBuilders(def);
204
+ export function validateRelations(dbContext: object): void {
205
+ const builders = collectBuilders(dbContext);
194
206
  const tables = builders.filter((b) => b instanceof TableBuilder);
195
207
 
196
208
  for (const table of tables) {
@@ -47,7 +47,7 @@ export class Executable<TParams extends ColumnBuilderRecord, TReturns extends Co
47
47
  params:
48
48
  params && meta.params
49
49
  ? Object.fromEntries(
50
- Object.keys(params).map((key) => [
50
+ Object.keys(meta.params).map((key) => [
51
51
  key,
52
52
  params[key] instanceof ExprUnit
53
53
  ? params[key].expr
@@ -1127,8 +1127,8 @@ export class Queryable<
1127
1127
  * ```
1128
1128
  */
1129
1129
  async exists(): Promise<boolean> {
1130
- const count = await this.count();
1131
- return count > 0;
1130
+ const result = await this.top(1).execute();
1131
+ return result.length > 0;
1132
1132
  }
1133
1133
 
1134
1134
  getSelectQueryDef(): SelectQueryDef {
@@ -96,7 +96,8 @@ export function parseSearchQuery(searchText: string): ParsedSearchQuery {
96
96
  .replace(/\\%/g, ESC.PERCENT)
97
97
  .replace(/\\"/g, ESC.QUOTE)
98
98
  .replace(/\\\+/g, ESC.PLUS)
99
- .replace(/\\-/g, ESC.MINUS);
99
+ .replace(/\\-/g, ESC.MINUS)
100
+ .replace(/\\(.)/g, "$1"); // 미정의 이스케이프: backslash 제거, 리터럴 문자 유지
100
101
 
101
102
  // 따옴표 구간 추출
102
103
  const quotedRegex = /([+-]?)"([^"]*)"/g;
package/src/expr/expr.ts CHANGED
@@ -912,7 +912,7 @@ export const expr = {
912
912
  * @example
913
913
  * ```typescript
914
914
  * db.order().select((o) => ({
915
- * pages: expr.ceil(expr.divide(o.itemCount, 10)),
915
+ * pages: expr.ceil(expr.multiply(o.itemCount, 0.1)),
916
916
  * }))
917
917
  * // SELECT CEILING(itemCount / 10) AS pages
918
918
  * // 25 / 10 = 2.5 → 3
@@ -934,7 +934,7 @@ export const expr = {
934
934
  * @example
935
935
  * ```typescript
936
936
  * db.user().select((u) => ({
937
- * ageGroup: expr.floor(expr.divide(u.age, 10)),
937
+ * ageGroup: expr.floor(expr.multiply(u.age, 0.1)),
938
938
  * }))
939
939
  * // SELECT FLOOR(age / 10) AS ageGroup
940
940
  * // 25 / 10 = 2.5 → 2
package/src/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  //#region ========== Core ==========
2
2
 
3
- // 함수형 API (권장)
4
- export * from "./define-db-context";
5
- export * from "./create-db-context";
3
+ // Class 기반 API
4
+ export * from "./db-context";
6
5
  export * from "./types/db-context-def";
7
6
  export * from "./errors/db-transaction-error";
8
7