sonamu 0.5.5 → 0.5.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 (52) hide show
  1. package/dist/api/decorators.d.ts +1 -0
  2. package/dist/api/decorators.d.ts.map +1 -1
  3. package/dist/api/decorators.js +1 -1
  4. package/dist/api/decorators.js.map +1 -1
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +1 -1
  7. package/dist/api/sonamu.js.map +1 -1
  8. package/dist/bin/build-config.d.ts +4 -0
  9. package/dist/bin/build-config.d.ts.map +1 -1
  10. package/dist/bin/build-config.js +1 -1
  11. package/dist/bin/build-config.js.map +1 -1
  12. package/dist/bin/cli-wrapper.js +1 -1
  13. package/dist/bin/cli-wrapper.js.map +1 -1
  14. package/dist/database/db.d.ts +3 -0
  15. package/dist/database/db.d.ts.map +1 -1
  16. package/dist/database/db.js +1 -1
  17. package/dist/database/db.js.map +1 -1
  18. package/dist/database/puri-wrapper.d.ts +22 -10
  19. package/dist/database/puri-wrapper.d.ts.map +1 -1
  20. package/dist/database/puri-wrapper.js +1 -1
  21. package/dist/database/puri-wrapper.js.map +1 -1
  22. package/dist/database/puri.d.ts +91 -65
  23. package/dist/database/puri.d.ts.map +1 -1
  24. package/dist/database/puri.js +1 -1
  25. package/dist/database/puri.js.map +1 -1
  26. package/dist/database/puri.types.d.ts +28 -42
  27. package/dist/database/puri.types.d.ts.map +1 -1
  28. package/dist/database/transaction-context.d.ts +3 -3
  29. package/dist/database/transaction-context.d.ts.map +1 -1
  30. package/dist/database/transaction-context.js.map +1 -1
  31. package/dist/templates/service.template.d.ts.map +1 -1
  32. package/dist/templates/service.template.js +1 -1
  33. package/dist/templates/service.template.js.map +1 -1
  34. package/dist/types/types.d.ts +1 -1
  35. package/dist/types/types.d.ts.map +1 -1
  36. package/dist/types/types.js.map +1 -1
  37. package/package.json +1 -2
  38. package/src/api/decorators.ts +14 -5
  39. package/src/api/sonamu.ts +21 -20
  40. package/src/bin/build-config.ts +7 -3
  41. package/src/bin/cli-wrapper.ts +31 -12
  42. package/src/database/db.ts +44 -9
  43. package/src/database/puri-wrapper.ts +104 -26
  44. package/src/database/puri.ts +434 -543
  45. package/src/database/puri.types.ts +99 -200
  46. package/src/database/transaction-context.ts +4 -4
  47. package/src/templates/service.template.ts +10 -1
  48. package/src/types/types.ts +1 -1
  49. package/dist/entity/migrator.d.ts +0 -135
  50. package/dist/entity/migrator.d.ts.map +0 -1
  51. package/dist/entity/migrator.js +0 -2
  52. package/dist/entity/migrator.js.map +0 -1
@@ -74,6 +74,25 @@ class DBClass {
74
74
  getDB(which: DBPreset): Knex {
75
75
  const dbConfig = Sonamu.dbConfig;
76
76
 
77
+ // 테스트 트랜잭션 격리
78
+ if (process.env.NODE_ENV === "test") {
79
+ if (this.testTransaction) {
80
+ return this.testTransaction;
81
+ } else if (this.wdb) {
82
+ return this.wdb;
83
+ } else {
84
+ this["wdb"] = knex({
85
+ ...dbConfig["test"],
86
+ // 단일 풀
87
+ pool: {
88
+ min: 1,
89
+ max: 1,
90
+ },
91
+ });
92
+ return this["wdb"];
93
+ }
94
+ }
95
+
77
96
  const instanceName = which === "w" ? "wdb" : "rdb";
78
97
 
79
98
  if (!this[instanceName]) {
@@ -84,16 +103,14 @@ class DBClass {
84
103
  config =
85
104
  which === "w"
86
105
  ? dbConfig["development_master"]
87
- : dbConfig["development_slave"] ?? dbConfig["development_master"];
106
+ : (dbConfig["development_slave"] ??
107
+ dbConfig["development_master"]);
88
108
  break;
89
109
  case "production":
90
110
  config =
91
111
  which === "w"
92
112
  ? dbConfig["production_master"]
93
- : dbConfig["production_slave"] ?? dbConfig["production_master"];
94
- break;
95
- case "test":
96
- config = dbConfig["test"];
113
+ : (dbConfig["production_slave"] ?? dbConfig["production_master"]);
97
114
  break;
98
115
  default:
99
116
  throw new Error(
@@ -163,11 +180,17 @@ class DBClass {
163
180
  devSlaveOptions
164
181
  );
165
182
  // NOTE: fixture remote는 default connection의 DB를 override해선 안됨.
166
- const fixture_remote = _.merge({}, defaultKnexConfig, devMasterOptions, {
167
- connection: {
168
- database: `${config.database}_fixture_remote`,
183
+ const fixture_remote = _.merge(
184
+ {},
185
+ defaultKnexConfig,
186
+ devMasterOptions,
187
+ {
188
+ connection: {
189
+ database: `${config.database}_fixture_remote`,
190
+ },
169
191
  },
170
- }, config.environments?.remote_fixture);
192
+ config.environments?.remote_fixture
193
+ );
171
194
 
172
195
  // 프로덕션 환경 설정
173
196
  const prodMasterOptions = config.environments?.production ?? {};
@@ -190,5 +213,17 @@ class DBClass {
190
213
  production_slave,
191
214
  };
192
215
  }
216
+
217
+ // Test 환경에서 트랜잭션 사용
218
+ public testTransaction: Knex.Transaction | null = null;
219
+ async createTestTransaction(): Promise<Knex.Transaction> {
220
+ const db = this.getDB("w");
221
+ this.testTransaction = await db.transaction();
222
+ return this.testTransaction;
223
+ }
224
+ async clearTestTransaction(): Promise<void> {
225
+ await this.testTransaction?.rollback();
226
+ this.testTransaction = null;
227
+ }
193
228
  }
194
229
  export const DB = new DBClass();
@@ -5,8 +5,8 @@ import { DatabaseSchemaExtend } from "../types/types";
5
5
  import chalk from "chalk";
6
6
  import { DBPreset } from "./db";
7
7
 
8
- type TableName<DBSchema extends DatabaseSchemaExtend> = Extract<
9
- keyof DBSchema,
8
+ type TableName<TSchema extends DatabaseSchemaExtend> = Extract<
9
+ keyof TSchema,
10
10
  string
11
11
  >;
12
12
 
@@ -17,7 +17,7 @@ export type TransactionalOptions = {
17
17
  };
18
18
 
19
19
  export class PuriWrapper<
20
- DBSchema extends DatabaseSchemaExtend = DatabaseSchemaExtend,
20
+ TSchema extends DatabaseSchemaExtend = DatabaseSchemaExtend,
21
21
  > {
22
22
  constructor(
23
23
  public knex: Knex,
@@ -27,60 +27,138 @@ export class PuriWrapper<
27
27
  raw(sql: string): Knex.Raw {
28
28
  return this.knex.raw(sql);
29
29
  }
30
- // 기존: 테이블로 시작
31
- table<TTable extends TableName<DBSchema>>(
30
+
31
+ // 테이블명으로 시작
32
+ from<TTable extends keyof TSchema>(
32
33
  tableName: TTable
33
- ): Puri<DBSchema, TTable> {
34
- return new Puri(this.knex, tableName as any);
34
+ ): Puri<
35
+ TSchema,
36
+ Record<TTable, TSchema[TTable]>,
37
+ Omit<TSchema[TTable], "__fulltext__">
38
+ >;
39
+ // 테이블명 + Alias로 시작
40
+ from<TTable extends keyof TSchema, TAlias extends string>(spec: {
41
+ [K in TAlias]: TTable;
42
+ }): Puri<
43
+ TSchema,
44
+ Record<TAlias, TSchema[TTable]>,
45
+ Omit<TSchema[TTable], "__fulltext__">
46
+ >;
47
+ // 서브쿼리로 시작
48
+ from<TAlias extends string, TSubResult>(spec: {
49
+ [K in TAlias]: Puri<TSchema, any, TSubResult>;
50
+ }): Puri<
51
+ TSchema,
52
+ Record<TAlias, TSubResult>,
53
+ Omit<TSubResult, "__fulltext__">
54
+ >;
55
+ from(spec: any): any {
56
+ return new Puri(this.knex, spec);
35
57
  }
36
58
 
37
- // 새로 추가: 서브쿼리로 시작
38
- fromSubquery<TSubResult, TAlias extends string>(
39
- subquery: Puri<DBSchema, any, any, TSubResult, any>,
40
- alias: TAlias extends string ? TAlias : never
41
- ): Puri<DBSchema, TAlias, TSubResult, TSubResult, {}> {
42
- return new Puri(this.knex, subquery, alias);
59
+ // 테이블명으로 시작
60
+ table<TTable extends keyof TSchema>(
61
+ tableName: TTable
62
+ ): Puri<
63
+ TSchema,
64
+ Record<TTable, TSchema[TTable]>,
65
+ Omit<TSchema[TTable], "__fulltext__">
66
+ >;
67
+ // 테이블명 + Alias로 시작
68
+ table<TTable extends keyof TSchema, TAlias extends string>(spec: {
69
+ [K in TAlias]: TTable;
70
+ }): Puri<
71
+ TSchema,
72
+ Record<TAlias, TSchema[TTable]>,
73
+ Omit<TSchema[TTable], "__fulltext__">
74
+ >;
75
+ // 서브쿼리로 시작
76
+ table<TAlias extends string, TSubResult>(spec: {
77
+ [K in TAlias]: Puri<TSchema, any, TSubResult>;
78
+ }): Puri<
79
+ TSchema,
80
+ Record<TAlias, TSubResult>,
81
+ Omit<TSubResult, "__fulltext__">
82
+ >;
83
+ table(spec: any): any {
84
+ return new Puri(this.knex, spec);
43
85
  }
44
86
 
45
87
  async transaction<T>(
46
88
  callback: (trx: PuriTransactionWrapper) => Promise<T>,
47
89
  options: TransactionalOptions = {}
48
90
  ): Promise<T> {
49
- const { isolation, readOnly } = options;
91
+ const { isolation, readOnly, dbPreset = "w" } = options;
92
+
93
+ // @transactional 데코레이터와 동일한 로직: 이미 트랜잭션 컨텍스트가 있는지 확인
94
+ const { DB } = await import("./db");
95
+ const existingContext = DB.transactionStorage.getStore();
96
+
97
+ // AsyncLocalStorage 컨텍스트가 없거나 해당 preset의 트랜잭션이 없으면 새로 시작
98
+ const startTransaction = async (
99
+ knex: Knex | Knex.Transaction,
100
+ upsertBuilder: UpsertBuilder
101
+ ) => {
102
+ return knex.transaction(
103
+ async (trx) => {
104
+ const trxWrapper = new PuriTransactionWrapper(trx, upsertBuilder);
105
+
106
+ // TransactionContext에 트랜잭션 저장
107
+ DB.getTransactionContext().setTransaction(dbPreset, trxWrapper);
108
+
109
+ try {
110
+ return await callback(trxWrapper);
111
+ } finally {
112
+ // 트랜잭션 제거
113
+ DB.getTransactionContext().deleteTransaction(dbPreset);
114
+ }
115
+ },
116
+ { isolationLevel: isolation, readOnly }
117
+ );
118
+ };
119
+
120
+ // AsyncLocalStorage 컨텍스트가 없으면 새로 생성
121
+ if (!existingContext) {
122
+ return DB.runWithTransaction(() =>
123
+ startTransaction(this.knex, this.upsertBuilder)
124
+ );
125
+ }
50
126
 
51
- return this.knex.transaction(
52
- async (trx) => {
53
- return callback(new PuriTransactionWrapper(trx, this.upsertBuilder));
54
- },
55
- { isolationLevel: isolation, readOnly }
56
- );
127
+ // 해당 preset의 트랜잭션이 이미 있으면 SAVEPOINT로 중첩 트랜잭션 생성
128
+ const existingTrx = existingContext.getTransaction(dbPreset);
129
+ if (existingTrx) {
130
+ return startTransaction(existingTrx.trx, existingTrx.upsertBuilder);
131
+ } else {
132
+ // 컨텍스트는 있지만 이 preset의 트랜잭션은 없는 경우 (같은 컨텍스트 내에서 실행)
133
+ return startTransaction(this.knex, this.upsertBuilder);
134
+ }
57
135
  }
58
136
 
59
- ubRegister<TTable extends TableName<DBSchema>>(
137
+ ubRegister<TTable extends TableName<TSchema>>(
60
138
  tableName: TTable,
61
139
  row: Partial<{
62
- [K in keyof DBSchema[TTable]]: DBSchema[TTable][K] | UBRef;
140
+ [K in keyof TSchema[TTable]]: TSchema[TTable][K] | UBRef;
63
141
  }>
64
142
  ): UBRef {
65
143
  return this.upsertBuilder.register(tableName, row);
66
144
  }
67
145
 
68
146
  ubUpsert(
69
- tableName: TableName<DBSchema>,
147
+ tableName: TableName<TSchema>,
70
148
  chunkSize?: number
71
149
  ): Promise<number[]> {
72
150
  return this.upsertBuilder.upsert(this.knex, tableName, chunkSize);
73
151
  }
74
152
 
75
153
  ubInsertOnly(
76
- tableName: TableName<DBSchema>,
154
+ tableName: TableName<TSchema>,
77
155
  chunkSize?: number
78
156
  ): Promise<number[]> {
79
157
  return this.upsertBuilder.insertOnly(this.knex, tableName, chunkSize);
80
158
  }
81
159
 
82
160
  ubUpsertOrInsert(
83
- tableName: TableName<DBSchema>,
161
+ tableName: TableName<TSchema>,
84
162
  mode: "upsert" | "insert",
85
163
  chunkSize?: number
86
164
  ): Promise<number[]> {
@@ -93,7 +171,7 @@ export class PuriWrapper<
93
171
  }
94
172
 
95
173
  ubUpdateBatch(
96
- tableName: TableName<DBSchema>,
174
+ tableName: TableName<TSchema>,
97
175
  options?: { chunkSize?: number; where?: string | string[] }
98
176
  ): Promise<void> {
99
177
  return this.upsertBuilder.updateBatch(this.knex, tableName, options);