@stingerloom/orm 0.2.1 → 0.3.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.
Files changed (103) hide show
  1. package/README.md +3 -3
  2. package/dist/core/AggregateQueryHandler.d.ts +17 -0
  3. package/dist/core/AggregateQueryHandler.d.ts.map +1 -0
  4. package/dist/core/AggregateQueryHandler.js +96 -0
  5. package/dist/core/AggregateQueryHandler.js.map +1 -0
  6. package/dist/core/CascadeHandler.d.ts +17 -0
  7. package/dist/core/CascadeHandler.d.ts.map +1 -0
  8. package/dist/core/CascadeHandler.js +106 -0
  9. package/dist/core/CascadeHandler.js.map +1 -0
  10. package/dist/core/Conditions.d.ts +2 -0
  11. package/dist/core/Conditions.d.ts.map +1 -1
  12. package/dist/core/Conditions.js +15 -1
  13. package/dist/core/Conditions.js.map +1 -1
  14. package/dist/core/EntityManager.d.ts +30 -45
  15. package/dist/core/EntityManager.d.ts.map +1 -1
  16. package/dist/core/EntityManager.js +483 -1501
  17. package/dist/core/EntityManager.js.map +1 -1
  18. package/dist/core/EntityManagerInternals.d.ts +28 -0
  19. package/dist/core/EntityManagerInternals.d.ts.map +1 -0
  20. package/dist/core/EntityManagerInternals.js +3 -0
  21. package/dist/core/EntityManagerInternals.js.map +1 -0
  22. package/dist/core/ExplainQueryHandler.d.ts +16 -0
  23. package/dist/core/ExplainQueryHandler.d.ts.map +1 -0
  24. package/dist/core/ExplainQueryHandler.js +236 -0
  25. package/dist/core/ExplainQueryHandler.js.map +1 -0
  26. package/dist/core/QueryTracker.d.ts +5 -1
  27. package/dist/core/QueryTracker.d.ts.map +1 -1
  28. package/dist/core/QueryTracker.js +75 -21
  29. package/dist/core/QueryTracker.js.map +1 -1
  30. package/dist/core/RelationLoader.d.ts +13 -0
  31. package/dist/core/RelationLoader.d.ts.map +1 -0
  32. package/dist/core/RelationLoader.js +206 -0
  33. package/dist/core/RelationLoader.js.map +1 -0
  34. package/dist/core/RelationMetadataResolver.d.ts +24 -0
  35. package/dist/core/RelationMetadataResolver.d.ts.map +1 -0
  36. package/dist/core/RelationMetadataResolver.js +182 -0
  37. package/dist/core/RelationMetadataResolver.js.map +1 -0
  38. package/dist/core/ReplicationManager.d.ts +11 -0
  39. package/dist/core/ReplicationManager.d.ts.map +1 -0
  40. package/dist/core/ReplicationManager.js +38 -0
  41. package/dist/core/ReplicationManager.js.map +1 -0
  42. package/dist/core/SchemaRegistrar.d.ts +15 -0
  43. package/dist/core/SchemaRegistrar.d.ts.map +1 -0
  44. package/dist/core/SchemaRegistrar.js +238 -0
  45. package/dist/core/SchemaRegistrar.js.map +1 -0
  46. package/dist/core/index.d.ts +8 -0
  47. package/dist/core/index.d.ts.map +1 -1
  48. package/dist/core/index.js +8 -0
  49. package/dist/core/index.js.map +1 -1
  50. package/dist/decorators/Entity.d.ts.map +1 -1
  51. package/dist/decorators/Entity.js +9 -3
  52. package/dist/decorators/Entity.js.map +1 -1
  53. package/dist/decorators/Version.d.ts.map +1 -1
  54. package/dist/decorators/Version.js +3 -3
  55. package/dist/decorators/Version.js.map +1 -1
  56. package/dist/dialects/FindOption.d.ts +2 -2
  57. package/dist/dialects/FindOption.d.ts.map +1 -1
  58. package/dist/dialects/TransactionSessionManager.d.ts.map +1 -1
  59. package/dist/dialects/TransactionSessionManager.js +5 -2
  60. package/dist/dialects/TransactionSessionManager.js.map +1 -1
  61. package/dist/dialects/mysql/MySqlDataSource.d.ts.map +1 -1
  62. package/dist/dialects/mysql/MySqlDataSource.js +3 -0
  63. package/dist/dialects/mysql/MySqlDataSource.js.map +1 -1
  64. package/dist/dialects/mysql/MySqlDriver.d.ts.map +1 -1
  65. package/dist/dialects/mysql/MySqlDriver.js +9 -3
  66. package/dist/dialects/mysql/MySqlDriver.js.map +1 -1
  67. package/dist/dialects/postgres/PostgresDataSource.d.ts.map +1 -1
  68. package/dist/dialects/postgres/PostgresDataSource.js +3 -0
  69. package/dist/dialects/postgres/PostgresDataSource.js.map +1 -1
  70. package/dist/dialects/postgres/PostgresDriver.d.ts.map +1 -1
  71. package/dist/dialects/postgres/PostgresDriver.js +4 -0
  72. package/dist/dialects/postgres/PostgresDriver.js.map +1 -1
  73. package/dist/dialects/sqlite/SqliteDataSource.d.ts.map +1 -1
  74. package/dist/dialects/sqlite/SqliteDataSource.js +3 -0
  75. package/dist/dialects/sqlite/SqliteDataSource.js.map +1 -1
  76. package/dist/dialects/sqlite/SqliteDriver.d.ts.map +1 -1
  77. package/dist/dialects/sqlite/SqliteDriver.js +4 -0
  78. package/dist/dialects/sqlite/SqliteDriver.js.map +1 -1
  79. package/dist/errors/DatabaseConnectionFailedError.d.ts +2 -1
  80. package/dist/errors/DatabaseConnectionFailedError.d.ts.map +1 -1
  81. package/dist/errors/DatabaseConnectionFailedError.js +12 -2
  82. package/dist/errors/DatabaseConnectionFailedError.js.map +1 -1
  83. package/dist/errors/OptimisticLockError.d.ts +5 -0
  84. package/dist/errors/OptimisticLockError.d.ts.map +1 -0
  85. package/dist/errors/OptimisticLockError.js +14 -0
  86. package/dist/errors/OptimisticLockError.js.map +1 -0
  87. package/dist/errors/OrmErrorCode.d.ts +3 -1
  88. package/dist/errors/OrmErrorCode.d.ts.map +1 -1
  89. package/dist/errors/OrmErrorCode.js +2 -0
  90. package/dist/errors/OrmErrorCode.js.map +1 -1
  91. package/dist/errors/index.d.ts +1 -0
  92. package/dist/errors/index.d.ts.map +1 -1
  93. package/dist/errors/index.js +1 -0
  94. package/dist/errors/index.js.map +1 -1
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +1 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/dist/utils/validateSavepointName.d.ts +2 -0
  100. package/dist/utils/validateSavepointName.d.ts.map +1 -0
  101. package/dist/utils/validateSavepointName.js +12 -0
  102. package/dist/utils/validateSavepointName.js.map +1 -0
  103. package/package.json +7 -3
@@ -32,46 +32,43 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.EntityManager = void 0;
40
37
  require("reflect-metadata");
41
38
  const utils_1 = require("../utils");
42
- const scanner_1 = require("../scanner");
43
- const typedi_1 = __importDefault(require("typedi"));
44
39
  const DatabaseClient_1 = require("../DatabaseClient");
45
40
  const MySqlDriver_1 = require("../dialects/mysql/MySqlDriver");
46
41
  const PostgresDriver_1 = require("../dialects/postgres/PostgresDriver");
47
42
  const SqliteDriver_1 = require("../dialects/sqlite/SqliteDriver");
48
- const SchemaGenerator_1 = require("./generators/SchemaGenerator");
49
- const Indexer_1 = require("../decorators/Indexer");
50
- const UniqueIndex_1 = require("../decorators/UniqueIndex");
51
43
  const TransactionSessionManager_1 = require("../dialects/TransactionSessionManager");
52
44
  const MySqlDataSource_1 = require("../dialects/mysql/MySqlDataSource");
53
45
  const PostgresDataSource_1 = require("../dialects/postgres/PostgresDataSource");
54
46
  const SqliteDataSource_1 = require("../dialects/sqlite/SqliteDataSource");
55
47
  const sql_template_tag_1 = __importStar(require("sql-template-tag"));
56
- const decorators_1 = require("../decorators");
57
48
  const BaseRepository_1 = require("./BaseRepository");
58
- const EntityNotFound_1 = require("../dialects/EntityNotFound");
59
49
  const RawQueryBuilderFactory_1 = require("./RawQueryBuilderFactory");
60
50
  const Conditions_1 = require("./Conditions");
61
51
  const ResultTransformerFactory_1 = require("./ResultTransformerFactory");
62
52
  const MetadataContext_1 = require("../metadata/MetadataContext");
63
53
  const LazyLoader_1 = require("./LazyLoader");
64
- const CascadeType_1 = require("../types/CascadeType");
65
54
  const EntityValidator_1 = require("./EntityValidator");
66
55
  const EntityEventEmitter_1 = require("./EntityEventEmitter");
67
56
  const EntityMetadataNotFoundError_1 = require("../errors/EntityMetadataNotFoundError");
68
57
  const InvalidQueryError_1 = require("../errors/InvalidQueryError");
58
+ const OptimisticLockError_1 = require("../errors/OptimisticLockError");
69
59
  const PrimaryKeyNotFoundError_1 = require("../errors/PrimaryKeyNotFoundError");
70
60
  const DeleteWithoutConditionsError_1 = require("../errors/DeleteWithoutConditionsError");
71
61
  const NotSupportedDatabaseTypeError_1 = require("../errors/NotSupportedDatabaseTypeError");
72
62
  const QueryTracker_1 = require("./QueryTracker");
73
63
  const CursorPagination_1 = require("./CursorPagination");
74
- const ReplicationRouter_1 = require("../dialects/ReplicationRouter");
64
+ const Transactional_1 = require("../decorators/Transactional");
65
+ const RelationMetadataResolver_1 = require("./RelationMetadataResolver");
66
+ const ReplicationManager_1 = require("./ReplicationManager");
67
+ const CascadeHandler_1 = require("./CascadeHandler");
68
+ const RelationLoader_1 = require("./RelationLoader");
69
+ const SchemaRegistrar_1 = require("./SchemaRegistrar");
70
+ const ExplainQueryHandler_1 = require("./ExplainQueryHandler");
71
+ const AggregateQueryHandler_1 = require("./AggregateQueryHandler");
75
72
  class EntityManager {
76
73
  constructor() {
77
74
  this._entities = [];
@@ -79,13 +76,39 @@ class EntityManager {
79
76
  this.dirtyEntities = new Set();
80
77
  this.eventEmitter = new EntityEventEmitter_1.EntityEventEmitter();
81
78
  this.subscribers = [];
79
+ this.cursorPkWarned = new Set();
82
80
  this.queryTracker = null;
83
- this.replicationRouter = null;
84
81
  this.connectionName = "default";
82
+ this.resolver = new RelationMetadataResolver_1.RelationMetadataResolver();
83
+ this.replication = new ReplicationManager_1.ReplicationManager();
84
+ this._ctx = {
85
+ wrap: (col) => this.wrap(col),
86
+ isMySqlFamily: () => this.isMySqlFamily(),
87
+ isPostgres: () => this.isPostgres(),
88
+ getDriver: () => this.driver,
89
+ getSynchronize: () => this.client.getOptions().synchronize ?? false,
90
+ executeInTransaction: (fn, s, r) => this.executeInTransaction(fn, s, r),
91
+ beginTrackQuery: () => this.beginTrackQuery(),
92
+ trackQuery: (e, s, m) => this.trackQuery(e, s, m),
93
+ getReadNode: (u) => this.getReadNode(u),
94
+ getNameStrategy: (c) => this.getNameStrategy(c),
95
+ resolveSelectColumns: (s) => this.resolveSelectColumns(s),
96
+ markDirty: (e) => this.dirtyEntities.add(e),
97
+ findInternal: (e, o, s) => this.findInternal(e, o, s),
98
+ findOneInternal: (e, o, s) => this.findOneInternal(e, o, s),
99
+ save: (e, i) => this.save(e, i),
100
+ find: (e, o) => this.find(e, o),
101
+ delete: (e, c) => this.delete(e, c),
102
+ };
103
+ this.cascadeHandler = new CascadeHandler_1.CascadeHandler(this.resolver, this._ctx);
104
+ this.relationLoader = new RelationLoader_1.RelationLoader(this.resolver, this._ctx);
105
+ this.schemaRegistrar = new SchemaRegistrar_1.SchemaRegistrar(this.resolver, this._ctx);
106
+ this.explainHandler = new ExplainQueryHandler_1.ExplainQueryHandler(this.resolver, this._ctx);
107
+ this.aggregateHandler = new AggregateQueryHandler_1.AggregateQueryHandler(this.resolver, this._ctx);
85
108
  }
86
109
  async register(databaseClientOptions, connectionName = "default") {
87
110
  await this.connect(databaseClientOptions, connectionName);
88
- await this.registerEntities();
111
+ await this.schemaRegistrar.registerEntities();
89
112
  }
90
113
  get client() {
91
114
  return DatabaseClient_1.DatabaseClient.getInstance();
@@ -132,8 +155,42 @@ class EntityManager {
132
155
  this.defaultQueryTimeout = queryTimeout;
133
156
  }
134
157
  if (replication) {
135
- this.replicationRouter = new ReplicationRouter_1.ReplicationRouter(replication);
158
+ this.replication.initialize(replication);
159
+ }
160
+ }
161
+ async propagateShutdown(options) {
162
+ const gracefulTimeoutMs = options?.gracefulTimeoutMs ?? 0;
163
+ const closeConnections = options?.closeConnections ?? false;
164
+ let allQueriesCompleted = true;
165
+ if (gracefulTimeoutMs > 0 && this.queryTracker) {
166
+ const activeCount = this.queryTracker.activeQueryCount;
167
+ if (activeCount > 0) {
168
+ this.logger.info(`[Shutdown] Waiting for ${activeCount} active queries (timeout: ${gracefulTimeoutMs}ms)...`);
169
+ allQueriesCompleted = await this.queryTracker.waitForQueries(gracefulTimeoutMs);
170
+ if (!allQueriesCompleted) {
171
+ this.logger.warn(`[Shutdown] Timed out waiting for active queries. Forcing shutdown.`);
172
+ }
173
+ }
174
+ }
175
+ this.removeAllListeners();
176
+ this.subscribers.length = 0;
177
+ this.dirtyEntities.clear();
178
+ this.cursorPkWarned.clear();
179
+ this.queryTracker?.reset();
180
+ this.queryTracker = null;
181
+ this.replication.shutdown();
182
+ if (closeConnections) {
183
+ try {
184
+ await this.client.close(this.connectionName);
185
+ }
186
+ catch (err) {
187
+ this.logger.warn(`[Shutdown] Error closing connection '${this.connectionName}': ${err}`);
188
+ }
136
189
  }
190
+ return allQueriesCompleted;
191
+ }
192
+ getNameStrategy(clazz) {
193
+ return clazz.name;
137
194
  }
138
195
  initQueryTracker(options) {
139
196
  const logging = options.logging;
@@ -167,22 +224,16 @@ class EntityManager {
167
224
  this.queryTracker?.track(entityName, sqlText, durationMs);
168
225
  }
169
226
  getReadNode(useMaster) {
170
- if (!this.replicationRouter)
171
- return null;
172
- if (useMaster)
173
- return this.replicationRouter.getWriteNode();
174
- return this.replicationRouter.getReadNode();
227
+ return this.replication.getReadNode(useMaster);
175
228
  }
176
229
  getWriteNode() {
177
- if (!this.replicationRouter)
178
- return null;
179
- return this.replicationRouter.getWriteNode();
230
+ return this.replication.getWriteNode();
180
231
  }
181
232
  get isReplicationEnabled() {
182
- return this.replicationRouter !== null;
233
+ return this.replication.isEnabled;
183
234
  }
184
235
  getReplicationRouter() {
185
- return this.replicationRouter;
236
+ return this.replication.getRouter();
186
237
  }
187
238
  on(event, listener) {
188
239
  this.eventEmitter.on(event, listener);
@@ -216,614 +267,11 @@ class EntityManager {
216
267
  }
217
268
  }
218
269
  }
219
- async propagateShutdown(options) {
220
- const gracefulTimeoutMs = options?.gracefulTimeoutMs ?? 0;
221
- const closeConnections = options?.closeConnections ?? false;
222
- let allQueriesCompleted = true;
223
- if (gracefulTimeoutMs > 0 && this.queryTracker) {
224
- const activeCount = this.queryTracker.activeQueryCount;
225
- if (activeCount > 0) {
226
- this.logger.info(`[Shutdown] Waiting for ${activeCount} active queries (timeout: ${gracefulTimeoutMs}ms)...`);
227
- allQueriesCompleted = await this.queryTracker.waitForQueries(gracefulTimeoutMs);
228
- if (!allQueriesCompleted) {
229
- this.logger.warn(`[Shutdown] Timed out waiting for active queries. Forcing shutdown.`);
230
- }
231
- }
232
- }
233
- this.removeAllListeners();
234
- this.subscribers.length = 0;
235
- this.dirtyEntities.clear();
236
- this.queryTracker?.reset();
237
- this.queryTracker = null;
238
- if (this.replicationRouter) {
239
- this.replicationRouter.resetFailedSlaves();
240
- this.replicationRouter = null;
241
- }
242
- if (closeConnections) {
243
- try {
244
- await this.client.close(this.connectionName);
245
- }
246
- catch (err) {
247
- this.logger.warn(`[Shutdown] Error closing connection '${this.connectionName}': ${err}`);
248
- }
249
- }
250
- return allQueriesCompleted;
251
- }
252
- getNameStrategy(clazz) {
253
- return clazz.name;
254
- }
255
- resolveEntityMetadata(entity) {
256
- const context = MetadataContext_1.MetadataContext.isActive()
257
- ? MetadataContext_1.MetadataContext.getCurrentTenant()
258
- : "public";
259
- const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
260
- const layeredMetadata = entityScanner.scan(entity);
261
- if (layeredMetadata) {
262
- return layeredMetadata;
263
- }
264
- const reflectMetadata = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
265
- if (reflectMetadata) {
266
- this.logger.warn(`[resolveEntityMetadata] "${entity.name}" resolved via Reflect.getMetadata fallback (context: "${context}"). ` +
267
- `This entity was not found in the layered store.`);
268
- }
269
- else {
270
- this.logger.error(`[resolveEntityMetadata] "${entity.name}" not found in any metadata source (context: "${context}")`);
271
- }
272
- return reflectMetadata ?? null;
273
- }
274
- getDeletedAtColumn(entity) {
275
- const column = Reflect.getMetadata(decorators_1.DELETED_AT_TOKEN, entity);
276
- return column ?? null;
277
- }
278
- getCreateTimestampColumn(entity) {
279
- const column = Reflect.getMetadata(decorators_1.CREATE_TIMESTAMP_TOKEN, entity);
280
- return column ?? null;
281
- }
282
- getUpdateTimestampColumn(entity) {
283
- const column = Reflect.getMetadata(decorators_1.UPDATE_TIMESTAMP_TOKEN, entity);
284
- return column ?? null;
285
- }
286
- async runHooks(entity, item, event) {
287
- const hooks = Reflect.getMetadata(decorators_1.HOOK_TOKEN, entity);
288
- if (!hooks || hooks.length === 0)
289
- return;
290
- for (const hook of hooks) {
291
- if (hook.event !== event)
292
- continue;
293
- const method = item[hook.methodName];
294
- if (typeof method === "function") {
295
- await method.call(item);
296
- }
297
- }
298
- }
299
- createProxy(entity) {
300
- return new Proxy(entity, {
301
- set: (target, prop, value) => {
302
- target[prop] = value;
303
- this.dirtyEntities.add(target);
304
- return true;
305
- },
306
- });
307
- }
308
- async registerEntities() {
309
- const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
310
- const entities = entityScanner.makeEntities();
311
- let entity;
312
- const { synchronize } = this.client.getOptions();
313
- if (synchronize && this.isPostgres() && this.driver) {
314
- const pgDriver = this.driver;
315
- const hasSchema = await pgDriver.hasSchema();
316
- if (!hasSchema || hasSchema.length === 0) {
317
- await pgDriver.createSchema();
318
- await pgDriver.setSearchPath();
319
- }
320
- }
321
- const entityList = [];
322
- while ((entity = entities.next())) {
323
- if (entity.done) {
324
- break;
325
- }
326
- const metadata = entity.value;
327
- const TargetEntity = metadata.target;
328
- let tableName = metadata.name;
329
- if (!tableName) {
330
- tableName = this.getNameStrategy(TargetEntity);
331
- }
332
- if (!utils_1.ReflectManager.isEntity(TargetEntity)) {
333
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(tableName ?? "Unknown");
334
- }
335
- if (synchronize) {
336
- const hasTable = await this.driver?.hasTable(tableName);
337
- if (!hasTable || hasTable.length === 0) {
338
- await this.driver?.createTable(tableName, metadata.columns);
339
- }
340
- }
341
- entityList.push({ TargetEntity, tableName, metadata });
342
- }
343
- if (synchronize) {
344
- for (const { TargetEntity, tableName } of entityList) {
345
- await this.registerForeignKeys(TargetEntity, tableName);
346
- await this.registerIndex(TargetEntity, tableName);
347
- await this.registerUniqueIndexes(TargetEntity, tableName);
348
- }
349
- await this.registerManyToManyJoinTables(entityList.map((e) => e.TargetEntity));
350
- }
351
- }
352
- async registerUniqueIndexes(TargetEntity, tableName) {
353
- const uniqueIndexes = Reflect.getMetadata(UniqueIndex_1.UNIQUE_INDEX_TOKEN, TargetEntity);
354
- if (!uniqueIndexes || uniqueIndexes.length === 0)
355
- return;
356
- for (const uq of uniqueIndexes) {
357
- const indexName = uq.name ?? `uq_${tableName}_${uq.columns.join("_")}`;
358
- const indexes = (await this.driver?.getIndexes(tableName));
359
- let isExist = false;
360
- for (const idx of indexes || []) {
361
- const existingIndexName = idx["Key_name"] ?? idx["Field"] ?? idx["name"];
362
- if (existingIndexName === indexName) {
363
- isExist = true;
364
- break;
365
- }
366
- }
367
- if (!isExist) {
368
- await this.driver?.addCompositeUniqueIndex(tableName, uq.columns, indexName);
369
- }
370
- }
371
- }
372
- async registerManyToManyJoinTables(entities) {
373
- const processedTables = new Set();
374
- for (const entity of entities) {
375
- const m2mMeta = (Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
376
- []);
377
- for (const rel of m2mMeta) {
378
- if (!rel.joinTable)
379
- continue;
380
- const { name: joinTableName, joinColumn, inverseJoinColumn, } = rel.joinTable;
381
- if (processedTables.has(joinTableName))
382
- continue;
383
- processedTables.add(joinTableName);
384
- const ownerEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
385
- const ownerTable = ownerEntityMeta?.name ?? entity.name;
386
- const relatedEntity = rel.getRelatedEntity();
387
- const relatedEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, relatedEntity);
388
- const relatedTable = relatedEntityMeta?.name ?? relatedEntity.name;
389
- const hasTable = await this.driver?.hasTable(joinTableName);
390
- if (!hasTable || hasTable.length === 0) {
391
- const wJoinTable = this.wrap(joinTableName);
392
- const wJoinCol = this.wrap(joinColumn);
393
- const wInvCol = this.wrap(inverseJoinColumn);
394
- let ddl = `CREATE TABLE IF NOT EXISTS ${wJoinTable} (${wJoinCol} INT NOT NULL, ${wInvCol} INT NOT NULL, PRIMARY KEY (${wJoinCol}, ${wInvCol}))`;
395
- if (this.isMySqlFamily())
396
- ddl += " ENGINE=InnoDB";
397
- await this.driver?.executeRaw(ddl);
398
- }
399
- const ownerColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ?? []);
400
- const ownerPk = ownerColumns.find((c) => c.options?.primary)?.name;
401
- const relatedColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, relatedEntity.prototype) ?? []);
402
- const relatedPk = relatedColumns.find((c) => c.options?.primary)?.name;
403
- const ownerFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, joinColumn, ownerTable);
404
- if (ownerPk &&
405
- this.driver &&
406
- !(await this.driver.hasForeignKey(joinTableName, ownerFkName))) {
407
- const ddl = `ALTER TABLE ${this.wrap(joinTableName)} ADD CONSTRAINT ${ownerFkName} FOREIGN KEY (${this.wrap(joinColumn)}) REFERENCES ${this.wrap(ownerTable)}(${this.wrap(ownerPk)}) ON DELETE CASCADE ON UPDATE CASCADE`;
408
- await this.driver.executeRaw(ddl);
409
- }
410
- const relatedFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, inverseJoinColumn, relatedTable);
411
- if (relatedPk &&
412
- this.driver &&
413
- !(await this.driver.hasForeignKey(joinTableName, relatedFkName))) {
414
- const ddl = `ALTER TABLE ${this.wrap(joinTableName)} ADD CONSTRAINT ${relatedFkName} FOREIGN KEY (${this.wrap(inverseJoinColumn)}) REFERENCES ${this.wrap(relatedTable)}(${this.wrap(relatedPk)}) ON DELETE CASCADE ON UPDATE CASCADE`;
415
- await this.driver.executeRaw(ddl);
416
- }
417
- }
418
- }
419
- }
420
- resolveManyToOneMetadata(entity) {
421
- const manyToOneScanner = typedi_1.default.get(scanner_1.ManyToOneScanner);
422
- const allRelations = manyToOneScanner
423
- .allMetadata()
424
- .filter((rel) => rel.target === entity);
425
- if (allRelations.length > 0) {
426
- return this.resolveJoinColumnsFromColumnMeta(entity, allRelations);
427
- }
428
- const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity) ??
429
- Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity.prototype);
430
- if (reflectMetadata && reflectMetadata.length > 0) {
431
- this.logger.warn(`[resolveManyToOneMetadata] "${entity.name}" ManyToOne resolved via Reflect.getMetadata fallback.`);
432
- return this.resolveJoinColumnsFromColumnMeta(entity, reflectMetadata);
433
- }
434
- return [];
435
- }
436
- resolveJoinColumnsFromColumnMeta(entity, relations) {
437
- const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
438
- Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
439
- [];
440
- if (columnsMeta.length === 0) {
441
- return relations;
442
- }
443
- return relations.map((rel) => {
444
- if (rel.joinColumn)
445
- return rel;
446
- const fkPropertyName = `${rel.columnName}Id`;
447
- const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
448
- if (!matchingColumn)
449
- return rel;
450
- const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
451
- return {
452
- ...rel,
453
- joinColumn: resolvedJoinColumn,
454
- };
455
- });
456
- }
457
- resolveOneToManyMetadata(entity) {
458
- const oneToManyScanner = typedi_1.default.get(scanner_1.OneToManyScanner);
459
- const allRelations = oneToManyScanner
460
- .allMetadata()
461
- .filter((rel) => rel.target === entity);
462
- if (allRelations.length > 0) {
463
- return allRelations;
464
- }
465
- const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity) ??
466
- Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity.prototype);
467
- if (reflectMetadata && reflectMetadata.length > 0) {
468
- this.logger.warn(`[resolveOneToManyMetadata] "${entity.name}" OneToMany resolved via Reflect.getMetadata fallback.`);
469
- return reflectMetadata;
470
- }
471
- return [];
472
- }
473
- async loadOneToManyRelations(entity, parentResults, relations) {
474
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
475
- if (oneToManyMeta.length === 0)
476
- return;
477
- const parentMetadata = this.resolveEntityMetadata(entity);
478
- if (!parentMetadata)
479
- return;
480
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
481
- if (!pk)
482
- return;
483
- const parents = Array.isArray(parentResults)
484
- ? parentResults
485
- : [parentResults];
486
- for (const rel of oneToManyMeta) {
487
- if (!relations.includes(rel.propertyKey))
488
- continue;
489
- const RelatedEntity = rel.getRelatedEntity();
490
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
491
- if (!relatedMetadata)
492
- continue;
493
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
494
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
495
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
496
- for (const parent of parents) {
497
- const parentId = parent[pk.name];
498
- if (parentId === undefined || parentId === null)
499
- continue;
500
- const children = await this.find(RelatedEntity, {
501
- where: { [fkColumn]: parentId },
502
- });
503
- if (children === undefined) {
504
- parent[rel.propertyKey] = [];
505
- }
506
- else if (Array.isArray(children)) {
507
- parent[rel.propertyKey] = children;
508
- }
509
- else {
510
- parent[rel.propertyKey] = [children];
511
- }
512
- }
513
- }
514
- }
515
- resolveManyToManyMetadata(entity) {
516
- const manyToManyScanner = typedi_1.default.get(scanner_1.ManyToManyScanner);
517
- const allRelations = manyToManyScanner
518
- .allMetadata()
519
- .filter((rel) => rel.target === entity);
520
- if (allRelations.length > 0) {
521
- return allRelations;
522
- }
523
- const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
524
- Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity.prototype);
525
- if (reflectMetadata && reflectMetadata.length > 0) {
526
- this.logger.warn(`[resolveManyToManyMetadata] "${entity.name}" ManyToMany resolved via Reflect.getMetadata fallback.`);
527
- return reflectMetadata;
528
- }
529
- return [];
530
- }
531
- resolveOneToOneMetadata(entity) {
532
- const oneToOneScanner = typedi_1.default.get(scanner_1.OneToOneScanner);
533
- const allRelations = oneToOneScanner
534
- .allMetadata()
535
- .filter((rel) => rel.target === entity);
536
- if (allRelations.length > 0) {
537
- return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, allRelations);
538
- }
539
- const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity) ??
540
- Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity.prototype);
541
- if (reflectMetadata && reflectMetadata.length > 0) {
542
- this.logger.warn(`[resolveOneToOneMetadata] "${entity.name}" OneToOne resolved via Reflect.getMetadata fallback.`);
543
- return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, reflectMetadata);
544
- }
545
- return [];
546
- }
547
- resolveJoinColumnsFromColumnMetaForOneToOne(entity, relations) {
548
- const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
549
- Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
550
- [];
551
- if (columnsMeta.length === 0) {
552
- return relations;
553
- }
554
- return relations.map((rel) => {
555
- if (rel.joinColumn)
556
- return rel;
557
- const fkPropertyName = `${rel.propertyKey}Id`;
558
- const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
559
- if (!matchingColumn)
560
- return rel;
561
- const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
562
- return {
563
- ...rel,
564
- joinColumn: resolvedJoinColumn,
565
- };
566
- });
567
- }
568
- resolveManyToManyJoinTable(rel) {
569
- if (rel.joinTable) {
570
- return {
571
- joinTableName: rel.joinTable.name,
572
- joinColumn: rel.joinTable.joinColumn,
573
- inverseJoinColumn: rel.joinTable.inverseJoinColumn,
574
- };
575
- }
576
- if (rel.mappedBy) {
577
- const RelatedEntity = rel.getRelatedEntity();
578
- const relatedManyToMany = this.resolveManyToManyMetadata(RelatedEntity);
579
- const ownerRel = relatedManyToMany.find((r) => r.propertyKey === rel.mappedBy && r.joinTable);
580
- if (ownerRel?.joinTable) {
581
- return {
582
- joinTableName: ownerRel.joinTable.name,
583
- joinColumn: ownerRel.joinTable.inverseJoinColumn,
584
- inverseJoinColumn: ownerRel.joinTable.joinColumn,
585
- };
586
- }
587
- }
588
- return null;
589
- }
590
- async loadManyToManyRelations(entity, parentResults, relations) {
591
- const manyToManyMeta = this.resolveManyToManyMetadata(entity);
592
- if (manyToManyMeta.length === 0)
593
- return;
594
- const parentMetadata = this.resolveEntityMetadata(entity);
595
- if (!parentMetadata)
596
- return;
597
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
598
- if (!pk)
599
- return;
600
- const parents = Array.isArray(parentResults)
601
- ? parentResults
602
- : [parentResults];
603
- for (const rel of manyToManyMeta) {
604
- if (!relations.includes(rel.propertyKey))
605
- continue;
606
- const joinInfo = this.resolveManyToManyJoinTable(rel);
607
- if (!joinInfo)
608
- continue;
609
- const RelatedEntity = rel.getRelatedEntity();
610
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
611
- if (!relatedMetadata)
612
- continue;
613
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
614
- if (!relatedPk)
615
- continue;
616
- const relatedTableName = relatedMetadata.name ?? RelatedEntity.name;
617
- for (const parent of parents) {
618
- const parentId = parent[pk.name];
619
- if (parentId === undefined || parentId === null)
620
- continue;
621
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
622
- const selectCols = relatedMetadata.columns.map((col) => `${this.wrap(relatedTableName)}.${this.wrap(col.name)}`);
623
- const joinCondition = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(relatedTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(relatedPk.name))} = ${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.inverseJoinColumn))}`;
624
- const whereCondition = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinColumn))} = ${parentId}`;
625
- qb.select(selectCols)
626
- .from(this.wrap(relatedTableName))
627
- .innerJoin(this.wrap(joinInfo.joinTableName), this.wrap(joinInfo.joinTableName), joinCondition)
628
- .where([whereCondition]);
629
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
630
- try {
631
- await transactionHolder.connect();
632
- await transactionHolder.startTransaction();
633
- if (this.isMySqlFamily()) {
634
- await transactionHolder.query("SET autocommit = 0");
635
- }
636
- const resultQuery = qb.build();
637
- const subQueryStart = Date.now();
638
- this.beginTrackQuery();
639
- const queryResult = (await transactionHolder.query(resultQuery));
640
- this.trackQuery(relatedTableName, resultQuery.text ?? String(resultQuery), Date.now() - subQueryStart);
641
- await transactionHolder.commit();
642
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
643
- const { results } = queryResult;
644
- if (!results || results.length === 0) {
645
- parent[rel.propertyKey] = [];
646
- }
647
- else {
648
- parent[rel.propertyKey] = resultTransformer.toEntities(RelatedEntity, queryResult);
649
- }
650
- }
651
- catch (e) {
652
- try {
653
- await transactionHolder.rollback();
654
- }
655
- catch (rollbackError) {
656
- this.logger.error(`Failed to rollback ManyToMany transaction: ${rollbackError}`);
657
- }
658
- throw e;
659
- }
660
- finally {
661
- try {
662
- await transactionHolder.close();
663
- }
664
- catch (closeError) {
665
- this.logger.error(`Failed to close ManyToMany transaction: ${closeError}`);
666
- }
667
- }
668
- }
669
- }
670
- }
671
- async loadOneToOneRelations(entity, parentResults, relations) {
672
- const oneToOneMeta = this.resolveOneToOneMetadata(entity);
673
- if (oneToOneMeta.length === 0)
674
- return;
675
- const parentMetadata = this.resolveEntityMetadata(entity);
676
- if (!parentMetadata)
677
- return;
678
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
679
- if (!pk)
680
- return;
681
- const parents = Array.isArray(parentResults)
682
- ? parentResults
683
- : [parentResults];
684
- for (const rel of oneToOneMeta) {
685
- if (!relations.includes(rel.propertyKey))
686
- continue;
687
- if (rel.joinColumn) {
688
- continue;
689
- }
690
- const RelatedEntity = rel.getRelatedEntity();
691
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
692
- if (!relatedMetadata)
693
- continue;
694
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
695
- if (!relatedPk)
696
- continue;
697
- for (const parent of parents) {
698
- if (rel.joinColumn) {
699
- const fkValue = parent[rel.joinColumn];
700
- if (fkValue === undefined || fkValue === null) {
701
- parent[rel.propertyKey] = null;
702
- continue;
703
- }
704
- const related = await this.findOne(RelatedEntity, {
705
- where: { [relatedPk.name]: fkValue },
706
- });
707
- parent[rel.propertyKey] = related ?? null;
708
- }
709
- else if (rel.inverseSide) {
710
- const parentId = parent[pk.name];
711
- if (parentId === undefined || parentId === null) {
712
- parent[rel.propertyKey] = null;
713
- continue;
714
- }
715
- const relatedOneToOne = this.resolveOneToOneMetadata(RelatedEntity);
716
- const ownerRel = relatedOneToOne.find((r) => r.propertyKey === rel.inverseSide && r.joinColumn);
717
- if (ownerRel?.joinColumn) {
718
- const related = await this.findOne(RelatedEntity, {
719
- where: { [ownerRel.joinColumn]: parentId },
720
- });
721
- parent[rel.propertyKey] = related ?? null;
722
- }
723
- else {
724
- parent[rel.propertyKey] = null;
725
- }
726
- }
727
- }
728
- }
729
- }
730
- async registerForeignKeys(TargetEntity, tableName) {
731
- const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
732
- const manyToOneItems = this.resolveManyToOneMetadata(TargetEntity);
733
- const isValidManyToOne = manyToOneItems && manyToOneItems.length > 0;
734
- if (isValidManyToOne) {
735
- for (const manyToOneItem of manyToOneItems) {
736
- const { joinColumn } = manyToOneItem;
737
- const mappingEntity = manyToOneItem.getMappingEntity();
738
- if (!mappingEntity) {
739
- throw new EntityNotFound_1.EntityNotFound(mappingEntity);
740
- }
741
- const mappingTableMetadata = entityScanner.scan(mappingEntity);
742
- if (!mappingTableMetadata) {
743
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(mappingEntity.name);
744
- }
745
- if (!joinColumn) {
746
- throw new InvalidQueryError_1.InvalidQueryError("JoinColumn does not exist.");
747
- }
748
- const mappingTablePrimaryKey = manyToOneItem.references
749
- ? manyToOneItem.references
750
- : mappingTableMetadata.columns.find((e) => e.options?.primary)?.name;
751
- if (!mappingTablePrimaryKey) {
752
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(mappingEntity.name);
753
- }
754
- const mappingTableName = mappingTableMetadata.name || this.getNameStrategy(mappingEntity);
755
- if (this.driver) {
756
- const columnExists = await this.driver.hasColumn(tableName, joinColumn);
757
- if (!columnExists) {
758
- const fkColumnType = this.driver.castType("int") + " NULL";
759
- await this.driver.addColumn(tableName, joinColumn, fkColumnType);
760
- }
761
- }
762
- if (this.driver) {
763
- const fkName = this.driver.generateForeignKeyName(tableName, mappingTableName, joinColumn);
764
- const fkExists = await this.driver.hasForeignKey(tableName, fkName);
765
- if (fkExists)
766
- continue;
767
- }
768
- await this.driver?.addForeignKey(tableName, joinColumn, mappingTableName, mappingTablePrimaryKey);
769
- }
770
- }
771
- const oneToOneItems = this.resolveOneToOneMetadata(TargetEntity);
772
- for (const oneToOneItem of oneToOneItems) {
773
- const { joinColumn } = oneToOneItem;
774
- if (!joinColumn)
775
- continue;
776
- const RelatedEntity = oneToOneItem.getRelatedEntity();
777
- if (!RelatedEntity) {
778
- throw new EntityNotFound_1.EntityNotFound(RelatedEntity);
779
- }
780
- const relatedMetadata = entityScanner.scan(RelatedEntity);
781
- if (!relatedMetadata) {
782
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(RelatedEntity.name);
783
- }
784
- const relatedPrimaryKey = relatedMetadata.columns.find((e) => e.options?.primary)?.name;
785
- if (!relatedPrimaryKey) {
786
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(RelatedEntity.name);
787
- }
788
- if (this.driver) {
789
- const columnExists = await this.driver.hasColumn(tableName, joinColumn);
790
- if (!columnExists) {
791
- const fkColumnType = this.driver.castType("int") + " NULL";
792
- await this.driver.addColumn(tableName, joinColumn, fkColumnType);
793
- }
794
- }
795
- const relatedTableName = relatedMetadata.name || this.getNameStrategy(RelatedEntity);
796
- if (this.driver) {
797
- const fkName = this.driver.generateForeignKeyName(tableName, relatedTableName, joinColumn);
798
- const fkExists = await this.driver.hasForeignKey(tableName, fkName);
799
- if (fkExists)
800
- continue;
801
- }
802
- await this.driver?.addForeignKey(tableName, joinColumn, relatedTableName, relatedPrimaryKey);
803
- }
804
- }
805
- async registerIndex(TargetEntity, tableName) {
806
- const indexer = Reflect.getMetadata(Indexer_1.INDEX_TOKEN, TargetEntity.prototype);
807
- if (indexer) {
808
- for (const index of indexer) {
809
- const indexName = `INDEX_${tableName}_${index.name}`;
810
- const indexes = (await this.driver?.getIndexes(tableName));
811
- let isExist = false;
812
- for (const idx of indexes || []) {
813
- const existingIndexName = idx["Key_name"] ?? idx["Field"];
814
- if (existingIndexName === indexName) {
815
- isExist = true;
816
- break;
817
- }
818
- }
819
- if (!isExist) {
820
- await this.driver?.addIndex(tableName, index.name, indexName);
821
- }
822
- }
823
- }
824
- }
825
270
  async findOne(entity, findOption) {
826
- const result = await this.find(entity, { ...findOption, limit: 1 });
271
+ return this.findOneInternal(entity, findOption);
272
+ }
273
+ async findOneInternal(entity, findOption, existingSession) {
274
+ const result = await this.findInternal(entity, { ...findOption, limit: 1 }, existingSession);
827
275
  if (result === undefined || result === null) {
828
276
  return null;
829
277
  }
@@ -832,347 +280,16 @@ class EntityManager {
832
280
  }
833
281
  return result;
834
282
  }
835
- async explain(entity, findOption = {}) {
836
- if (!this.driver || !this.driver.supportsExplain()) {
837
- throw new InvalidQueryError_1.InvalidQueryError("EXPLAIN is not supported by the current database driver.");
838
- }
839
- const { select, orderBy, where, take } = findOption;
840
- const { limit } = findOption;
841
- const metadata = this.resolveEntityMetadata(entity);
842
- if (!metadata) {
843
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
844
- }
845
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
846
- const selectMap = [];
847
- const whereMap = [];
848
- const orderByMap = [];
849
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
850
- const eagerRelations = manyToOneRelations.filter((rel) => {
851
- const isEager = rel.option?.eager === true;
852
- const isInRelations = findOption.relations?.includes(rel.columnName);
853
- return isEager || isInRelations;
854
- });
855
- const oneToOneRelations = this.resolveOneToOneMetadata(entity);
856
- const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
857
- if (!rel.joinColumn)
858
- return false;
859
- const isEager = rel.option?.eager === true;
860
- const isInRelations = findOption.relations?.includes(rel.propertyKey);
861
- return isEager || isInRelations;
862
- });
863
- const hasEagerJoins = eagerRelations.length > 0 || eagerOneToOneRelations.length > 0;
864
- const tableName = metadata.name;
865
- if (select) {
866
- const selectedColumns = this.resolveSelectColumns(select);
867
- if (hasEagerJoins) {
868
- selectMap.push(...selectedColumns.map((col) => `${this.wrap(tableName)}.${this.wrap(col)}`));
869
- }
870
- else {
871
- selectMap.push(...selectedColumns.map((col) => this.wrap(col)));
872
- }
873
- }
874
- else {
875
- if (hasEagerJoins) {
876
- selectMap.push(...metadata.columns.map((column) => `${this.wrap(tableName)}.${this.wrap(column.name)}`));
877
- }
878
- else {
879
- selectMap.push(...metadata.columns.map((column) => this.wrap(column.name)));
880
- }
881
- }
882
- for (const key in where) {
883
- const value = where[key];
884
- if (value !== undefined && value !== null) {
885
- if (hasEagerJoins) {
886
- whereMap.push(Conditions_1.Conditions.equals(`${this.wrap(tableName)}.${this.wrap(key)}`, value));
887
- }
888
- else {
889
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
890
- }
891
- }
892
- }
893
- const deletedAtColumn = this.getDeletedAtColumn(entity);
894
- if (deletedAtColumn && !findOption.withDeleted) {
895
- if (hasEagerJoins) {
896
- whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
897
- }
898
- else {
899
- whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
900
- }
901
- }
902
- for (const key in orderBy) {
903
- const value = orderBy[key];
904
- if (value) {
905
- orderByMap.push({ column: this.wrap(key), direction: value });
906
- }
907
- }
908
- qb.select(selectMap).from(this.wrap(tableName));
909
- qb.where(whereMap).orderBy(orderByMap);
910
- if (Array.isArray(limit)) {
911
- let [offset, count] = limit;
912
- if (offset < 0)
913
- offset = 0;
914
- if (count < 0)
915
- count = 0;
916
- if (count === 0)
917
- count = 1;
918
- if (take && take > 0)
919
- count = take;
920
- if (this.isMySqlFamily())
921
- qb.setDatabaseType("mysql");
922
- qb.limit([offset, count]);
923
- }
924
- else if (limit) {
925
- qb.limit(limit);
926
- }
927
- const selectQuery = qb.build();
928
- const explainPrefix = this.driver.buildExplainSql("");
929
- const explainQuery = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(explainPrefix)}${selectQuery}`;
930
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
931
- try {
932
- const readNode = this.getReadNode(findOption.useMaster);
933
- if (readNode) {
934
- await transactionHolder.connectToNode(readNode);
935
- }
936
- else {
937
- await transactionHolder.connect();
938
- }
939
- await transactionHolder.startTransaction();
940
- if (this.isMySqlFamily()) {
941
- await transactionHolder.query("SET autocommit = 0");
942
- }
943
- const result = await transactionHolder.query(explainQuery);
944
- await transactionHolder.commit();
945
- const rawRows = result?.results ?? [];
946
- return this.parseExplainResult(rawRows);
947
- }
948
- catch (e) {
949
- try {
950
- await transactionHolder.rollback();
951
- }
952
- catch {
953
- }
954
- throw e;
955
- }
956
- finally {
957
- try {
958
- await transactionHolder.close();
959
- }
960
- catch {
961
- }
962
- }
963
- }
964
- parseExplainResult(rawRows) {
965
- if (!rawRows || rawRows.length === 0) {
966
- return {
967
- raw: [],
968
- rows: null,
969
- type: null,
970
- possibleKeys: null,
971
- key: null,
972
- cost: null,
973
- };
974
- }
975
- const firstRow = rawRows[0];
976
- if (firstRow && "QUERY PLAN" in firstRow) {
977
- return this.parsePostgresExplain(firstRow["QUERY PLAN"]);
978
- }
979
- if ("type" in firstRow || "select_type" in firstRow) {
980
- return this.parseMysqlExplain(rawRows);
981
- }
982
- if ("detail" in firstRow || "notused" in firstRow) {
983
- return this.parseSqliteExplain(rawRows);
984
- }
985
- return {
986
- raw: rawRows,
987
- rows: null,
988
- type: null,
989
- possibleKeys: null,
990
- key: null,
991
- cost: null,
992
- };
993
- }
994
- parseMysqlExplain(rawRows) {
995
- const first = rawRows[0];
996
- const rows = first.rows != null ? Number(first.rows) : null;
997
- const type = first.type != null ? String(first.type) : null;
998
- const possibleKeysRaw = first.possible_keys;
999
- const possibleKeys = possibleKeysRaw != null
1000
- ? String(possibleKeysRaw)
1001
- .split(",")
1002
- .map((k) => k.trim())
1003
- : null;
1004
- const key = first.key != null ? String(first.key) : null;
1005
- const cost = first.filtered != null ? Number(first.filtered) : null;
1006
- return { raw: rawRows, rows, type, possibleKeys, key, cost };
1007
- }
1008
- parsePostgresExplain(queryPlan) {
1009
- const rawArray = Array.isArray(queryPlan) ? queryPlan : [queryPlan];
1010
- const plan = rawArray[0]?.Plan ?? rawArray[0]?.["Plan"] ?? null;
1011
- if (!plan) {
1012
- return {
1013
- raw: rawArray,
1014
- rows: null,
1015
- type: null,
1016
- possibleKeys: null,
1017
- key: null,
1018
- cost: null,
1019
- };
1020
- }
1021
- const rows = plan["Plan Rows"] != null ? Number(plan["Plan Rows"]) : null;
1022
- const type = plan["Node Type"] != null ? String(plan["Node Type"]) : null;
1023
- const key = plan["Index Name"] != null ? String(plan["Index Name"]) : null;
1024
- const cost = plan["Total Cost"] != null ? Number(plan["Total Cost"]) : null;
1025
- return { raw: rawArray, rows, type, possibleKeys: null, key, cost };
1026
- }
1027
- parseSqliteExplain(rawRows) {
1028
- const details = rawRows.map((r) => String(r.detail ?? ""));
1029
- const firstDetail = details[0] ?? "";
1030
- let type = null;
1031
- let key = null;
1032
- if (firstDetail.startsWith("SCAN"))
1033
- type = "SCAN";
1034
- else if (firstDetail.startsWith("SEARCH"))
1035
- type = "SEARCH";
1036
- const indexMatch = firstDetail.match(/USING (?:COVERING )?INDEX (\S+)/);
1037
- if (indexMatch)
1038
- key = indexMatch[1];
1039
- return {
1040
- raw: rawRows,
1041
- rows: null,
1042
- type,
1043
- possibleKeys: null,
1044
- key,
1045
- cost: null,
1046
- };
1047
- }
1048
- async findWithCursor(entity, option = {}) {
1049
- const metadata = this.resolveEntityMetadata(entity);
1050
- if (!metadata) {
1051
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1052
- }
1053
- const pk = metadata.columns.find((column) => column.options?.primary);
1054
- const orderByColumn = option.orderBy ?? pk?.name;
1055
- if (!orderByColumn) {
1056
- throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
1057
- }
1058
- const direction = option.direction ?? "ASC";
1059
- const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
1060
- let cursorValue = null;
1061
- if (option.cursor) {
1062
- cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
1063
- if (cursorValue === null) {
1064
- throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
1065
- }
1066
- }
1067
- const where = { ...(option.where ?? {}) };
1068
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
1069
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
1070
- try {
1071
- const readNode = this.getReadNode(option.useMaster);
1072
- if (readNode) {
1073
- await transactionHolder.connectToNode(readNode);
1074
- }
1075
- else {
1076
- await transactionHolder.connect();
1077
- }
1078
- await transactionHolder.startTransaction();
1079
- if (this.isMySqlFamily()) {
1080
- await transactionHolder.query("SET autocommit = 0");
1081
- }
1082
- const tableName = metadata.name;
1083
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
1084
- const selectMap = metadata.columns.map((column) => this.wrap(column.name));
1085
- const whereMap = [];
1086
- for (const key in where) {
1087
- const value = where[key];
1088
- if (value !== undefined && value !== null) {
1089
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
1090
- }
1091
- }
1092
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1093
- if (deletedAtColumn) {
1094
- whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
1095
- }
1096
- if (cursorValue !== null) {
1097
- if (direction === "ASC") {
1098
- whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
1099
- }
1100
- else {
1101
- whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
1102
- }
1103
- }
1104
- qb.select(selectMap)
1105
- .from(this.wrap(tableName))
1106
- .where(whereMap)
1107
- .orderBy([{ column: this.wrap(orderByColumn), direction }]);
1108
- qb.limit(pageSize + 1);
1109
- const resultQuery = qb.build();
1110
- const queryResult = (await transactionHolder.query(resultQuery));
1111
- await transactionHolder.commit();
1112
- const { results } = queryResult;
1113
- if (!results || results.length === 0) {
1114
- return {
1115
- data: [],
1116
- hasNextPage: false,
1117
- nextCursor: null,
1118
- count: 0,
1119
- };
1120
- }
1121
- const hasNextPage = results.length > pageSize;
1122
- const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
1123
- const entities = resultTransformer.toEntities(entity, {
1124
- results: pageResults,
1125
- fields: queryResult.fields,
1126
- });
1127
- let nextCursor = null;
1128
- if (hasNextPage && pageResults.length > 0) {
1129
- const lastItem = pageResults[pageResults.length - 1];
1130
- const lastValue = lastItem[orderByColumn];
1131
- nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
1132
- }
1133
- return {
1134
- data: entities,
1135
- hasNextPage,
1136
- nextCursor,
1137
- count: entities.length,
1138
- };
1139
- }
1140
- catch (e) {
1141
- try {
1142
- await transactionHolder.rollback();
1143
- }
1144
- catch (rollbackError) {
1145
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1146
- }
1147
- throw e;
1148
- }
1149
- finally {
1150
- try {
1151
- await transactionHolder.close();
1152
- }
1153
- catch (closeError) {
1154
- this.logger.error(`Failed to close transaction: ${closeError}`);
1155
- }
1156
- }
1157
- }
1158
283
  async find(entity, findOption = {}) {
284
+ return this.findInternal(entity, findOption);
285
+ }
286
+ async findInternal(entity, findOption = {}, existingSession) {
1159
287
  const { select, orderBy, where, take, groupBy, having } = findOption;
1160
288
  const { limit } = findOption;
1161
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
1162
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
1163
- try {
1164
- const readNode = this.getReadNode(findOption.useMaster);
1165
- if (readNode) {
1166
- await transactionHolder.connectToNode(readNode);
1167
- }
1168
- else {
1169
- await transactionHolder.connect();
1170
- }
1171
- await transactionHolder.startTransaction();
1172
- if (this.isMySqlFamily()) {
1173
- await transactionHolder.query("SET autocommit = 0");
1174
- }
1175
- const metadata = this.resolveEntityMetadata(entity);
289
+ const readNode = this.getReadNode(findOption.useMaster);
290
+ return this.executeInTransaction(async (session) => {
291
+ const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
292
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1176
293
  if (!metadata) {
1177
294
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1178
295
  }
@@ -1180,13 +297,13 @@ class EntityManager {
1180
297
  const selectMap = [];
1181
298
  const whereMap = [];
1182
299
  const orderByMap = [];
1183
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
300
+ const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1184
301
  const eagerRelations = manyToOneRelations.filter((rel) => {
1185
302
  const isEager = rel.option?.eager === true;
1186
303
  const isInRelations = findOption.relations?.includes(rel.columnName);
1187
304
  return isEager || isInRelations;
1188
305
  });
1189
- const oneToOneRelations = this.resolveOneToOneMetadata(entity);
306
+ const oneToOneRelations = this.resolver.resolveOneToOneMetadata(entity);
1190
307
  const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
1191
308
  if (!rel.joinColumn)
1192
309
  return false;
@@ -1215,7 +332,7 @@ class EntityManager {
1215
332
  }
1216
333
  for (const rel of eagerRelations) {
1217
334
  const RelatedEntity = rel.getMappingEntity();
1218
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
335
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1219
336
  if (!relatedMetadata)
1220
337
  continue;
1221
338
  const relatedName = relatedMetadata.name || RelatedEntity.name;
@@ -1226,7 +343,7 @@ class EntityManager {
1226
343
  }
1227
344
  for (const rel of eagerOneToOneRelations) {
1228
345
  const RelatedEntity = rel.getRelatedEntity();
1229
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
346
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1230
347
  if (!relatedMetadata)
1231
348
  continue;
1232
349
  const relatedName = relatedMetadata.name || RelatedEntity.name;
@@ -1246,7 +363,7 @@ class EntityManager {
1246
363
  }
1247
364
  }
1248
365
  }
1249
- const deletedAtColumn = this.getDeletedAtColumn(entity);
366
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
1250
367
  if (deletedAtColumn && !findOption.withDeleted) {
1251
368
  if (hasEagerJoins) {
1252
369
  whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
@@ -1264,7 +381,7 @@ class EntityManager {
1264
381
  qb.select(selectMap).from(this.wrap(tableName));
1265
382
  for (const rel of eagerRelations) {
1266
383
  const RelatedEntity = rel.getMappingEntity();
1267
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
384
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1268
385
  if (!relatedMetadata)
1269
386
  continue;
1270
387
  const relatedTableName = relatedMetadata.name || RelatedEntity.name;
@@ -1277,7 +394,7 @@ class EntityManager {
1277
394
  }
1278
395
  for (const rel of eagerOneToOneRelations) {
1279
396
  const RelatedEntity = rel.getRelatedEntity();
1280
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
397
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1281
398
  if (!relatedMetadata)
1282
399
  continue;
1283
400
  const relatedTableName = relatedMetadata.name || RelatedEntity.name;
@@ -1301,21 +418,16 @@ class EntityManager {
1301
418
  qb.orderBy(orderByMap);
1302
419
  if (Array.isArray(limit)) {
1303
420
  let [offset, count] = limit;
1304
- if (offset < 0) {
421
+ if (offset < 0)
1305
422
  offset = 0;
1306
- }
1307
- if (count < 0) {
423
+ if (count < 0)
1308
424
  count = 0;
1309
- }
1310
- if (count === 0) {
425
+ if (count === 0)
1311
426
  count = 1;
1312
- }
1313
- if (take && take > 0) {
427
+ if (take && take > 0)
1314
428
  count = take;
1315
- }
1316
- if (this.isMySqlFamily()) {
429
+ if (this.isMySqlFamily())
1317
430
  qb.setDatabaseType("mysql");
1318
- }
1319
431
  qb.limit([offset, count]);
1320
432
  }
1321
433
  else {
@@ -1327,13 +439,12 @@ class EntityManager {
1327
439
  const effectiveTimeout = findOption.timeout ?? this.defaultQueryTimeout;
1328
440
  if (effectiveTimeout && effectiveTimeout > 0 && this.driver) {
1329
441
  const timeoutSql = this.driver.setQueryTimeout(effectiveTimeout);
1330
- await transactionHolder.query(timeoutSql);
442
+ await session.query(timeoutSql);
1331
443
  }
1332
444
  const queryStartTime = Date.now();
1333
445
  this.beginTrackQuery();
1334
- const queryResult = (await transactionHolder.query(resultQuery));
446
+ const queryResult = (await session.query(resultQuery));
1335
447
  this.trackQuery(entity.name, resultQuery.text ?? String(resultQuery), Date.now() - queryStartTime);
1336
- await transactionHolder.commit();
1337
448
  const { results } = queryResult;
1338
449
  if (!results || results.length === 0) {
1339
450
  return undefined;
@@ -1352,9 +463,9 @@ class EntityManager {
1352
463
  if (findOption.relations &&
1353
464
  findOption.relations.length > 0 &&
1354
465
  entityResult) {
1355
- await this.loadOneToManyRelations(entity, entityResult, findOption.relations);
1356
- await this.loadManyToManyRelations(entity, entityResult, findOption.relations);
1357
- await this.loadOneToOneRelations(entity, entityResult, findOption.relations);
466
+ await this.relationLoader.loadOneToManyRelations(entity, entityResult, findOption.relations, session);
467
+ await this.relationLoader.loadManyToManyRelations(entity, entityResult, findOption.relations, session);
468
+ await this.relationLoader.loadOneToOneRelations(entity, entityResult, findOption.relations, session);
1358
469
  }
1359
470
  const lazyRelations = manyToOneRelations.filter((rel) => {
1360
471
  return rel.option?.lazy === true && rel.option?.eager !== true;
@@ -1370,7 +481,7 @@ class EntityManager {
1370
481
  const fkValue = item[joinColumn];
1371
482
  if (fkValue === undefined || fkValue === null)
1372
483
  continue;
1373
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
484
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1374
485
  if (!relatedMetadata)
1375
486
  continue;
1376
487
  const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
@@ -1386,69 +497,111 @@ class EntityManager {
1386
497
  }
1387
498
  }
1388
499
  }
1389
- return entityResult;
500
+ return entityResult;
501
+ }, existingSession, readNode);
502
+ }
503
+ async findWithCursor(entity, option = {}) {
504
+ const metadata = this.resolver.resolveEntityMetadata(entity);
505
+ if (!metadata) {
506
+ throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
507
+ }
508
+ const pk = metadata.columns.find((column) => column.options?.primary);
509
+ const orderByColumn = option.orderBy ?? pk?.name;
510
+ if (!orderByColumn) {
511
+ throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
512
+ }
513
+ if (!option.orderBy && pk) {
514
+ this.warnIfNonSortablePk(entity.name, pk);
515
+ }
516
+ const direction = option.direction ?? "ASC";
517
+ const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
518
+ let cursorValue = null;
519
+ if (option.cursor) {
520
+ cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
521
+ if (cursorValue === null) {
522
+ throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
523
+ }
1390
524
  }
1391
- catch (e) {
1392
- try {
1393
- await transactionHolder.rollback();
525
+ const where = { ...(option.where ?? {}) };
526
+ const readNode = this.getReadNode(option.useMaster);
527
+ return this.executeInTransaction(async (session) => {
528
+ const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
529
+ const tableName = metadata.name;
530
+ const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
531
+ const selectMap = metadata.columns.map((column) => this.wrap(column.name));
532
+ const whereMap = [];
533
+ for (const key in where) {
534
+ const value = where[key];
535
+ if (value !== undefined && value !== null) {
536
+ whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
537
+ }
1394
538
  }
1395
- catch (rollbackError) {
1396
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
539
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
540
+ if (deletedAtColumn) {
541
+ whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
1397
542
  }
1398
- throw e;
1399
- }
1400
- finally {
1401
- try {
1402
- await transactionHolder.close();
543
+ if (cursorValue !== null) {
544
+ if (direction === "ASC") {
545
+ whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
546
+ }
547
+ else {
548
+ whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
549
+ }
1403
550
  }
1404
- catch (closeError) {
1405
- this.logger.error(`Failed to close transaction: ${closeError}`);
551
+ qb.select(selectMap)
552
+ .from(this.wrap(tableName))
553
+ .where(whereMap)
554
+ .orderBy([{ column: this.wrap(orderByColumn), direction }]);
555
+ qb.limit(pageSize + 1);
556
+ const resultQuery = qb.build();
557
+ const queryResult = (await session.query(resultQuery));
558
+ const { results } = queryResult;
559
+ if (!results || results.length === 0) {
560
+ return {
561
+ data: [],
562
+ hasNextPage: false,
563
+ nextCursor: null,
564
+ count: 0,
565
+ };
1406
566
  }
1407
- }
1408
- }
1409
- resolveSelectColumns(select) {
1410
- if (Array.isArray(select)) {
1411
- return select.map((col) => String(col));
1412
- }
1413
- const columns = [];
1414
- for (const key in select) {
1415
- if (select[key] === true) {
1416
- columns.push(key);
567
+ const hasNextPage = results.length > pageSize;
568
+ const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
569
+ const entities = resultTransformer.toEntities(entity, {
570
+ results: pageResults,
571
+ fields: queryResult.fields,
572
+ });
573
+ let nextCursor = null;
574
+ if (hasNextPage && pageResults.length > 0) {
575
+ const lastItem = pageResults[pageResults.length - 1];
576
+ const lastValue = lastItem[orderByColumn];
577
+ nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
1417
578
  }
1418
- }
1419
- return columns;
1420
- }
1421
- wrap(columnName) {
1422
- if (this.driver && "wrap" in this.driver) {
1423
- return this.driver.wrap(columnName);
1424
- }
1425
- if (this.isPostgres()) {
1426
- return `"${columnName.replace(/"/g, '""')}"`;
1427
- }
1428
- return `\`${columnName.replace(/`/g, "``")}\``;
1429
- }
1430
- isMySqlFamily() {
1431
- const t = this.dbType ?? this.client.type;
1432
- return ["mysql", "mariadb"].includes(t);
579
+ return {
580
+ data: entities,
581
+ hasNextPage,
582
+ nextCursor,
583
+ count: entities.length,
584
+ };
585
+ }, undefined, readNode);
1433
586
  }
1434
- isPostgres() {
1435
- const t = this.dbType ?? this.client.type;
1436
- return t === "postgres";
587
+ async findAndCount(entity, findOption = {}) {
588
+ return this.executeInTransaction(async (session) => {
589
+ const entities = await this.findInternal(entity, findOption, session);
590
+ const totalCount = await this.aggregateHandler.aggregate(entity, "COUNT", "*", findOption.where, session);
591
+ return [entities, totalCount];
592
+ });
1437
593
  }
1438
594
  async save(entity, item) {
1439
- const metadata = this.resolveEntityMetadata(entity);
595
+ return this.saveInternal(entity, item);
596
+ }
597
+ async saveInternal(entity, item, existingSession) {
598
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1440
599
  if (!metadata) {
1441
600
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1442
601
  }
1443
602
  EntityValidator_1.EntityValidator.validate(entity, item);
1444
- await this.cascadeSaveManyToOne(entity, item);
1445
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1446
- try {
1447
- await transactionManager.connect();
1448
- await transactionManager.startTransaction();
1449
- if (this.isMySqlFamily()) {
1450
- await transactionManager.query("SET autocommit = 0");
1451
- }
603
+ await this.cascadeHandler.cascadeSaveManyToOne(entity, item);
604
+ return this.executeInTransaction(async (session) => {
1452
605
  const pkColumns = metadata.columns.filter((column) => column.options?.primary);
1453
606
  const pk = pkColumns[0];
1454
607
  const hasAutoIncrementPk = pkColumns.some((col) => col.options?.autoIncrement);
@@ -1474,7 +627,7 @@ class EntityManager {
1474
627
  return where;
1475
628
  };
1476
629
  if (isInsert) {
1477
- await this.runHooks(entity, item, "beforeInsert");
630
+ await this.cascadeHandler.runHooks(entity, item, "beforeInsert");
1478
631
  await this.eventEmitter.emit("beforeInsert", { entity, data: item });
1479
632
  await this.notifySubscribers(entity, "beforeInsert", {
1480
633
  entity: item,
@@ -1495,21 +648,28 @@ class EntityManager {
1495
648
  return item[column.name];
1496
649
  });
1497
650
  const now = new Date().toISOString();
1498
- const createTsCol = this.getCreateTimestampColumn(entity);
651
+ const createTsCol = this.resolver.getCreateTimestampColumn(entity);
1499
652
  if (createTsCol) {
1500
653
  const idx = insertableColumns.findIndex((col) => col.name === createTsCol);
1501
654
  if (idx >= 0) {
1502
655
  values[idx] = item[createTsCol]?.toISOString?.() ?? now;
1503
656
  }
1504
657
  }
1505
- const updateTsCol = this.getUpdateTimestampColumn(entity);
658
+ const updateTsCol = this.resolver.getUpdateTimestampColumn(entity);
1506
659
  if (updateTsCol) {
1507
660
  const idx = insertableColumns.findIndex((col) => col.name === updateTsCol);
1508
661
  if (idx >= 0) {
1509
662
  values[idx] = item[updateTsCol]?.toISOString?.() ?? now;
1510
663
  }
1511
664
  }
1512
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
665
+ const versionCol = this.resolver.getVersionColumn(entity);
666
+ if (versionCol) {
667
+ const versionIdx = insertableColumns.findIndex((col) => col.name === versionCol);
668
+ if (versionIdx >= 0) {
669
+ values[versionIdx] = 1;
670
+ }
671
+ }
672
+ const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1513
673
  for (const rel of manyToOneRelations) {
1514
674
  if (!rel.joinColumn)
1515
675
  continue;
@@ -1522,7 +682,7 @@ class EntityManager {
1522
682
  }
1523
683
  else if (relatedValue && typeof relatedValue === "object") {
1524
684
  const RelatedEntity = rel.getMappingEntity();
1525
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
685
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1526
686
  if (relatedMeta) {
1527
687
  const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
1528
688
  if (relatedPk) {
@@ -1557,21 +717,20 @@ class EntityManager {
1557
717
  `;
1558
718
  const saveQueryStart = Date.now();
1559
719
  this.beginTrackQuery();
1560
- const queryResult = (await transactionManager.query(insertSql));
720
+ const queryResult = (await session.query(insertSql));
1561
721
  this.trackQuery(entity.name, insertSql.text ?? String(insertSql), Date.now() - saveQueryStart);
1562
- await transactionManager.commit();
1563
722
  if (this.isMySqlFamily()) {
1564
723
  const findWhere = hasAutoIncrementPk
1565
724
  ? { [pk.name]: queryResult?.results?.insertId }
1566
725
  : buildPkFindWhere();
1567
- const result = await this.findOne(entity, {
726
+ const result = await this.findOneInternal(entity, {
1568
727
  where: findWhere,
1569
- });
728
+ }, session);
1570
729
  const cascadeId = hasAutoIncrementPk
1571
730
  ? queryResult?.results?.insertId
1572
731
  : primaryKeyValue;
1573
- await this.cascadeSaveOneToMany(entity, item, cascadeId);
1574
- await this.runHooks(entity, item, "afterInsert");
732
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
733
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1575
734
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1576
735
  await this.notifySubscribers(entity, "afterInsert", {
1577
736
  entity: item,
@@ -1582,12 +741,12 @@ class EntityManager {
1582
741
  if (isPostgres && queryResult?.results?.length > 0) {
1583
742
  const returnedRow = queryResult.results[0];
1584
743
  const findWhere = buildPkFindWhere(returnedRow);
1585
- const result = await this.findOne(entity, {
744
+ const result = await this.findOneInternal(entity, {
1586
745
  where: findWhere,
1587
- });
746
+ }, session);
1588
747
  const cascadeId = returnedRow[pk.name];
1589
- await this.cascadeSaveOneToMany(entity, item, cascadeId);
1590
- await this.runHooks(entity, item, "afterInsert");
748
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
749
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1591
750
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1592
751
  await this.notifySubscribers(entity, "afterInsert", {
1593
752
  entity: item,
@@ -1595,7 +754,7 @@ class EntityManager {
1595
754
  });
1596
755
  return result;
1597
756
  }
1598
- await this.runHooks(entity, item, "afterInsert");
757
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1599
758
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1600
759
  await this.notifySubscribers(entity, "afterInsert", {
1601
760
  entity: item,
@@ -1603,22 +762,25 @@ class EntityManager {
1603
762
  });
1604
763
  return queryResult;
1605
764
  }
1606
- await this.runHooks(entity, item, "beforeUpdate");
765
+ await this.cascadeHandler.runHooks(entity, item, "beforeUpdate");
1607
766
  await this.eventEmitter.emit("beforeUpdate", { entity, data: item });
1608
767
  await this.notifySubscribers(entity, "beforeUpdate", {
1609
768
  entity: item,
1610
769
  manager: this,
1611
770
  });
771
+ const versionColName = this.resolver.getVersionColumn(entity);
1612
772
  const pkColumnNames = new Set(pkColumns.map((col) => col.name));
1613
773
  const updatableColumns = metadata.columns.filter((column) => {
1614
774
  if (pkColumnNames.has(column.name))
1615
775
  return false;
776
+ if (versionColName && column.name === versionColName)
777
+ return false;
1616
778
  return item[column.name] !== undefined;
1617
779
  });
1618
780
  const updateMap = updatableColumns.map((column) => {
1619
781
  return (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(column.name))} = ${item[column.name]}`;
1620
782
  });
1621
- const updateTsColName = this.getUpdateTimestampColumn(entity);
783
+ const updateTsColName = this.resolver.getUpdateTimestampColumn(entity);
1622
784
  if (updateTsColName) {
1623
785
  const existingIdx = updatableColumns.findIndex((col) => col.name === updateTsColName);
1624
786
  const updateNow = new Date().toISOString();
@@ -1631,7 +793,7 @@ class EntityManager {
1631
793
  }
1632
794
  }
1633
795
  const updatedColumnNames = new Set(updatableColumns.map((col) => col.name));
1634
- const updateManyToOneRelations = this.resolveManyToOneMetadata(entity);
796
+ const updateManyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1635
797
  for (const rel of updateManyToOneRelations) {
1636
798
  if (!rel.joinColumn)
1637
799
  continue;
@@ -1652,7 +814,7 @@ class EntityManager {
1652
814
  }
1653
815
  else if (typeof relatedValue === "object") {
1654
816
  const RelatedEntity = rel.getMappingEntity();
1655
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
817
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1656
818
  if (relatedMeta) {
1657
819
  const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
1658
820
  if (relatedPk) {
@@ -1673,6 +835,15 @@ class EntityManager {
1673
835
  }
1674
836
  }
1675
837
  const pkWhereClauses = buildPkWhere();
838
+ const currentVersion = versionColName
839
+ ? item[versionColName]
840
+ : undefined;
841
+ if (versionColName) {
842
+ updateMap.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} = ${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} + 1`);
843
+ if (currentVersion !== undefined && currentVersion !== null) {
844
+ pkWhereClauses.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} = ${currentVersion}`);
845
+ }
846
+ }
1676
847
  if (updateMap.length > 0) {
1677
848
  const updateSql = (0, sql_template_tag_1.default) `
1678
849
  UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))}
@@ -1681,117 +852,57 @@ class EntityManager {
1681
852
  `;
1682
853
  const updateStart = Date.now();
1683
854
  this.beginTrackQuery();
1684
- await transactionManager.query(updateSql);
855
+ const updateResult = (await session.query(updateSql));
1685
856
  this.trackQuery(entity.name, updateSql.text ?? String(updateSql), Date.now() - updateStart);
857
+ if (versionColName && currentVersion !== undefined && currentVersion !== null) {
858
+ let affected = 0;
859
+ if (this.isMySqlFamily()) {
860
+ affected = updateResult?.results?.affectedRows ?? 0;
861
+ }
862
+ else {
863
+ affected = updateResult?.rowCount ?? 0;
864
+ }
865
+ if (affected === 0) {
866
+ throw new OptimisticLockError_1.OptimisticLockError(entity.name, currentVersion);
867
+ }
868
+ }
1686
869
  }
1687
- await transactionManager.commit();
1688
- await this.cascadeSaveOneToMany(entity, item, primaryKeyValue);
1689
- await this.runHooks(entity, item, "afterUpdate");
870
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, primaryKeyValue);
871
+ await this.cascadeHandler.runHooks(entity, item, "afterUpdate");
1690
872
  await this.eventEmitter.emit("afterUpdate", { entity, data: item });
1691
873
  await this.notifySubscribers(entity, "afterUpdate", {
1692
874
  entity: item,
1693
875
  manager: this,
1694
876
  });
1695
- const result = await this.findOne(entity, {
877
+ const result = await this.findOneInternal(entity, {
1696
878
  where: buildPkFindWhere(),
1697
- });
879
+ }, session);
1698
880
  return result;
1699
- }
1700
- catch (e) {
1701
- try {
1702
- await transactionManager.rollback();
1703
- }
1704
- catch (rollbackError) {
1705
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1706
- }
1707
- throw e;
1708
- }
1709
- finally {
1710
- try {
1711
- await transactionManager.close();
1712
- }
1713
- catch (closeError) {
1714
- this.logger.error(`Failed to close transaction: ${closeError}`);
1715
- }
1716
- }
1717
- }
1718
- async deleteMany(entity, ids) {
1719
- if (ids.length === 0) {
1720
- return { affected: 0 };
1721
- }
1722
- const metadata = this.resolveEntityMetadata(entity);
1723
- if (!metadata) {
1724
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1725
- }
1726
- const pk = metadata.columns.find((column) => column.options?.primary);
1727
- if (!pk) {
1728
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1729
- }
1730
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1731
- try {
1732
- await transactionManager.connect();
1733
- await transactionManager.startTransaction();
1734
- if (this.isMySqlFamily()) {
1735
- await transactionManager.query("SET autocommit = 0");
1736
- }
1737
- const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
1738
- const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${(0, sql_template_tag_1.raw)(this.wrap(pk.name))} IN (${placeholders})`;
1739
- const queryResult = (await transactionManager.query(deleteQuery));
1740
- await transactionManager.commit();
1741
- let affected = 0;
1742
- if (this.isMySqlFamily()) {
1743
- affected = queryResult?.results?.affectedRows ?? 0;
1744
- }
1745
- else {
1746
- affected = queryResult?.rowCount ?? 0;
1747
- }
1748
- return { affected };
1749
- }
1750
- catch (e) {
1751
- try {
1752
- await transactionManager.rollback();
1753
- }
1754
- catch (rollbackError) {
1755
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1756
- }
1757
- throw e;
1758
- }
1759
- finally {
1760
- try {
1761
- await transactionManager.close();
1762
- }
1763
- catch (closeError) {
1764
- this.logger.error(`Failed to close transaction: ${closeError}`);
1765
- }
1766
- }
881
+ }, existingSession);
1767
882
  }
1768
883
  async saveMany(entity, items) {
1769
884
  if (items.length === 0) {
1770
885
  return [];
1771
886
  }
1772
- const results = [];
1773
- for (const item of items) {
1774
- const saved = await this.save(entity, item);
1775
- results.push(saved);
1776
- }
1777
- return results;
887
+ return this.executeInTransaction(async (session) => {
888
+ const results = [];
889
+ for (const item of items) {
890
+ const saved = await this.saveInternal(entity, item, session);
891
+ results.push(saved);
892
+ }
893
+ return results;
894
+ });
1778
895
  }
1779
896
  async insertMany(entity, items) {
1780
897
  if (items.length === 0) {
1781
898
  return { affected: 0 };
1782
899
  }
1783
- const metadata = this.resolveEntityMetadata(entity);
900
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1784
901
  if (!metadata) {
1785
902
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1786
903
  }
1787
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1788
- try {
1789
- await transactionManager.connect();
1790
- await transactionManager.startTransaction();
1791
- if (this.isMySqlFamily()) {
1792
- await transactionManager.query("SET autocommit = 0");
1793
- }
1794
- const deletedAtColumn = this.getDeletedAtColumn(entity);
904
+ return this.executeInTransaction(async (session) => {
905
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
1795
906
  const timestampTypes = new Set(["datetime", "timestamp", "date"]);
1796
907
  const timestampColumns = metadata.columns.filter((col) => col.options?.type &&
1797
908
  timestampTypes.has(col.options.type) &&
@@ -1806,6 +917,14 @@ class EntityManager {
1806
917
  }
1807
918
  }
1808
919
  }
920
+ const versionCol = this.resolver.getVersionColumn(entity);
921
+ if (versionCol) {
922
+ for (const item of items) {
923
+ if (item[versionCol] == null) {
924
+ item[versionCol] = 1;
925
+ }
926
+ }
927
+ }
1809
928
  const insertableColumns = metadata.columns.filter((column) => {
1810
929
  const isAutoIncrement = column.options?.autoIncrement;
1811
930
  if (!isAutoIncrement)
@@ -1814,7 +933,7 @@ class EntityManager {
1814
933
  item[column.name] !== undefined);
1815
934
  });
1816
935
  const columns = insertableColumns.map((column) => (0, sql_template_tag_1.raw)(this.wrap(column.name)));
1817
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
936
+ const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1818
937
  const fkColumns = [];
1819
938
  for (const rel of manyToOneRelations) {
1820
939
  if (!rel.joinColumn)
@@ -1837,7 +956,7 @@ class EntityManager {
1837
956
  if (relatedValue != null) {
1838
957
  if (typeof relatedValue === "object") {
1839
958
  const RelatedEntity = fk.relMeta.getMappingEntity();
1840
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
959
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1841
960
  const relatedPk = relatedMeta?.columns.find((col) => col.options?.primary);
1842
961
  rowValues.push(relatedPk ? relatedValue[relatedPk.name] ?? null : null);
1843
962
  }
@@ -1852,143 +971,34 @@ class EntityManager {
1852
971
  rowValues.push(null);
1853
972
  }
1854
973
  }
1855
- return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
1856
- });
1857
- const queryStr = (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} (${(0, sql_template_tag_1.join)(columns, ", ")}) VALUES ${(0, sql_template_tag_1.join)(valueRows, ", ")}`;
1858
- const queryResult = (await transactionManager.query(queryStr));
1859
- await transactionManager.commit();
1860
- let affected = items.length;
1861
- if (this.isMySqlFamily()) {
1862
- affected = queryResult?.results?.affectedRows ?? items.length;
1863
- }
1864
- else if (queryResult?.rowCount !== undefined) {
1865
- affected = queryResult.rowCount;
1866
- }
1867
- return { affected };
1868
- }
1869
- catch (e) {
1870
- try {
1871
- await transactionManager.rollback();
1872
- }
1873
- catch (rollbackError) {
1874
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1875
- }
1876
- throw e;
1877
- }
1878
- finally {
1879
- try {
1880
- await transactionManager.close();
1881
- }
1882
- catch (closeError) {
1883
- this.logger.error(`Failed to close transaction: ${closeError}`);
1884
- }
1885
- }
1886
- }
1887
- async upsert(entity, data, conflictColumns) {
1888
- const metadata = this.resolveEntityMetadata(entity);
1889
- if (!metadata) {
1890
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1891
- }
1892
- if (!this.driver) {
1893
- throw new Error("Driver is not initialized.");
1894
- }
1895
- const pkColumns = metadata.columns
1896
- .filter((col) => col.options?.primary)
1897
- .map((col) => col.name);
1898
- const resolvedConflictColumns = conflictColumns ?? pkColumns;
1899
- if (resolvedConflictColumns.length === 0) {
1900
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1901
- }
1902
- const insertableColumns = metadata.columns.filter((col) => {
1903
- const value = data[col.name];
1904
- if (col.options?.autoIncrement &&
1905
- (value === null || value === undefined)) {
1906
- return false;
1907
- }
1908
- return value !== undefined;
1909
- });
1910
- if (insertableColumns.length === 0) {
1911
- return;
1912
- }
1913
- const conflictSet = new Set(resolvedConflictColumns);
1914
- const updateColumnNames = insertableColumns
1915
- .map((col) => col.name)
1916
- .filter((name) => !conflictSet.has(name));
1917
- const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
1918
- const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
1919
- const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
1920
- const tableName = this.wrap(metadata.name);
1921
- if (wrappedUpdate.length === 0) {
1922
- return;
1923
- }
1924
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1925
- try {
1926
- await transactionManager.connect();
1927
- await transactionManager.startTransaction();
1928
- if (this.isMySqlFamily()) {
1929
- await transactionManager.query("SET autocommit = 0");
1930
- }
1931
- const columnValues = insertableColumns.map((col) => data[col.name]);
1932
- const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
1933
- await transactionManager.query(upsertSql);
1934
- await transactionManager.commit();
1935
- }
1936
- catch (e) {
1937
- try {
1938
- await transactionManager.rollback();
1939
- }
1940
- catch (rollbackError) {
1941
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1942
- }
1943
- throw e;
1944
- }
1945
- finally {
1946
- try {
1947
- await transactionManager.close();
974
+ return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
975
+ });
976
+ const queryStr = (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} (${(0, sql_template_tag_1.join)(columns, ", ")}) VALUES ${(0, sql_template_tag_1.join)(valueRows, ", ")}`;
977
+ const queryResult = (await session.query(queryStr));
978
+ let affected = items.length;
979
+ if (this.isMySqlFamily()) {
980
+ affected = queryResult?.results?.affectedRows ?? items.length;
1948
981
  }
1949
- catch (closeError) {
1950
- this.logger.error(`Failed to close transaction: ${closeError}`);
982
+ else if (queryResult?.rowCount !== undefined) {
983
+ affected = queryResult.rowCount;
1951
984
  }
1952
- }
1953
- }
1954
- buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
1955
- const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1956
- const valueList = (0, sql_template_tag_1.join)(values, ", ");
1957
- if (this.isMySqlFamily()) {
1958
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
1959
- return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
1960
- }
1961
- const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1962
- if (this.isPostgres()) {
1963
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
1964
- return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
1965
- }
1966
- if ((this.dbType ?? this.client.type) === "sqlite") {
1967
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
1968
- return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
1969
- }
1970
- throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
985
+ return { affected };
986
+ });
1971
987
  }
1972
988
  async delete(entity, criteria) {
1973
- const metadata = this.resolveEntityMetadata(entity);
989
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1974
990
  if (!metadata) {
1975
991
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1976
992
  }
1977
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1978
- try {
1979
- await transactionManager.connect();
1980
- await transactionManager.startTransaction();
1981
- if (this.isMySqlFamily()) {
1982
- await transactionManager.query("SET autocommit = 0");
1983
- }
1984
- await this.runHooks(entity, criteria, "beforeDelete");
993
+ return this.executeInTransaction(async (session) => {
994
+ await this.cascadeHandler.runHooks(entity, criteria, "beforeDelete");
1985
995
  await this.eventEmitter.emit("beforeDelete", { entity, data: criteria });
1986
996
  await this.notifySubscribers(entity, "beforeDelete", {
1987
997
  entityClass: entity,
1988
998
  criteria,
1989
999
  manager: this,
1990
1000
  });
1991
- await this.cascadeDeleteOneToMany(entity, criteria);
1001
+ await this.cascadeHandler.cascadeDeleteOneToMany(entity, criteria);
1992
1002
  const whereMap = [];
1993
1003
  for (const key in criteria) {
1994
1004
  const value = criteria[key];
@@ -2003,9 +1013,8 @@ class EntityManager {
2003
1013
  const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${whereSql}`;
2004
1014
  const deleteStart = Date.now();
2005
1015
  this.beginTrackQuery();
2006
- const queryResult = (await transactionManager.query(deleteQuery));
1016
+ const queryResult = (await session.query(deleteQuery));
2007
1017
  this.trackQuery(entity.name, deleteQuery.text ?? String(deleteQuery), Date.now() - deleteStart);
2008
- await transactionManager.commit();
2009
1018
  let affected = 0;
2010
1019
  if (this.isMySqlFamily()) {
2011
1020
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -2013,7 +1022,7 @@ class EntityManager {
2013
1022
  else {
2014
1023
  affected = queryResult?.rowCount ?? 0;
2015
1024
  }
2016
- await this.runHooks(entity, criteria, "afterDelete");
1025
+ await this.cascadeHandler.runHooks(entity, criteria, "afterDelete");
2017
1026
  await this.eventEmitter.emit("afterDelete", { entity, data: criteria });
2018
1027
  await this.notifySubscribers(entity, "afterDelete", {
2019
1028
  entityClass: entity,
@@ -2021,27 +1030,36 @@ class EntityManager {
2021
1030
  manager: this,
2022
1031
  });
2023
1032
  return { affected };
1033
+ });
1034
+ }
1035
+ async deleteMany(entity, ids) {
1036
+ if (ids.length === 0) {
1037
+ return { affected: 0 };
2024
1038
  }
2025
- catch (e) {
2026
- try {
2027
- await transactionManager.rollback();
2028
- }
2029
- catch (rollbackError) {
2030
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
2031
- }
2032
- throw e;
1039
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1040
+ if (!metadata) {
1041
+ throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2033
1042
  }
2034
- finally {
2035
- try {
2036
- await transactionManager.close();
1043
+ const pk = metadata.columns.find((column) => column.options?.primary);
1044
+ if (!pk) {
1045
+ throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1046
+ }
1047
+ return this.executeInTransaction(async (session) => {
1048
+ const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
1049
+ const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${(0, sql_template_tag_1.raw)(this.wrap(pk.name))} IN (${placeholders})`;
1050
+ const queryResult = (await session.query(deleteQuery));
1051
+ let affected = 0;
1052
+ if (this.isMySqlFamily()) {
1053
+ affected = queryResult?.results?.affectedRows ?? 0;
2037
1054
  }
2038
- catch (closeError) {
2039
- this.logger.error(`Failed to close transaction: ${closeError}`);
1055
+ else {
1056
+ affected = queryResult?.rowCount ?? 0;
2040
1057
  }
2041
- }
1058
+ return { affected };
1059
+ });
2042
1060
  }
2043
1061
  async clear(entity) {
2044
- const metadata = this.resolveEntityMetadata(entity);
1062
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2045
1063
  if (!metadata) {
2046
1064
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2047
1065
  }
@@ -2051,21 +1069,15 @@ class EntityManager {
2051
1069
  await this.driver.clear(metadata.name);
2052
1070
  }
2053
1071
  async softDelete(entity, criteria) {
2054
- const metadata = this.resolveEntityMetadata(entity);
1072
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2055
1073
  if (!metadata) {
2056
1074
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2057
1075
  }
2058
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1076
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
2059
1077
  if (!deletedAtColumn) {
2060
1078
  throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Use delete() instead.`);
2061
1079
  }
2062
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
2063
- try {
2064
- await transactionManager.connect();
2065
- await transactionManager.startTransaction();
2066
- if (this.isMySqlFamily()) {
2067
- await transactionManager.query("SET autocommit = 0");
2068
- }
1080
+ return this.executeInTransaction(async (session) => {
2069
1081
  const whereMap = [];
2070
1082
  for (const key in criteria) {
2071
1083
  const value = criteria[key];
@@ -2079,8 +1091,7 @@ class EntityManager {
2079
1091
  const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2080
1092
  const nowExpr = this.isPostgres() ? (0, sql_template_tag_1.raw)("NOW()") : (0, sql_template_tag_1.raw)("NOW()");
2081
1093
  const updateQuery = (0, sql_template_tag_1.default) `UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} SET ${(0, sql_template_tag_1.raw)(this.wrap(deletedAtColumn))} = ${nowExpr} WHERE ${whereSql}`;
2082
- const queryResult = (await transactionManager.query(updateQuery));
2083
- await transactionManager.commit();
1094
+ const queryResult = (await session.query(updateQuery));
2084
1095
  let affected = 0;
2085
1096
  if (this.isMySqlFamily()) {
2086
1097
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -2089,41 +1100,18 @@ class EntityManager {
2089
1100
  affected = queryResult?.rowCount ?? 0;
2090
1101
  }
2091
1102
  return { affected };
2092
- }
2093
- catch (e) {
2094
- try {
2095
- await transactionManager.rollback();
2096
- }
2097
- catch (rollbackError) {
2098
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
2099
- }
2100
- throw e;
2101
- }
2102
- finally {
2103
- try {
2104
- await transactionManager.close();
2105
- }
2106
- catch (closeError) {
2107
- this.logger.error(`Failed to close transaction: ${closeError}`);
2108
- }
2109
- }
1103
+ });
2110
1104
  }
2111
1105
  async restore(entity, criteria) {
2112
- const metadata = this.resolveEntityMetadata(entity);
1106
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2113
1107
  if (!metadata) {
2114
1108
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2115
1109
  }
2116
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1110
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
2117
1111
  if (!deletedAtColumn) {
2118
1112
  throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Cannot restore.`);
2119
1113
  }
2120
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
2121
- try {
2122
- await transactionManager.connect();
2123
- await transactionManager.startTransaction();
2124
- if (this.isMySqlFamily()) {
2125
- await transactionManager.query("SET autocommit = 0");
2126
- }
1114
+ return this.executeInTransaction(async (session) => {
2127
1115
  const whereMap = [];
2128
1116
  for (const key in criteria) {
2129
1117
  const value = criteria[key];
@@ -2136,8 +1124,7 @@ class EntityManager {
2136
1124
  }
2137
1125
  const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2138
1126
  const restoreQuery = (0, sql_template_tag_1.default) `UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} SET ${(0, sql_template_tag_1.raw)(this.wrap(deletedAtColumn))} = NULL WHERE ${whereSql}`;
2139
- const queryResult = (await transactionManager.query(restoreQuery));
2140
- await transactionManager.commit();
1127
+ const queryResult = (await session.query(restoreQuery));
2141
1128
  let affected = 0;
2142
1129
  if (this.isMySqlFamily()) {
2143
1130
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -2146,138 +1133,167 @@ class EntityManager {
2146
1133
  affected = queryResult?.rowCount ?? 0;
2147
1134
  }
2148
1135
  return { affected };
1136
+ });
1137
+ }
1138
+ async upsert(entity, data, conflictColumns) {
1139
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1140
+ if (!metadata) {
1141
+ throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2149
1142
  }
2150
- catch (e) {
2151
- try {
2152
- await transactionManager.rollback();
2153
- }
2154
- catch (rollbackError) {
2155
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
2156
- }
2157
- throw e;
1143
+ if (!this.driver) {
1144
+ throw new Error("Driver is not initialized.");
2158
1145
  }
2159
- finally {
2160
- try {
2161
- await transactionManager.close();
2162
- }
2163
- catch (closeError) {
2164
- this.logger.error(`Failed to close transaction: ${closeError}`);
1146
+ const pkColumns = metadata.columns
1147
+ .filter((col) => col.options?.primary)
1148
+ .map((col) => col.name);
1149
+ const resolvedConflictColumns = conflictColumns ?? pkColumns;
1150
+ if (resolvedConflictColumns.length === 0) {
1151
+ throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1152
+ }
1153
+ const insertableColumns = metadata.columns.filter((col) => {
1154
+ const value = data[col.name];
1155
+ if (col.options?.autoIncrement &&
1156
+ (value === null || value === undefined)) {
1157
+ return false;
2165
1158
  }
1159
+ return value !== undefined;
1160
+ });
1161
+ if (insertableColumns.length === 0) {
1162
+ return;
1163
+ }
1164
+ const conflictSet = new Set(resolvedConflictColumns);
1165
+ const updateColumnNames = insertableColumns
1166
+ .map((col) => col.name)
1167
+ .filter((name) => !conflictSet.has(name));
1168
+ const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
1169
+ const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
1170
+ const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
1171
+ const tableName = this.wrap(metadata.name);
1172
+ if (wrappedUpdate.length === 0) {
1173
+ return;
2166
1174
  }
1175
+ await this.executeInTransaction(async (session) => {
1176
+ const columnValues = insertableColumns.map((col) => data[col.name]);
1177
+ const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
1178
+ await session.query(upsertSql);
1179
+ });
2167
1180
  }
2168
- async cascadeSaveOneToMany(entity, item, savedParentId) {
2169
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
2170
- for (const rel of oneToManyMeta) {
2171
- const children = item[rel.propertyKey];
2172
- if (!children || !Array.isArray(children) || children.length === 0)
2173
- continue;
2174
- const RelatedEntity = rel.getRelatedEntity();
2175
- if (!(0, CascadeType_1.hasCascade)(rel.cascade, "insert") &&
2176
- !(0, CascadeType_1.hasCascade)(rel.cascade, "update"))
2177
- continue;
2178
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
2179
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
2180
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
2181
- for (const child of children) {
2182
- child[fkColumn] = savedParentId;
2183
- await this.save(RelatedEntity, child);
2184
- }
1181
+ buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
1182
+ const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1183
+ const valueList = (0, sql_template_tag_1.join)(values, ", ");
1184
+ if (this.isMySqlFamily()) {
1185
+ const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
1186
+ return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
1187
+ }
1188
+ const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1189
+ if (this.isPostgres()) {
1190
+ const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
1191
+ return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
1192
+ }
1193
+ if ((this.dbType ?? this.client.type) === "sqlite") {
1194
+ const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
1195
+ return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
2185
1196
  }
1197
+ throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
2186
1198
  }
2187
- async cascadeSaveManyToOne(entity, item) {
2188
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
2189
- for (const rel of manyToOneRelations) {
2190
- const relatedValue = item[rel.columnName];
2191
- if (!relatedValue || typeof relatedValue !== "object")
2192
- continue;
2193
- if (!(0, CascadeType_1.hasCascade)(rel.option?.cascade, "insert") &&
2194
- !(0, CascadeType_1.hasCascade)(rel.option?.cascade, "update"))
2195
- continue;
2196
- const RelatedEntity = rel.getMappingEntity();
2197
- const saved = await this.save(RelatedEntity, relatedValue);
2198
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
2199
- if (relatedMetadata) {
2200
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
2201
- if (relatedPk && rel.joinColumn) {
2202
- item[rel.joinColumn] = saved[relatedPk.name];
2203
- }
2204
- }
1199
+ async count(entity, where) {
1200
+ return this.aggregateHandler.count(entity, where);
1201
+ }
1202
+ async sum(entity, field, where) {
1203
+ return this.aggregateHandler.sum(entity, field, where);
1204
+ }
1205
+ async avg(entity, field, where) {
1206
+ return this.aggregateHandler.avg(entity, field, where);
1207
+ }
1208
+ async min(entity, field, where) {
1209
+ return this.aggregateHandler.min(entity, field, where);
1210
+ }
1211
+ async max(entity, field, where) {
1212
+ return this.aggregateHandler.max(entity, field, where);
1213
+ }
1214
+ async explain(entity, findOption = {}) {
1215
+ return this.explainHandler.explain(entity, findOption);
1216
+ }
1217
+ wrap(columnName) {
1218
+ if (this.driver && "wrap" in this.driver) {
1219
+ return this.driver.wrap(columnName);
1220
+ }
1221
+ if (this.isPostgres()) {
1222
+ return `"${columnName.replace(/"/g, '""')}"`;
2205
1223
  }
1224
+ return `\`${columnName.replace(/`/g, "``")}\``;
2206
1225
  }
2207
- async cascadeDeleteOneToMany(entity, criteria) {
2208
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
2209
- for (const rel of oneToManyMeta) {
2210
- if (!(0, CascadeType_1.hasCascade)(rel.cascade, "delete"))
2211
- continue;
2212
- const RelatedEntity = rel.getRelatedEntity();
2213
- const parentMetadata = this.resolveEntityMetadata(entity);
2214
- if (!parentMetadata)
2215
- continue;
2216
- const pk = parentMetadata.columns.find((col) => col.options?.primary);
2217
- if (!pk)
2218
- continue;
2219
- const parents = await this.find(entity, {
2220
- where: criteria,
2221
- });
2222
- if (!parents)
2223
- continue;
2224
- const parentArray = Array.isArray(parents) ? parents : [parents];
2225
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
2226
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
2227
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
2228
- for (const parent of parentArray) {
2229
- const parentId = parent[pk.name];
2230
- if (parentId === undefined || parentId === null)
2231
- continue;
2232
- await this.delete(RelatedEntity, {
2233
- [fkColumn]: parentId,
2234
- });
2235
- }
1226
+ isMySqlFamily() {
1227
+ const t = this.dbType ?? this.client.type;
1228
+ return ["mysql", "mariadb"].includes(t);
1229
+ }
1230
+ isPostgres() {
1231
+ const t = this.dbType ?? this.client.type;
1232
+ return t === "postgres";
1233
+ }
1234
+ isSqlite() {
1235
+ const t = this.dbType ?? this.client.type;
1236
+ return t === "sqlite";
1237
+ }
1238
+ warnIfNonSortablePk(entityName, pk) {
1239
+ const pkType = pk.options?.type;
1240
+ const numericTypes = new Set([
1241
+ "int", "number", "float", "double", "bigint",
1242
+ ]);
1243
+ if (!pkType || numericTypes.has(pkType)) {
1244
+ return;
1245
+ }
1246
+ const key = entityName;
1247
+ if (this.cursorPkWarned.has(key)) {
1248
+ return;
1249
+ }
1250
+ this.cursorPkWarned.add(key);
1251
+ const base = `[CursorPagination] '${entityName}' entity uses a non-numeric PK ` +
1252
+ `(type: ${pkType}). Cursor pagination defaults to PK ordering, ` +
1253
+ `which may not reflect insertion order for random values like UUID v4.`;
1254
+ if (this.isMySqlFamily()) {
1255
+ this.logger.warn(`${base} MySQL stores UUIDs as VARCHAR — lexicographic ordering ` +
1256
+ `does not match time-based ordering. Consider specifying ` +
1257
+ `orderBy: "createdAt" or using a sequential ID.`);
1258
+ }
1259
+ else if (this.isPostgres()) {
1260
+ this.logger.warn(`${base} PostgreSQL compares UUID values lexicographically. ` +
1261
+ `For time-ordered pagination, use UUID v7 (sortable) or specify ` +
1262
+ `orderBy: "createdAt".`);
1263
+ }
1264
+ else if (this.isSqlite()) {
1265
+ this.logger.warn(`${base} SQLite compares TEXT values lexicographically. ` +
1266
+ `Consider specifying orderBy: "createdAt" or using an INTEGER PK.`);
1267
+ }
1268
+ else {
1269
+ this.logger.warn(`${base} Consider specifying an explicit orderBy column ` +
1270
+ `(e.g. orderBy: "createdAt") for meaningful pagination order.`);
2236
1271
  }
2237
1272
  }
2238
- async aggregate(entity, fn, field, where) {
2239
- const metadata = this.resolveEntityMetadata(entity);
2240
- if (!metadata) {
2241
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1273
+ async executeInTransaction(fn, existingSession, readNodeOverride) {
1274
+ const reusable = existingSession ?? Transactional_1.transactionStorage.getStore();
1275
+ if (reusable) {
1276
+ return fn(reusable);
2242
1277
  }
2243
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1278
+ const session = new TransactionSessionManager_1.TransactionSessionManager();
2244
1279
  try {
2245
- await transactionManager.connect();
2246
- await transactionManager.startTransaction();
2247
- if (this.isMySqlFamily()) {
2248
- await transactionManager.query("SET autocommit = 0");
2249
- }
2250
- const tableName = metadata.name;
2251
- const selectExpr = (0, sql_template_tag_1.raw)(`${fn}(${field === "*" ? "*" : this.wrap(field)})`);
2252
- const whereMap = [];
2253
- if (where) {
2254
- for (const key in where) {
2255
- const value = where[key];
2256
- if (value !== undefined && value !== null) {
2257
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
2258
- }
2259
- }
2260
- }
2261
- let queryStr;
2262
- if (whereMap.length > 0) {
2263
- const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2264
- queryStr = (0, sql_template_tag_1.default) `SELECT ${selectExpr} AS ${(0, sql_template_tag_1.raw)(this.wrap("result"))} FROM ${(0, sql_template_tag_1.raw)(this.wrap(tableName))} WHERE ${whereSql}`;
1280
+ if (readNodeOverride) {
1281
+ await session.connectToNode(readNodeOverride);
2265
1282
  }
2266
1283
  else {
2267
- queryStr = (0, sql_template_tag_1.default) `SELECT ${selectExpr} AS ${(0, sql_template_tag_1.raw)(this.wrap("result"))} FROM ${(0, sql_template_tag_1.raw)(this.wrap(tableName))}`;
1284
+ await session.connect();
2268
1285
  }
2269
- const queryResult = (await transactionManager.query(queryStr));
2270
- await transactionManager.commit();
2271
- const { results } = queryResult;
2272
- if (!results || results.length === 0)
2273
- return 0;
2274
- const row = results[0];
2275
- const value = row.result ?? row["result"];
2276
- return value === null || value === undefined ? 0 : Number(value);
1286
+ await session.startTransaction();
1287
+ if (this.isMySqlFamily()) {
1288
+ await session.query("SET autocommit = 0");
1289
+ }
1290
+ const result = await fn(session);
1291
+ await session.commit();
1292
+ return result;
2277
1293
  }
2278
1294
  catch (e) {
2279
1295
  try {
2280
- await transactionManager.rollback();
1296
+ await session.rollback();
2281
1297
  }
2282
1298
  catch (rollbackError) {
2283
1299
  this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
@@ -2286,43 +1302,27 @@ class EntityManager {
2286
1302
  }
2287
1303
  finally {
2288
1304
  try {
2289
- await transactionManager.close();
1305
+ await session.close();
2290
1306
  }
2291
1307
  catch (closeError) {
2292
1308
  this.logger.error(`Failed to close transaction: ${closeError}`);
2293
1309
  }
2294
1310
  }
2295
1311
  }
2296
- async count(entity, where) {
2297
- return this.aggregate(entity, "COUNT", "*", where);
2298
- }
2299
- async findAndCount(entity, findOption = {}) {
2300
- const [entities, totalCount] = await Promise.all([
2301
- this.find(entity, findOption),
2302
- this.count(entity, findOption.where),
2303
- ]);
2304
- return [entities, totalCount];
2305
- }
2306
- async sum(entity, field, where) {
2307
- return this.aggregate(entity, "SUM", field, where);
2308
- }
2309
- async avg(entity, field, where) {
2310
- return this.aggregate(entity, "AVG", field, where);
2311
- }
2312
- async min(entity, field, where) {
2313
- return this.aggregate(entity, "MIN", field, where);
2314
- }
2315
- async max(entity, field, where) {
2316
- return this.aggregate(entity, "MAX", field, where);
1312
+ resolveSelectColumns(select) {
1313
+ if (Array.isArray(select)) {
1314
+ return select.map((col) => String(col));
1315
+ }
1316
+ const columns = [];
1317
+ for (const key in select) {
1318
+ if (select[key] === true) {
1319
+ columns.push(key);
1320
+ }
1321
+ }
1322
+ return columns;
2317
1323
  }
2318
1324
  async query(sqlQuery, params) {
2319
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
2320
- try {
2321
- await transactionHolder.connect();
2322
- await transactionHolder.startTransaction();
2323
- if (this.isMySqlFamily()) {
2324
- await transactionHolder.query("SET autocommit = 0");
2325
- }
1325
+ return this.executeInTransaction(async (session) => {
2326
1326
  let queryResult;
2327
1327
  if (typeof sqlQuery === "string") {
2328
1328
  if (params && params.length > 0) {
@@ -2332,16 +1332,15 @@ class EntityManager {
2332
1332
  values: params,
2333
1333
  strings: [sqlQuery],
2334
1334
  };
2335
- queryResult = await transactionHolder.query(parameterizedSql);
1335
+ queryResult = await session.query(parameterizedSql);
2336
1336
  }
2337
1337
  else {
2338
- queryResult = await transactionHolder.query(sqlQuery);
1338
+ queryResult = await session.query(sqlQuery);
2339
1339
  }
2340
1340
  }
2341
1341
  else {
2342
- queryResult = await transactionHolder.query(sqlQuery);
1342
+ queryResult = await session.query(sqlQuery);
2343
1343
  }
2344
- await transactionHolder.commit();
2345
1344
  if (queryResult?.results) {
2346
1345
  return queryResult.results ?? [];
2347
1346
  }
@@ -2349,24 +1348,7 @@ class EntityManager {
2349
1348
  return queryResult;
2350
1349
  }
2351
1350
  return [];
2352
- }
2353
- catch (e) {
2354
- try {
2355
- await transactionHolder.rollback();
2356
- }
2357
- catch (rollbackError) {
2358
- this.logger.error(`Failed to rollback raw query transaction: ${rollbackError}`);
2359
- }
2360
- throw e;
2361
- }
2362
- finally {
2363
- try {
2364
- await transactionHolder.close();
2365
- }
2366
- catch (closeError) {
2367
- this.logger.error(`Failed to close raw query transaction: ${closeError}`);
2368
- }
2369
- }
1351
+ });
2370
1352
  }
2371
1353
  getRepository(entity) {
2372
1354
  return BaseRepository_1.BaseRepository.of(entity, this);