@stingerloom/orm 0.2.0 → 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 (124) 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 -43
  15. package/dist/core/EntityManager.d.ts.map +1 -1
  16. package/dist/core/EntityManager.js +509 -1492
  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/generators/SchemaGenerator.d.ts.map +1 -1
  47. package/dist/core/generators/SchemaGenerator.js +4 -0
  48. package/dist/core/generators/SchemaGenerator.js.map +1 -1
  49. package/dist/core/index.d.ts +8 -0
  50. package/dist/core/index.d.ts.map +1 -1
  51. package/dist/core/index.js +8 -0
  52. package/dist/core/index.js.map +1 -1
  53. package/dist/decorators/Column.d.ts +1 -1
  54. package/dist/decorators/Column.d.ts.map +1 -1
  55. package/dist/decorators/Column.js.map +1 -1
  56. package/dist/decorators/CreateTimestamp.d.ts +3 -0
  57. package/dist/decorators/CreateTimestamp.d.ts.map +1 -0
  58. package/dist/decorators/CreateTimestamp.js +16 -0
  59. package/dist/decorators/CreateTimestamp.js.map +1 -0
  60. package/dist/decorators/Entity.d.ts.map +1 -1
  61. package/dist/decorators/Entity.js +9 -3
  62. package/dist/decorators/Entity.js.map +1 -1
  63. package/dist/decorators/UpdateTimestamp.d.ts +3 -0
  64. package/dist/decorators/UpdateTimestamp.d.ts.map +1 -0
  65. package/dist/decorators/UpdateTimestamp.js +16 -0
  66. package/dist/decorators/UpdateTimestamp.js.map +1 -0
  67. package/dist/decorators/Version.d.ts.map +1 -1
  68. package/dist/decorators/Version.js +3 -3
  69. package/dist/decorators/Version.js.map +1 -1
  70. package/dist/decorators/index.d.ts +2 -0
  71. package/dist/decorators/index.d.ts.map +1 -1
  72. package/dist/decorators/index.js +2 -0
  73. package/dist/decorators/index.js.map +1 -1
  74. package/dist/dialects/FindOption.d.ts +2 -2
  75. package/dist/dialects/FindOption.d.ts.map +1 -1
  76. package/dist/dialects/TransactionSessionManager.d.ts.map +1 -1
  77. package/dist/dialects/TransactionSessionManager.js +5 -2
  78. package/dist/dialects/TransactionSessionManager.js.map +1 -1
  79. package/dist/dialects/mysql/MySqlDataSource.d.ts.map +1 -1
  80. package/dist/dialects/mysql/MySqlDataSource.js +3 -0
  81. package/dist/dialects/mysql/MySqlDataSource.js.map +1 -1
  82. package/dist/dialects/mysql/MySqlDriver.d.ts.map +1 -1
  83. package/dist/dialects/mysql/MySqlDriver.js +11 -3
  84. package/dist/dialects/mysql/MySqlDriver.js.map +1 -1
  85. package/dist/dialects/postgres/PostgresDataSource.d.ts.map +1 -1
  86. package/dist/dialects/postgres/PostgresDataSource.js +3 -0
  87. package/dist/dialects/postgres/PostgresDataSource.js.map +1 -1
  88. package/dist/dialects/postgres/PostgresDriver.d.ts.map +1 -1
  89. package/dist/dialects/postgres/PostgresDriver.js +6 -0
  90. package/dist/dialects/postgres/PostgresDriver.js.map +1 -1
  91. package/dist/dialects/sqlite/SqliteDataSource.d.ts.map +1 -1
  92. package/dist/dialects/sqlite/SqliteDataSource.js +3 -0
  93. package/dist/dialects/sqlite/SqliteDataSource.js.map +1 -1
  94. package/dist/dialects/sqlite/SqliteDriver.d.ts.map +1 -1
  95. package/dist/dialects/sqlite/SqliteDriver.js +5 -0
  96. package/dist/dialects/sqlite/SqliteDriver.js.map +1 -1
  97. package/dist/errors/DatabaseConnectionFailedError.d.ts +2 -1
  98. package/dist/errors/DatabaseConnectionFailedError.d.ts.map +1 -1
  99. package/dist/errors/DatabaseConnectionFailedError.js +12 -2
  100. package/dist/errors/DatabaseConnectionFailedError.js.map +1 -1
  101. package/dist/errors/OptimisticLockError.d.ts +5 -0
  102. package/dist/errors/OptimisticLockError.d.ts.map +1 -0
  103. package/dist/errors/OptimisticLockError.js +14 -0
  104. package/dist/errors/OptimisticLockError.js.map +1 -0
  105. package/dist/errors/OrmErrorCode.d.ts +3 -1
  106. package/dist/errors/OrmErrorCode.d.ts.map +1 -1
  107. package/dist/errors/OrmErrorCode.js +2 -0
  108. package/dist/errors/OrmErrorCode.js.map +1 -1
  109. package/dist/errors/index.d.ts +1 -0
  110. package/dist/errors/index.d.ts.map +1 -1
  111. package/dist/errors/index.js +1 -0
  112. package/dist/errors/index.js.map +1 -1
  113. package/dist/metadata/MetadataContext.d.ts.map +1 -1
  114. package/dist/metadata/MetadataContext.js +3 -1
  115. package/dist/metadata/MetadataContext.js.map +1 -1
  116. package/dist/utils/index.d.ts +1 -0
  117. package/dist/utils/index.d.ts.map +1 -1
  118. package/dist/utils/index.js +1 -0
  119. package/dist/utils/index.js.map +1 -1
  120. package/dist/utils/validateSavepointName.d.ts +2 -0
  121. package/dist/utils/validateSavepointName.d.ts.map +1 -0
  122. package/dist/utils/validateSavepointName.js +12 -0
  123. package/dist/utils/validateSavepointName.js.map +1 -0
  124. 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,9 +155,43 @@ 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);
136
159
  }
137
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
+ }
189
+ }
190
+ return allQueriesCompleted;
191
+ }
192
+ getNameStrategy(clazz) {
193
+ return clazz.name;
194
+ }
138
195
  initQueryTracker(options) {
139
196
  const logging = options.logging;
140
197
  if (typeof logging === "object" && logging !== null) {
@@ -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,606 +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
- async runHooks(entity, item, event) {
279
- const hooks = Reflect.getMetadata(decorators_1.HOOK_TOKEN, entity);
280
- if (!hooks || hooks.length === 0)
281
- return;
282
- for (const hook of hooks) {
283
- if (hook.event !== event)
284
- continue;
285
- const method = item[hook.methodName];
286
- if (typeof method === "function") {
287
- await method.call(item);
288
- }
289
- }
290
- }
291
- createProxy(entity) {
292
- return new Proxy(entity, {
293
- set: (target, prop, value) => {
294
- target[prop] = value;
295
- this.dirtyEntities.add(target);
296
- return true;
297
- },
298
- });
299
- }
300
- async registerEntities() {
301
- const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
302
- const entities = entityScanner.makeEntities();
303
- let entity;
304
- const { synchronize } = this.client.getOptions();
305
- if (synchronize && this.isPostgres() && this.driver) {
306
- const pgDriver = this.driver;
307
- const hasSchema = await pgDriver.hasSchema();
308
- if (!hasSchema || hasSchema.length === 0) {
309
- await pgDriver.createSchema();
310
- await pgDriver.setSearchPath();
311
- }
312
- }
313
- const entityList = [];
314
- while ((entity = entities.next())) {
315
- if (entity.done) {
316
- break;
317
- }
318
- const metadata = entity.value;
319
- const TargetEntity = metadata.target;
320
- let tableName = metadata.name;
321
- if (!tableName) {
322
- tableName = this.getNameStrategy(TargetEntity);
323
- }
324
- if (!utils_1.ReflectManager.isEntity(TargetEntity)) {
325
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(tableName ?? "Unknown");
326
- }
327
- if (synchronize) {
328
- const hasTable = await this.driver?.hasTable(tableName);
329
- if (!hasTable || hasTable.length === 0) {
330
- await this.driver?.createTable(tableName, metadata.columns);
331
- }
332
- }
333
- entityList.push({ TargetEntity, tableName, metadata });
334
- }
335
- if (synchronize) {
336
- for (const { TargetEntity, tableName } of entityList) {
337
- await this.registerForeignKeys(TargetEntity, tableName);
338
- await this.registerIndex(TargetEntity, tableName);
339
- await this.registerUniqueIndexes(TargetEntity, tableName);
340
- }
341
- await this.registerManyToManyJoinTables(entityList.map((e) => e.TargetEntity));
342
- }
343
- }
344
- async registerUniqueIndexes(TargetEntity, tableName) {
345
- const uniqueIndexes = Reflect.getMetadata(UniqueIndex_1.UNIQUE_INDEX_TOKEN, TargetEntity);
346
- if (!uniqueIndexes || uniqueIndexes.length === 0)
347
- return;
348
- for (const uq of uniqueIndexes) {
349
- const indexName = uq.name ?? `uq_${tableName}_${uq.columns.join("_")}`;
350
- const indexes = (await this.driver?.getIndexes(tableName));
351
- let isExist = false;
352
- for (const idx of indexes || []) {
353
- const existingIndexName = idx["Key_name"] ?? idx["Field"] ?? idx["name"];
354
- if (existingIndexName === indexName) {
355
- isExist = true;
356
- break;
357
- }
358
- }
359
- if (!isExist) {
360
- await this.driver?.addCompositeUniqueIndex(tableName, uq.columns, indexName);
361
- }
362
- }
363
- }
364
- async registerManyToManyJoinTables(entities) {
365
- const processedTables = new Set();
366
- for (const entity of entities) {
367
- const m2mMeta = (Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
368
- []);
369
- for (const rel of m2mMeta) {
370
- if (!rel.joinTable)
371
- continue;
372
- const { name: joinTableName, joinColumn, inverseJoinColumn, } = rel.joinTable;
373
- if (processedTables.has(joinTableName))
374
- continue;
375
- processedTables.add(joinTableName);
376
- const ownerEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
377
- const ownerTable = ownerEntityMeta?.name ?? entity.name;
378
- const relatedEntity = rel.getRelatedEntity();
379
- const relatedEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, relatedEntity);
380
- const relatedTable = relatedEntityMeta?.name ?? relatedEntity.name;
381
- const hasTable = await this.driver?.hasTable(joinTableName);
382
- if (!hasTable || hasTable.length === 0) {
383
- const wJoinTable = this.wrap(joinTableName);
384
- const wJoinCol = this.wrap(joinColumn);
385
- const wInvCol = this.wrap(inverseJoinColumn);
386
- let ddl = `CREATE TABLE IF NOT EXISTS ${wJoinTable} (${wJoinCol} INT NOT NULL, ${wInvCol} INT NOT NULL, PRIMARY KEY (${wJoinCol}, ${wInvCol}))`;
387
- if (this.isMySqlFamily())
388
- ddl += " ENGINE=InnoDB";
389
- await this.driver?.executeRaw(ddl);
390
- }
391
- const ownerColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ?? []);
392
- const ownerPk = ownerColumns.find((c) => c.options?.primary)?.name;
393
- const relatedColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, relatedEntity.prototype) ?? []);
394
- const relatedPk = relatedColumns.find((c) => c.options?.primary)?.name;
395
- const ownerFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, joinColumn, ownerTable);
396
- if (ownerPk &&
397
- this.driver &&
398
- !(await this.driver.hasForeignKey(joinTableName, ownerFkName))) {
399
- 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`;
400
- await this.driver.executeRaw(ddl);
401
- }
402
- const relatedFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, inverseJoinColumn, relatedTable);
403
- if (relatedPk &&
404
- this.driver &&
405
- !(await this.driver.hasForeignKey(joinTableName, relatedFkName))) {
406
- 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`;
407
- await this.driver.executeRaw(ddl);
408
- }
409
- }
410
- }
411
- }
412
- resolveManyToOneMetadata(entity) {
413
- const manyToOneScanner = typedi_1.default.get(scanner_1.ManyToOneScanner);
414
- const allRelations = manyToOneScanner
415
- .allMetadata()
416
- .filter((rel) => rel.target === entity);
417
- if (allRelations.length > 0) {
418
- return this.resolveJoinColumnsFromColumnMeta(entity, allRelations);
419
- }
420
- const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity) ??
421
- Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity.prototype);
422
- if (reflectMetadata && reflectMetadata.length > 0) {
423
- this.logger.warn(`[resolveManyToOneMetadata] "${entity.name}" ManyToOne resolved via Reflect.getMetadata fallback.`);
424
- return this.resolveJoinColumnsFromColumnMeta(entity, reflectMetadata);
425
- }
426
- return [];
427
- }
428
- resolveJoinColumnsFromColumnMeta(entity, relations) {
429
- const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
430
- Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
431
- [];
432
- if (columnsMeta.length === 0) {
433
- return relations;
434
- }
435
- return relations.map((rel) => {
436
- if (rel.joinColumn)
437
- return rel;
438
- const fkPropertyName = `${rel.columnName}Id`;
439
- const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
440
- if (!matchingColumn)
441
- return rel;
442
- const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
443
- return {
444
- ...rel,
445
- joinColumn: resolvedJoinColumn,
446
- };
447
- });
448
- }
449
- resolveOneToManyMetadata(entity) {
450
- const oneToManyScanner = typedi_1.default.get(scanner_1.OneToManyScanner);
451
- const allRelations = oneToManyScanner
452
- .allMetadata()
453
- .filter((rel) => rel.target === entity);
454
- if (allRelations.length > 0) {
455
- return allRelations;
456
- }
457
- const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity) ??
458
- Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity.prototype);
459
- if (reflectMetadata && reflectMetadata.length > 0) {
460
- this.logger.warn(`[resolveOneToManyMetadata] "${entity.name}" OneToMany resolved via Reflect.getMetadata fallback.`);
461
- return reflectMetadata;
462
- }
463
- return [];
464
- }
465
- async loadOneToManyRelations(entity, parentResults, relations) {
466
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
467
- if (oneToManyMeta.length === 0)
468
- return;
469
- const parentMetadata = this.resolveEntityMetadata(entity);
470
- if (!parentMetadata)
471
- return;
472
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
473
- if (!pk)
474
- return;
475
- const parents = Array.isArray(parentResults)
476
- ? parentResults
477
- : [parentResults];
478
- for (const rel of oneToManyMeta) {
479
- if (!relations.includes(rel.propertyKey))
480
- continue;
481
- const RelatedEntity = rel.getRelatedEntity();
482
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
483
- if (!relatedMetadata)
484
- continue;
485
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
486
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
487
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
488
- for (const parent of parents) {
489
- const parentId = parent[pk.name];
490
- if (parentId === undefined || parentId === null)
491
- continue;
492
- const children = await this.find(RelatedEntity, {
493
- where: { [fkColumn]: parentId },
494
- });
495
- if (children === undefined) {
496
- parent[rel.propertyKey] = [];
497
- }
498
- else if (Array.isArray(children)) {
499
- parent[rel.propertyKey] = children;
500
- }
501
- else {
502
- parent[rel.propertyKey] = [children];
503
- }
504
- }
505
- }
506
- }
507
- resolveManyToManyMetadata(entity) {
508
- const manyToManyScanner = typedi_1.default.get(scanner_1.ManyToManyScanner);
509
- const allRelations = manyToManyScanner
510
- .allMetadata()
511
- .filter((rel) => rel.target === entity);
512
- if (allRelations.length > 0) {
513
- return allRelations;
514
- }
515
- const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
516
- Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity.prototype);
517
- if (reflectMetadata && reflectMetadata.length > 0) {
518
- this.logger.warn(`[resolveManyToManyMetadata] "${entity.name}" ManyToMany resolved via Reflect.getMetadata fallback.`);
519
- return reflectMetadata;
520
- }
521
- return [];
522
- }
523
- resolveOneToOneMetadata(entity) {
524
- const oneToOneScanner = typedi_1.default.get(scanner_1.OneToOneScanner);
525
- const allRelations = oneToOneScanner
526
- .allMetadata()
527
- .filter((rel) => rel.target === entity);
528
- if (allRelations.length > 0) {
529
- return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, allRelations);
530
- }
531
- const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity) ??
532
- Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity.prototype);
533
- if (reflectMetadata && reflectMetadata.length > 0) {
534
- this.logger.warn(`[resolveOneToOneMetadata] "${entity.name}" OneToOne resolved via Reflect.getMetadata fallback.`);
535
- return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, reflectMetadata);
536
- }
537
- return [];
538
- }
539
- resolveJoinColumnsFromColumnMetaForOneToOne(entity, relations) {
540
- const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
541
- Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
542
- [];
543
- if (columnsMeta.length === 0) {
544
- return relations;
545
- }
546
- return relations.map((rel) => {
547
- if (rel.joinColumn)
548
- return rel;
549
- const fkPropertyName = `${rel.propertyKey}Id`;
550
- const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
551
- if (!matchingColumn)
552
- return rel;
553
- const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
554
- return {
555
- ...rel,
556
- joinColumn: resolvedJoinColumn,
557
- };
558
- });
559
- }
560
- resolveManyToManyJoinTable(rel) {
561
- if (rel.joinTable) {
562
- return {
563
- joinTableName: rel.joinTable.name,
564
- joinColumn: rel.joinTable.joinColumn,
565
- inverseJoinColumn: rel.joinTable.inverseJoinColumn,
566
- };
567
- }
568
- if (rel.mappedBy) {
569
- const RelatedEntity = rel.getRelatedEntity();
570
- const relatedManyToMany = this.resolveManyToManyMetadata(RelatedEntity);
571
- const ownerRel = relatedManyToMany.find((r) => r.propertyKey === rel.mappedBy && r.joinTable);
572
- if (ownerRel?.joinTable) {
573
- return {
574
- joinTableName: ownerRel.joinTable.name,
575
- joinColumn: ownerRel.joinTable.inverseJoinColumn,
576
- inverseJoinColumn: ownerRel.joinTable.joinColumn,
577
- };
578
- }
579
- }
580
- return null;
581
- }
582
- async loadManyToManyRelations(entity, parentResults, relations) {
583
- const manyToManyMeta = this.resolveManyToManyMetadata(entity);
584
- if (manyToManyMeta.length === 0)
585
- return;
586
- const parentMetadata = this.resolveEntityMetadata(entity);
587
- if (!parentMetadata)
588
- return;
589
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
590
- if (!pk)
591
- return;
592
- const parents = Array.isArray(parentResults)
593
- ? parentResults
594
- : [parentResults];
595
- for (const rel of manyToManyMeta) {
596
- if (!relations.includes(rel.propertyKey))
597
- continue;
598
- const joinInfo = this.resolveManyToManyJoinTable(rel);
599
- if (!joinInfo)
600
- continue;
601
- const RelatedEntity = rel.getRelatedEntity();
602
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
603
- if (!relatedMetadata)
604
- continue;
605
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
606
- if (!relatedPk)
607
- continue;
608
- const relatedTableName = relatedMetadata.name ?? RelatedEntity.name;
609
- for (const parent of parents) {
610
- const parentId = parent[pk.name];
611
- if (parentId === undefined || parentId === null)
612
- continue;
613
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
614
- const selectCols = relatedMetadata.columns.map((col) => `${this.wrap(relatedTableName)}.${this.wrap(col.name)}`);
615
- 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))}`;
616
- 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}`;
617
- qb.select(selectCols)
618
- .from(this.wrap(relatedTableName))
619
- .innerJoin(this.wrap(joinInfo.joinTableName), this.wrap(joinInfo.joinTableName), joinCondition)
620
- .where([whereCondition]);
621
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
622
- try {
623
- await transactionHolder.connect();
624
- await transactionHolder.startTransaction();
625
- if (this.isMySqlFamily()) {
626
- await transactionHolder.query("SET autocommit = 0");
627
- }
628
- const resultQuery = qb.build();
629
- const subQueryStart = Date.now();
630
- this.beginTrackQuery();
631
- const queryResult = (await transactionHolder.query(resultQuery));
632
- this.trackQuery(relatedTableName, resultQuery.text ?? String(resultQuery), Date.now() - subQueryStart);
633
- await transactionHolder.commit();
634
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
635
- const { results } = queryResult;
636
- if (!results || results.length === 0) {
637
- parent[rel.propertyKey] = [];
638
- }
639
- else {
640
- parent[rel.propertyKey] = resultTransformer.toEntities(RelatedEntity, queryResult);
641
- }
642
- }
643
- catch (e) {
644
- try {
645
- await transactionHolder.rollback();
646
- }
647
- catch (rollbackError) {
648
- this.logger.error(`Failed to rollback ManyToMany transaction: ${rollbackError}`);
649
- }
650
- throw e;
651
- }
652
- finally {
653
- try {
654
- await transactionHolder.close();
655
- }
656
- catch (closeError) {
657
- this.logger.error(`Failed to close ManyToMany transaction: ${closeError}`);
658
- }
659
- }
660
- }
661
- }
662
- }
663
- async loadOneToOneRelations(entity, parentResults, relations) {
664
- const oneToOneMeta = this.resolveOneToOneMetadata(entity);
665
- if (oneToOneMeta.length === 0)
666
- return;
667
- const parentMetadata = this.resolveEntityMetadata(entity);
668
- if (!parentMetadata)
669
- return;
670
- const pk = parentMetadata.columns.find((column) => column.options?.primary);
671
- if (!pk)
672
- return;
673
- const parents = Array.isArray(parentResults)
674
- ? parentResults
675
- : [parentResults];
676
- for (const rel of oneToOneMeta) {
677
- if (!relations.includes(rel.propertyKey))
678
- continue;
679
- if (rel.joinColumn) {
680
- continue;
681
- }
682
- const RelatedEntity = rel.getRelatedEntity();
683
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
684
- if (!relatedMetadata)
685
- continue;
686
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
687
- if (!relatedPk)
688
- continue;
689
- for (const parent of parents) {
690
- if (rel.joinColumn) {
691
- const fkValue = parent[rel.joinColumn];
692
- if (fkValue === undefined || fkValue === null) {
693
- parent[rel.propertyKey] = null;
694
- continue;
695
- }
696
- const related = await this.findOne(RelatedEntity, {
697
- where: { [relatedPk.name]: fkValue },
698
- });
699
- parent[rel.propertyKey] = related ?? null;
700
- }
701
- else if (rel.inverseSide) {
702
- const parentId = parent[pk.name];
703
- if (parentId === undefined || parentId === null) {
704
- parent[rel.propertyKey] = null;
705
- continue;
706
- }
707
- const relatedOneToOne = this.resolveOneToOneMetadata(RelatedEntity);
708
- const ownerRel = relatedOneToOne.find((r) => r.propertyKey === rel.inverseSide && r.joinColumn);
709
- if (ownerRel?.joinColumn) {
710
- const related = await this.findOne(RelatedEntity, {
711
- where: { [ownerRel.joinColumn]: parentId },
712
- });
713
- parent[rel.propertyKey] = related ?? null;
714
- }
715
- else {
716
- parent[rel.propertyKey] = null;
717
- }
718
- }
719
- }
720
- }
721
- }
722
- async registerForeignKeys(TargetEntity, tableName) {
723
- const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
724
- const manyToOneItems = this.resolveManyToOneMetadata(TargetEntity);
725
- const isValidManyToOne = manyToOneItems && manyToOneItems.length > 0;
726
- if (isValidManyToOne) {
727
- for (const manyToOneItem of manyToOneItems) {
728
- const { joinColumn } = manyToOneItem;
729
- const mappingEntity = manyToOneItem.getMappingEntity();
730
- if (!mappingEntity) {
731
- throw new EntityNotFound_1.EntityNotFound(mappingEntity);
732
- }
733
- const mappingTableMetadata = entityScanner.scan(mappingEntity);
734
- if (!mappingTableMetadata) {
735
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(mappingEntity.name);
736
- }
737
- if (!joinColumn) {
738
- throw new InvalidQueryError_1.InvalidQueryError("JoinColumn does not exist.");
739
- }
740
- const mappingTablePrimaryKey = manyToOneItem.references
741
- ? manyToOneItem.references
742
- : mappingTableMetadata.columns.find((e) => e.options?.primary)?.name;
743
- if (!mappingTablePrimaryKey) {
744
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(mappingEntity.name);
745
- }
746
- const mappingTableName = mappingTableMetadata.name || this.getNameStrategy(mappingEntity);
747
- if (this.driver) {
748
- const columnExists = await this.driver.hasColumn(tableName, joinColumn);
749
- if (!columnExists) {
750
- const fkColumnType = this.driver.castType("int") + " NULL";
751
- await this.driver.addColumn(tableName, joinColumn, fkColumnType);
752
- }
753
- }
754
- if (this.driver) {
755
- const fkName = this.driver.generateForeignKeyName(tableName, mappingTableName, joinColumn);
756
- const fkExists = await this.driver.hasForeignKey(tableName, fkName);
757
- if (fkExists)
758
- continue;
759
- }
760
- await this.driver?.addForeignKey(tableName, joinColumn, mappingTableName, mappingTablePrimaryKey);
761
- }
762
- }
763
- const oneToOneItems = this.resolveOneToOneMetadata(TargetEntity);
764
- for (const oneToOneItem of oneToOneItems) {
765
- const { joinColumn } = oneToOneItem;
766
- if (!joinColumn)
767
- continue;
768
- const RelatedEntity = oneToOneItem.getRelatedEntity();
769
- if (!RelatedEntity) {
770
- throw new EntityNotFound_1.EntityNotFound(RelatedEntity);
771
- }
772
- const relatedMetadata = entityScanner.scan(RelatedEntity);
773
- if (!relatedMetadata) {
774
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(RelatedEntity.name);
775
- }
776
- const relatedPrimaryKey = relatedMetadata.columns.find((e) => e.options?.primary)?.name;
777
- if (!relatedPrimaryKey) {
778
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(RelatedEntity.name);
779
- }
780
- if (this.driver) {
781
- const columnExists = await this.driver.hasColumn(tableName, joinColumn);
782
- if (!columnExists) {
783
- const fkColumnType = this.driver.castType("int") + " NULL";
784
- await this.driver.addColumn(tableName, joinColumn, fkColumnType);
785
- }
786
- }
787
- const relatedTableName = relatedMetadata.name || this.getNameStrategy(RelatedEntity);
788
- if (this.driver) {
789
- const fkName = this.driver.generateForeignKeyName(tableName, relatedTableName, joinColumn);
790
- const fkExists = await this.driver.hasForeignKey(tableName, fkName);
791
- if (fkExists)
792
- continue;
793
- }
794
- await this.driver?.addForeignKey(tableName, joinColumn, relatedTableName, relatedPrimaryKey);
795
- }
796
- }
797
- async registerIndex(TargetEntity, tableName) {
798
- const indexer = Reflect.getMetadata(Indexer_1.INDEX_TOKEN, TargetEntity.prototype);
799
- if (indexer) {
800
- for (const index of indexer) {
801
- const indexName = `INDEX_${tableName}_${index.name}`;
802
- const indexes = (await this.driver?.getIndexes(tableName));
803
- let isExist = false;
804
- for (const idx of indexes || []) {
805
- const existingIndexName = idx["Key_name"] ?? idx["Field"];
806
- if (existingIndexName === indexName) {
807
- isExist = true;
808
- break;
809
- }
810
- }
811
- if (!isExist) {
812
- await this.driver?.addIndex(tableName, index.name, indexName);
813
- }
814
- }
815
- }
816
- }
817
270
  async findOne(entity, findOption) {
818
- 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);
819
275
  if (result === undefined || result === null) {
820
276
  return null;
821
277
  }
@@ -824,347 +280,16 @@ class EntityManager {
824
280
  }
825
281
  return result;
826
282
  }
827
- async explain(entity, findOption = {}) {
828
- if (!this.driver || !this.driver.supportsExplain()) {
829
- throw new InvalidQueryError_1.InvalidQueryError("EXPLAIN is not supported by the current database driver.");
830
- }
831
- const { select, orderBy, where, take } = findOption;
832
- const { limit } = findOption;
833
- const metadata = this.resolveEntityMetadata(entity);
834
- if (!metadata) {
835
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
836
- }
837
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
838
- const selectMap = [];
839
- const whereMap = [];
840
- const orderByMap = [];
841
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
842
- const eagerRelations = manyToOneRelations.filter((rel) => {
843
- const isEager = rel.option?.eager === true;
844
- const isInRelations = findOption.relations?.includes(rel.columnName);
845
- return isEager || isInRelations;
846
- });
847
- const oneToOneRelations = this.resolveOneToOneMetadata(entity);
848
- const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
849
- if (!rel.joinColumn)
850
- return false;
851
- const isEager = rel.option?.eager === true;
852
- const isInRelations = findOption.relations?.includes(rel.propertyKey);
853
- return isEager || isInRelations;
854
- });
855
- const hasEagerJoins = eagerRelations.length > 0 || eagerOneToOneRelations.length > 0;
856
- const tableName = metadata.name;
857
- if (select) {
858
- const selectedColumns = this.resolveSelectColumns(select);
859
- if (hasEagerJoins) {
860
- selectMap.push(...selectedColumns.map((col) => `${this.wrap(tableName)}.${this.wrap(col)}`));
861
- }
862
- else {
863
- selectMap.push(...selectedColumns.map((col) => this.wrap(col)));
864
- }
865
- }
866
- else {
867
- if (hasEagerJoins) {
868
- selectMap.push(...metadata.columns.map((column) => `${this.wrap(tableName)}.${this.wrap(column.name)}`));
869
- }
870
- else {
871
- selectMap.push(...metadata.columns.map((column) => this.wrap(column.name)));
872
- }
873
- }
874
- for (const key in where) {
875
- const value = where[key];
876
- if (value !== undefined && value !== null) {
877
- if (hasEagerJoins) {
878
- whereMap.push(Conditions_1.Conditions.equals(`${this.wrap(tableName)}.${this.wrap(key)}`, value));
879
- }
880
- else {
881
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
882
- }
883
- }
884
- }
885
- const deletedAtColumn = this.getDeletedAtColumn(entity);
886
- if (deletedAtColumn && !findOption.withDeleted) {
887
- if (hasEagerJoins) {
888
- whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
889
- }
890
- else {
891
- whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
892
- }
893
- }
894
- for (const key in orderBy) {
895
- const value = orderBy[key];
896
- if (value) {
897
- orderByMap.push({ column: this.wrap(key), direction: value });
898
- }
899
- }
900
- qb.select(selectMap).from(this.wrap(tableName));
901
- qb.where(whereMap).orderBy(orderByMap);
902
- if (Array.isArray(limit)) {
903
- let [offset, count] = limit;
904
- if (offset < 0)
905
- offset = 0;
906
- if (count < 0)
907
- count = 0;
908
- if (count === 0)
909
- count = 1;
910
- if (take && take > 0)
911
- count = take;
912
- if (this.isMySqlFamily())
913
- qb.setDatabaseType("mysql");
914
- qb.limit([offset, count]);
915
- }
916
- else if (limit) {
917
- qb.limit(limit);
918
- }
919
- const selectQuery = qb.build();
920
- const explainPrefix = this.driver.buildExplainSql("");
921
- const explainQuery = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(explainPrefix)}${selectQuery}`;
922
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
923
- try {
924
- const readNode = this.getReadNode(findOption.useMaster);
925
- if (readNode) {
926
- await transactionHolder.connectToNode(readNode);
927
- }
928
- else {
929
- await transactionHolder.connect();
930
- }
931
- await transactionHolder.startTransaction();
932
- if (this.isMySqlFamily()) {
933
- await transactionHolder.query("SET autocommit = 0");
934
- }
935
- const result = await transactionHolder.query(explainQuery);
936
- await transactionHolder.commit();
937
- const rawRows = result?.results ?? [];
938
- return this.parseExplainResult(rawRows);
939
- }
940
- catch (e) {
941
- try {
942
- await transactionHolder.rollback();
943
- }
944
- catch {
945
- }
946
- throw e;
947
- }
948
- finally {
949
- try {
950
- await transactionHolder.close();
951
- }
952
- catch {
953
- }
954
- }
955
- }
956
- parseExplainResult(rawRows) {
957
- if (!rawRows || rawRows.length === 0) {
958
- return {
959
- raw: [],
960
- rows: null,
961
- type: null,
962
- possibleKeys: null,
963
- key: null,
964
- cost: null,
965
- };
966
- }
967
- const firstRow = rawRows[0];
968
- if (firstRow && "QUERY PLAN" in firstRow) {
969
- return this.parsePostgresExplain(firstRow["QUERY PLAN"]);
970
- }
971
- if ("type" in firstRow || "select_type" in firstRow) {
972
- return this.parseMysqlExplain(rawRows);
973
- }
974
- if ("detail" in firstRow || "notused" in firstRow) {
975
- return this.parseSqliteExplain(rawRows);
976
- }
977
- return {
978
- raw: rawRows,
979
- rows: null,
980
- type: null,
981
- possibleKeys: null,
982
- key: null,
983
- cost: null,
984
- };
985
- }
986
- parseMysqlExplain(rawRows) {
987
- const first = rawRows[0];
988
- const rows = first.rows != null ? Number(first.rows) : null;
989
- const type = first.type != null ? String(first.type) : null;
990
- const possibleKeysRaw = first.possible_keys;
991
- const possibleKeys = possibleKeysRaw != null
992
- ? String(possibleKeysRaw)
993
- .split(",")
994
- .map((k) => k.trim())
995
- : null;
996
- const key = first.key != null ? String(first.key) : null;
997
- const cost = first.filtered != null ? Number(first.filtered) : null;
998
- return { raw: rawRows, rows, type, possibleKeys, key, cost };
999
- }
1000
- parsePostgresExplain(queryPlan) {
1001
- const rawArray = Array.isArray(queryPlan) ? queryPlan : [queryPlan];
1002
- const plan = rawArray[0]?.Plan ?? rawArray[0]?.["Plan"] ?? null;
1003
- if (!plan) {
1004
- return {
1005
- raw: rawArray,
1006
- rows: null,
1007
- type: null,
1008
- possibleKeys: null,
1009
- key: null,
1010
- cost: null,
1011
- };
1012
- }
1013
- const rows = plan["Plan Rows"] != null ? Number(plan["Plan Rows"]) : null;
1014
- const type = plan["Node Type"] != null ? String(plan["Node Type"]) : null;
1015
- const key = plan["Index Name"] != null ? String(plan["Index Name"]) : null;
1016
- const cost = plan["Total Cost"] != null ? Number(plan["Total Cost"]) : null;
1017
- return { raw: rawArray, rows, type, possibleKeys: null, key, cost };
1018
- }
1019
- parseSqliteExplain(rawRows) {
1020
- const details = rawRows.map((r) => String(r.detail ?? ""));
1021
- const firstDetail = details[0] ?? "";
1022
- let type = null;
1023
- let key = null;
1024
- if (firstDetail.startsWith("SCAN"))
1025
- type = "SCAN";
1026
- else if (firstDetail.startsWith("SEARCH"))
1027
- type = "SEARCH";
1028
- const indexMatch = firstDetail.match(/USING (?:COVERING )?INDEX (\S+)/);
1029
- if (indexMatch)
1030
- key = indexMatch[1];
1031
- return {
1032
- raw: rawRows,
1033
- rows: null,
1034
- type,
1035
- possibleKeys: null,
1036
- key,
1037
- cost: null,
1038
- };
1039
- }
1040
- async findWithCursor(entity, option = {}) {
1041
- const metadata = this.resolveEntityMetadata(entity);
1042
- if (!metadata) {
1043
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1044
- }
1045
- const pk = metadata.columns.find((column) => column.options?.primary);
1046
- const orderByColumn = option.orderBy ?? pk?.name;
1047
- if (!orderByColumn) {
1048
- throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
1049
- }
1050
- const direction = option.direction ?? "ASC";
1051
- const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
1052
- let cursorValue = null;
1053
- if (option.cursor) {
1054
- cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
1055
- if (cursorValue === null) {
1056
- throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
1057
- }
1058
- }
1059
- const where = { ...(option.where ?? {}) };
1060
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
1061
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
1062
- try {
1063
- const readNode = this.getReadNode(option.useMaster);
1064
- if (readNode) {
1065
- await transactionHolder.connectToNode(readNode);
1066
- }
1067
- else {
1068
- await transactionHolder.connect();
1069
- }
1070
- await transactionHolder.startTransaction();
1071
- if (this.isMySqlFamily()) {
1072
- await transactionHolder.query("SET autocommit = 0");
1073
- }
1074
- const tableName = metadata.name;
1075
- const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
1076
- const selectMap = metadata.columns.map((column) => this.wrap(column.name));
1077
- const whereMap = [];
1078
- for (const key in where) {
1079
- const value = where[key];
1080
- if (value !== undefined && value !== null) {
1081
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
1082
- }
1083
- }
1084
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1085
- if (deletedAtColumn) {
1086
- whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
1087
- }
1088
- if (cursorValue !== null) {
1089
- if (direction === "ASC") {
1090
- whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
1091
- }
1092
- else {
1093
- whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
1094
- }
1095
- }
1096
- qb.select(selectMap)
1097
- .from(this.wrap(tableName))
1098
- .where(whereMap)
1099
- .orderBy([{ column: this.wrap(orderByColumn), direction }]);
1100
- qb.limit(pageSize + 1);
1101
- const resultQuery = qb.build();
1102
- const queryResult = (await transactionHolder.query(resultQuery));
1103
- await transactionHolder.commit();
1104
- const { results } = queryResult;
1105
- if (!results || results.length === 0) {
1106
- return {
1107
- data: [],
1108
- hasNextPage: false,
1109
- nextCursor: null,
1110
- count: 0,
1111
- };
1112
- }
1113
- const hasNextPage = results.length > pageSize;
1114
- const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
1115
- const entities = resultTransformer.toEntities(entity, {
1116
- results: pageResults,
1117
- fields: queryResult.fields,
1118
- });
1119
- let nextCursor = null;
1120
- if (hasNextPage && pageResults.length > 0) {
1121
- const lastItem = pageResults[pageResults.length - 1];
1122
- const lastValue = lastItem[orderByColumn];
1123
- nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
1124
- }
1125
- return {
1126
- data: entities,
1127
- hasNextPage,
1128
- nextCursor,
1129
- count: entities.length,
1130
- };
1131
- }
1132
- catch (e) {
1133
- try {
1134
- await transactionHolder.rollback();
1135
- }
1136
- catch (rollbackError) {
1137
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1138
- }
1139
- throw e;
1140
- }
1141
- finally {
1142
- try {
1143
- await transactionHolder.close();
1144
- }
1145
- catch (closeError) {
1146
- this.logger.error(`Failed to close transaction: ${closeError}`);
1147
- }
1148
- }
1149
- }
1150
283
  async find(entity, findOption = {}) {
284
+ return this.findInternal(entity, findOption);
285
+ }
286
+ async findInternal(entity, findOption = {}, existingSession) {
1151
287
  const { select, orderBy, where, take, groupBy, having } = findOption;
1152
288
  const { limit } = findOption;
1153
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
1154
- const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
1155
- try {
1156
- const readNode = this.getReadNode(findOption.useMaster);
1157
- if (readNode) {
1158
- await transactionHolder.connectToNode(readNode);
1159
- }
1160
- else {
1161
- await transactionHolder.connect();
1162
- }
1163
- await transactionHolder.startTransaction();
1164
- if (this.isMySqlFamily()) {
1165
- await transactionHolder.query("SET autocommit = 0");
1166
- }
1167
- 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);
1168
293
  if (!metadata) {
1169
294
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1170
295
  }
@@ -1172,13 +297,13 @@ class EntityManager {
1172
297
  const selectMap = [];
1173
298
  const whereMap = [];
1174
299
  const orderByMap = [];
1175
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
300
+ const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1176
301
  const eagerRelations = manyToOneRelations.filter((rel) => {
1177
302
  const isEager = rel.option?.eager === true;
1178
303
  const isInRelations = findOption.relations?.includes(rel.columnName);
1179
304
  return isEager || isInRelations;
1180
305
  });
1181
- const oneToOneRelations = this.resolveOneToOneMetadata(entity);
306
+ const oneToOneRelations = this.resolver.resolveOneToOneMetadata(entity);
1182
307
  const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
1183
308
  if (!rel.joinColumn)
1184
309
  return false;
@@ -1207,7 +332,7 @@ class EntityManager {
1207
332
  }
1208
333
  for (const rel of eagerRelations) {
1209
334
  const RelatedEntity = rel.getMappingEntity();
1210
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
335
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1211
336
  if (!relatedMetadata)
1212
337
  continue;
1213
338
  const relatedName = relatedMetadata.name || RelatedEntity.name;
@@ -1218,7 +343,7 @@ class EntityManager {
1218
343
  }
1219
344
  for (const rel of eagerOneToOneRelations) {
1220
345
  const RelatedEntity = rel.getRelatedEntity();
1221
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
346
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1222
347
  if (!relatedMetadata)
1223
348
  continue;
1224
349
  const relatedName = relatedMetadata.name || RelatedEntity.name;
@@ -1238,7 +363,7 @@ class EntityManager {
1238
363
  }
1239
364
  }
1240
365
  }
1241
- const deletedAtColumn = this.getDeletedAtColumn(entity);
366
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
1242
367
  if (deletedAtColumn && !findOption.withDeleted) {
1243
368
  if (hasEagerJoins) {
1244
369
  whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
@@ -1256,7 +381,7 @@ class EntityManager {
1256
381
  qb.select(selectMap).from(this.wrap(tableName));
1257
382
  for (const rel of eagerRelations) {
1258
383
  const RelatedEntity = rel.getMappingEntity();
1259
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
384
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1260
385
  if (!relatedMetadata)
1261
386
  continue;
1262
387
  const relatedTableName = relatedMetadata.name || RelatedEntity.name;
@@ -1269,7 +394,7 @@ class EntityManager {
1269
394
  }
1270
395
  for (const rel of eagerOneToOneRelations) {
1271
396
  const RelatedEntity = rel.getRelatedEntity();
1272
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
397
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1273
398
  if (!relatedMetadata)
1274
399
  continue;
1275
400
  const relatedTableName = relatedMetadata.name || RelatedEntity.name;
@@ -1293,21 +418,16 @@ class EntityManager {
1293
418
  qb.orderBy(orderByMap);
1294
419
  if (Array.isArray(limit)) {
1295
420
  let [offset, count] = limit;
1296
- if (offset < 0) {
421
+ if (offset < 0)
1297
422
  offset = 0;
1298
- }
1299
- if (count < 0) {
423
+ if (count < 0)
1300
424
  count = 0;
1301
- }
1302
- if (count === 0) {
425
+ if (count === 0)
1303
426
  count = 1;
1304
- }
1305
- if (take && take > 0) {
427
+ if (take && take > 0)
1306
428
  count = take;
1307
- }
1308
- if (this.isMySqlFamily()) {
429
+ if (this.isMySqlFamily())
1309
430
  qb.setDatabaseType("mysql");
1310
- }
1311
431
  qb.limit([offset, count]);
1312
432
  }
1313
433
  else {
@@ -1319,13 +439,12 @@ class EntityManager {
1319
439
  const effectiveTimeout = findOption.timeout ?? this.defaultQueryTimeout;
1320
440
  if (effectiveTimeout && effectiveTimeout > 0 && this.driver) {
1321
441
  const timeoutSql = this.driver.setQueryTimeout(effectiveTimeout);
1322
- await transactionHolder.query(timeoutSql);
442
+ await session.query(timeoutSql);
1323
443
  }
1324
444
  const queryStartTime = Date.now();
1325
445
  this.beginTrackQuery();
1326
- const queryResult = (await transactionHolder.query(resultQuery));
446
+ const queryResult = (await session.query(resultQuery));
1327
447
  this.trackQuery(entity.name, resultQuery.text ?? String(resultQuery), Date.now() - queryStartTime);
1328
- await transactionHolder.commit();
1329
448
  const { results } = queryResult;
1330
449
  if (!results || results.length === 0) {
1331
450
  return undefined;
@@ -1344,9 +463,9 @@ class EntityManager {
1344
463
  if (findOption.relations &&
1345
464
  findOption.relations.length > 0 &&
1346
465
  entityResult) {
1347
- await this.loadOneToManyRelations(entity, entityResult, findOption.relations);
1348
- await this.loadManyToManyRelations(entity, entityResult, findOption.relations);
1349
- 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);
1350
469
  }
1351
470
  const lazyRelations = manyToOneRelations.filter((rel) => {
1352
471
  return rel.option?.lazy === true && rel.option?.eager !== true;
@@ -1362,7 +481,7 @@ class EntityManager {
1362
481
  const fkValue = item[joinColumn];
1363
482
  if (fkValue === undefined || fkValue === null)
1364
483
  continue;
1365
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
484
+ const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
1366
485
  if (!relatedMetadata)
1367
486
  continue;
1368
487
  const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
@@ -1378,69 +497,111 @@ class EntityManager {
1378
497
  }
1379
498
  }
1380
499
  }
1381
- return entityResult;
1382
- }
1383
- catch (e) {
1384
- try {
1385
- await transactionHolder.rollback();
1386
- }
1387
- catch (rollbackError) {
1388
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
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
+ }
524
+ }
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
+ }
538
+ }
539
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
540
+ if (deletedAtColumn) {
541
+ whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
1389
542
  }
1390
- throw e;
1391
- }
1392
- finally {
1393
- try {
1394
- 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
+ }
1395
550
  }
1396
- catch (closeError) {
1397
- 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
+ };
1398
566
  }
1399
- }
1400
- }
1401
- resolveSelectColumns(select) {
1402
- if (Array.isArray(select)) {
1403
- return select.map((col) => String(col));
1404
- }
1405
- const columns = [];
1406
- for (const key in select) {
1407
- if (select[key] === true) {
1408
- 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);
1409
578
  }
1410
- }
1411
- return columns;
1412
- }
1413
- wrap(columnName) {
1414
- if (this.driver && "wrap" in this.driver) {
1415
- return this.driver.wrap(columnName);
1416
- }
1417
- if (this.isPostgres()) {
1418
- return `"${columnName.replace(/"/g, '""')}"`;
1419
- }
1420
- return `\`${columnName.replace(/`/g, "``")}\``;
1421
- }
1422
- isMySqlFamily() {
1423
- const t = this.dbType ?? this.client.type;
1424
- return ["mysql", "mariadb"].includes(t);
579
+ return {
580
+ data: entities,
581
+ hasNextPage,
582
+ nextCursor,
583
+ count: entities.length,
584
+ };
585
+ }, undefined, readNode);
1425
586
  }
1426
- isPostgres() {
1427
- const t = this.dbType ?? this.client.type;
1428
- 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
+ });
1429
593
  }
1430
594
  async save(entity, item) {
1431
- 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);
1432
599
  if (!metadata) {
1433
600
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1434
601
  }
1435
602
  EntityValidator_1.EntityValidator.validate(entity, item);
1436
- await this.cascadeSaveManyToOne(entity, item);
1437
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1438
- try {
1439
- await transactionManager.connect();
1440
- await transactionManager.startTransaction();
1441
- if (this.isMySqlFamily()) {
1442
- await transactionManager.query("SET autocommit = 0");
1443
- }
603
+ await this.cascadeHandler.cascadeSaveManyToOne(entity, item);
604
+ return this.executeInTransaction(async (session) => {
1444
605
  const pkColumns = metadata.columns.filter((column) => column.options?.primary);
1445
606
  const pk = pkColumns[0];
1446
607
  const hasAutoIncrementPk = pkColumns.some((col) => col.options?.autoIncrement);
@@ -1466,7 +627,7 @@ class EntityManager {
1466
627
  return where;
1467
628
  };
1468
629
  if (isInsert) {
1469
- await this.runHooks(entity, item, "beforeInsert");
630
+ await this.cascadeHandler.runHooks(entity, item, "beforeInsert");
1470
631
  await this.eventEmitter.emit("beforeInsert", { entity, data: item });
1471
632
  await this.notifySubscribers(entity, "beforeInsert", {
1472
633
  entity: item,
@@ -1486,7 +647,29 @@ class EntityManager {
1486
647
  const values = insertableColumns.map((column) => {
1487
648
  return item[column.name];
1488
649
  });
1489
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
650
+ const now = new Date().toISOString();
651
+ const createTsCol = this.resolver.getCreateTimestampColumn(entity);
652
+ if (createTsCol) {
653
+ const idx = insertableColumns.findIndex((col) => col.name === createTsCol);
654
+ if (idx >= 0) {
655
+ values[idx] = item[createTsCol]?.toISOString?.() ?? now;
656
+ }
657
+ }
658
+ const updateTsCol = this.resolver.getUpdateTimestampColumn(entity);
659
+ if (updateTsCol) {
660
+ const idx = insertableColumns.findIndex((col) => col.name === updateTsCol);
661
+ if (idx >= 0) {
662
+ values[idx] = item[updateTsCol]?.toISOString?.() ?? now;
663
+ }
664
+ }
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);
1490
673
  for (const rel of manyToOneRelations) {
1491
674
  if (!rel.joinColumn)
1492
675
  continue;
@@ -1499,7 +682,7 @@ class EntityManager {
1499
682
  }
1500
683
  else if (relatedValue && typeof relatedValue === "object") {
1501
684
  const RelatedEntity = rel.getMappingEntity();
1502
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
685
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1503
686
  if (relatedMeta) {
1504
687
  const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
1505
688
  if (relatedPk) {
@@ -1534,21 +717,20 @@ class EntityManager {
1534
717
  `;
1535
718
  const saveQueryStart = Date.now();
1536
719
  this.beginTrackQuery();
1537
- const queryResult = (await transactionManager.query(insertSql));
720
+ const queryResult = (await session.query(insertSql));
1538
721
  this.trackQuery(entity.name, insertSql.text ?? String(insertSql), Date.now() - saveQueryStart);
1539
- await transactionManager.commit();
1540
722
  if (this.isMySqlFamily()) {
1541
723
  const findWhere = hasAutoIncrementPk
1542
724
  ? { [pk.name]: queryResult?.results?.insertId }
1543
725
  : buildPkFindWhere();
1544
- const result = await this.findOne(entity, {
726
+ const result = await this.findOneInternal(entity, {
1545
727
  where: findWhere,
1546
- });
728
+ }, session);
1547
729
  const cascadeId = hasAutoIncrementPk
1548
730
  ? queryResult?.results?.insertId
1549
731
  : primaryKeyValue;
1550
- await this.cascadeSaveOneToMany(entity, item, cascadeId);
1551
- await this.runHooks(entity, item, "afterInsert");
732
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
733
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1552
734
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1553
735
  await this.notifySubscribers(entity, "afterInsert", {
1554
736
  entity: item,
@@ -1559,12 +741,12 @@ class EntityManager {
1559
741
  if (isPostgres && queryResult?.results?.length > 0) {
1560
742
  const returnedRow = queryResult.results[0];
1561
743
  const findWhere = buildPkFindWhere(returnedRow);
1562
- const result = await this.findOne(entity, {
744
+ const result = await this.findOneInternal(entity, {
1563
745
  where: findWhere,
1564
- });
746
+ }, session);
1565
747
  const cascadeId = returnedRow[pk.name];
1566
- await this.cascadeSaveOneToMany(entity, item, cascadeId);
1567
- await this.runHooks(entity, item, "afterInsert");
748
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
749
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1568
750
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1569
751
  await this.notifySubscribers(entity, "afterInsert", {
1570
752
  entity: item,
@@ -1572,7 +754,7 @@ class EntityManager {
1572
754
  });
1573
755
  return result;
1574
756
  }
1575
- await this.runHooks(entity, item, "afterInsert");
757
+ await this.cascadeHandler.runHooks(entity, item, "afterInsert");
1576
758
  await this.eventEmitter.emit("afterInsert", { entity, data: item });
1577
759
  await this.notifySubscribers(entity, "afterInsert", {
1578
760
  entity: item,
@@ -1580,23 +762,38 @@ class EntityManager {
1580
762
  });
1581
763
  return queryResult;
1582
764
  }
1583
- await this.runHooks(entity, item, "beforeUpdate");
765
+ await this.cascadeHandler.runHooks(entity, item, "beforeUpdate");
1584
766
  await this.eventEmitter.emit("beforeUpdate", { entity, data: item });
1585
767
  await this.notifySubscribers(entity, "beforeUpdate", {
1586
768
  entity: item,
1587
769
  manager: this,
1588
770
  });
771
+ const versionColName = this.resolver.getVersionColumn(entity);
1589
772
  const pkColumnNames = new Set(pkColumns.map((col) => col.name));
1590
773
  const updatableColumns = metadata.columns.filter((column) => {
1591
774
  if (pkColumnNames.has(column.name))
1592
775
  return false;
776
+ if (versionColName && column.name === versionColName)
777
+ return false;
1593
778
  return item[column.name] !== undefined;
1594
779
  });
1595
780
  const updateMap = updatableColumns.map((column) => {
1596
781
  return (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(column.name))} = ${item[column.name]}`;
1597
782
  });
783
+ const updateTsColName = this.resolver.getUpdateTimestampColumn(entity);
784
+ if (updateTsColName) {
785
+ const existingIdx = updatableColumns.findIndex((col) => col.name === updateTsColName);
786
+ const updateNow = new Date().toISOString();
787
+ if (existingIdx >= 0) {
788
+ updateMap[existingIdx] =
789
+ (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(updateTsColName))} = ${updateNow}`;
790
+ }
791
+ else {
792
+ updateMap.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(updateTsColName))} = ${updateNow}`);
793
+ }
794
+ }
1598
795
  const updatedColumnNames = new Set(updatableColumns.map((col) => col.name));
1599
- const updateManyToOneRelations = this.resolveManyToOneMetadata(entity);
796
+ const updateManyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1600
797
  for (const rel of updateManyToOneRelations) {
1601
798
  if (!rel.joinColumn)
1602
799
  continue;
@@ -1617,7 +814,7 @@ class EntityManager {
1617
814
  }
1618
815
  else if (typeof relatedValue === "object") {
1619
816
  const RelatedEntity = rel.getMappingEntity();
1620
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
817
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1621
818
  if (relatedMeta) {
1622
819
  const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
1623
820
  if (relatedPk) {
@@ -1638,6 +835,15 @@ class EntityManager {
1638
835
  }
1639
836
  }
1640
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
+ }
1641
847
  if (updateMap.length > 0) {
1642
848
  const updateSql = (0, sql_template_tag_1.default) `
1643
849
  UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))}
@@ -1646,117 +852,57 @@ class EntityManager {
1646
852
  `;
1647
853
  const updateStart = Date.now();
1648
854
  this.beginTrackQuery();
1649
- await transactionManager.query(updateSql);
855
+ const updateResult = (await session.query(updateSql));
1650
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
+ }
1651
869
  }
1652
- await transactionManager.commit();
1653
- await this.cascadeSaveOneToMany(entity, item, primaryKeyValue);
1654
- await this.runHooks(entity, item, "afterUpdate");
870
+ await this.cascadeHandler.cascadeSaveOneToMany(entity, item, primaryKeyValue);
871
+ await this.cascadeHandler.runHooks(entity, item, "afterUpdate");
1655
872
  await this.eventEmitter.emit("afterUpdate", { entity, data: item });
1656
873
  await this.notifySubscribers(entity, "afterUpdate", {
1657
874
  entity: item,
1658
875
  manager: this,
1659
876
  });
1660
- const result = await this.findOne(entity, {
877
+ const result = await this.findOneInternal(entity, {
1661
878
  where: buildPkFindWhere(),
1662
- });
879
+ }, session);
1663
880
  return result;
1664
- }
1665
- catch (e) {
1666
- try {
1667
- await transactionManager.rollback();
1668
- }
1669
- catch (rollbackError) {
1670
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1671
- }
1672
- throw e;
1673
- }
1674
- finally {
1675
- try {
1676
- await transactionManager.close();
1677
- }
1678
- catch (closeError) {
1679
- this.logger.error(`Failed to close transaction: ${closeError}`);
1680
- }
1681
- }
1682
- }
1683
- async deleteMany(entity, ids) {
1684
- if (ids.length === 0) {
1685
- return { affected: 0 };
1686
- }
1687
- const metadata = this.resolveEntityMetadata(entity);
1688
- if (!metadata) {
1689
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1690
- }
1691
- const pk = metadata.columns.find((column) => column.options?.primary);
1692
- if (!pk) {
1693
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1694
- }
1695
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1696
- try {
1697
- await transactionManager.connect();
1698
- await transactionManager.startTransaction();
1699
- if (this.isMySqlFamily()) {
1700
- await transactionManager.query("SET autocommit = 0");
1701
- }
1702
- const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
1703
- 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})`;
1704
- const queryResult = (await transactionManager.query(deleteQuery));
1705
- await transactionManager.commit();
1706
- let affected = 0;
1707
- if (this.isMySqlFamily()) {
1708
- affected = queryResult?.results?.affectedRows ?? 0;
1709
- }
1710
- else {
1711
- affected = queryResult?.rowCount ?? 0;
1712
- }
1713
- return { affected };
1714
- }
1715
- catch (e) {
1716
- try {
1717
- await transactionManager.rollback();
1718
- }
1719
- catch (rollbackError) {
1720
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1721
- }
1722
- throw e;
1723
- }
1724
- finally {
1725
- try {
1726
- await transactionManager.close();
1727
- }
1728
- catch (closeError) {
1729
- this.logger.error(`Failed to close transaction: ${closeError}`);
1730
- }
1731
- }
881
+ }, existingSession);
1732
882
  }
1733
883
  async saveMany(entity, items) {
1734
884
  if (items.length === 0) {
1735
885
  return [];
1736
886
  }
1737
- const results = [];
1738
- for (const item of items) {
1739
- const saved = await this.save(entity, item);
1740
- results.push(saved);
1741
- }
1742
- 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
+ });
1743
895
  }
1744
896
  async insertMany(entity, items) {
1745
897
  if (items.length === 0) {
1746
898
  return { affected: 0 };
1747
899
  }
1748
- const metadata = this.resolveEntityMetadata(entity);
900
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1749
901
  if (!metadata) {
1750
902
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1751
903
  }
1752
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1753
- try {
1754
- await transactionManager.connect();
1755
- await transactionManager.startTransaction();
1756
- if (this.isMySqlFamily()) {
1757
- await transactionManager.query("SET autocommit = 0");
1758
- }
1759
- const deletedAtColumn = this.getDeletedAtColumn(entity);
904
+ return this.executeInTransaction(async (session) => {
905
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
1760
906
  const timestampTypes = new Set(["datetime", "timestamp", "date"]);
1761
907
  const timestampColumns = metadata.columns.filter((col) => col.options?.type &&
1762
908
  timestampTypes.has(col.options.type) &&
@@ -1771,6 +917,14 @@ class EntityManager {
1771
917
  }
1772
918
  }
1773
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
+ }
1774
928
  const insertableColumns = metadata.columns.filter((column) => {
1775
929
  const isAutoIncrement = column.options?.autoIncrement;
1776
930
  if (!isAutoIncrement)
@@ -1779,7 +933,7 @@ class EntityManager {
1779
933
  item[column.name] !== undefined);
1780
934
  });
1781
935
  const columns = insertableColumns.map((column) => (0, sql_template_tag_1.raw)(this.wrap(column.name)));
1782
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
936
+ const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
1783
937
  const fkColumns = [];
1784
938
  for (const rel of manyToOneRelations) {
1785
939
  if (!rel.joinColumn)
@@ -1802,7 +956,7 @@ class EntityManager {
1802
956
  if (relatedValue != null) {
1803
957
  if (typeof relatedValue === "object") {
1804
958
  const RelatedEntity = fk.relMeta.getMappingEntity();
1805
- const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
959
+ const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
1806
960
  const relatedPk = relatedMeta?.columns.find((col) => col.options?.primary);
1807
961
  rowValues.push(relatedPk ? relatedValue[relatedPk.name] ?? null : null);
1808
962
  }
@@ -1817,143 +971,34 @@ class EntityManager {
1817
971
  rowValues.push(null);
1818
972
  }
1819
973
  }
1820
- return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
1821
- });
1822
- 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, ", ")}`;
1823
- const queryResult = (await transactionManager.query(queryStr));
1824
- await transactionManager.commit();
1825
- let affected = items.length;
1826
- if (this.isMySqlFamily()) {
1827
- affected = queryResult?.results?.affectedRows ?? items.length;
1828
- }
1829
- else if (queryResult?.rowCount !== undefined) {
1830
- affected = queryResult.rowCount;
1831
- }
1832
- return { affected };
1833
- }
1834
- catch (e) {
1835
- try {
1836
- await transactionManager.rollback();
1837
- }
1838
- catch (rollbackError) {
1839
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1840
- }
1841
- throw e;
1842
- }
1843
- finally {
1844
- try {
1845
- await transactionManager.close();
1846
- }
1847
- catch (closeError) {
1848
- this.logger.error(`Failed to close transaction: ${closeError}`);
1849
- }
1850
- }
1851
- }
1852
- async upsert(entity, data, conflictColumns) {
1853
- const metadata = this.resolveEntityMetadata(entity);
1854
- if (!metadata) {
1855
- throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1856
- }
1857
- if (!this.driver) {
1858
- throw new Error("Driver is not initialized.");
1859
- }
1860
- const pkColumns = metadata.columns
1861
- .filter((col) => col.options?.primary)
1862
- .map((col) => col.name);
1863
- const resolvedConflictColumns = conflictColumns ?? pkColumns;
1864
- if (resolvedConflictColumns.length === 0) {
1865
- throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
1866
- }
1867
- const insertableColumns = metadata.columns.filter((col) => {
1868
- const value = data[col.name];
1869
- if (col.options?.autoIncrement &&
1870
- (value === null || value === undefined)) {
1871
- return false;
1872
- }
1873
- return value !== undefined;
1874
- });
1875
- if (insertableColumns.length === 0) {
1876
- return;
1877
- }
1878
- const conflictSet = new Set(resolvedConflictColumns);
1879
- const updateColumnNames = insertableColumns
1880
- .map((col) => col.name)
1881
- .filter((name) => !conflictSet.has(name));
1882
- const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
1883
- const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
1884
- const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
1885
- const tableName = this.wrap(metadata.name);
1886
- if (wrappedUpdate.length === 0) {
1887
- return;
1888
- }
1889
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1890
- try {
1891
- await transactionManager.connect();
1892
- await transactionManager.startTransaction();
1893
- if (this.isMySqlFamily()) {
1894
- await transactionManager.query("SET autocommit = 0");
1895
- }
1896
- const columnValues = insertableColumns.map((col) => data[col.name]);
1897
- const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
1898
- await transactionManager.query(upsertSql);
1899
- await transactionManager.commit();
1900
- }
1901
- catch (e) {
1902
- try {
1903
- await transactionManager.rollback();
1904
- }
1905
- catch (rollbackError) {
1906
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1907
- }
1908
- throw e;
1909
- }
1910
- finally {
1911
- try {
1912
- 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;
1913
981
  }
1914
- catch (closeError) {
1915
- this.logger.error(`Failed to close transaction: ${closeError}`);
982
+ else if (queryResult?.rowCount !== undefined) {
983
+ affected = queryResult.rowCount;
1916
984
  }
1917
- }
1918
- }
1919
- buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
1920
- const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1921
- const valueList = (0, sql_template_tag_1.join)(values, ", ");
1922
- if (this.isMySqlFamily()) {
1923
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
1924
- return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
1925
- }
1926
- const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
1927
- if (this.isPostgres()) {
1928
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
1929
- 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}`;
1930
- }
1931
- if ((this.dbType ?? this.client.type) === "sqlite") {
1932
- const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
1933
- 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}`;
1934
- }
1935
- throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
985
+ return { affected };
986
+ });
1936
987
  }
1937
988
  async delete(entity, criteria) {
1938
- const metadata = this.resolveEntityMetadata(entity);
989
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1939
990
  if (!metadata) {
1940
991
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1941
992
  }
1942
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1943
- try {
1944
- await transactionManager.connect();
1945
- await transactionManager.startTransaction();
1946
- if (this.isMySqlFamily()) {
1947
- await transactionManager.query("SET autocommit = 0");
1948
- }
1949
- await this.runHooks(entity, criteria, "beforeDelete");
993
+ return this.executeInTransaction(async (session) => {
994
+ await this.cascadeHandler.runHooks(entity, criteria, "beforeDelete");
1950
995
  await this.eventEmitter.emit("beforeDelete", { entity, data: criteria });
1951
996
  await this.notifySubscribers(entity, "beforeDelete", {
1952
997
  entityClass: entity,
1953
998
  criteria,
1954
999
  manager: this,
1955
1000
  });
1956
- await this.cascadeDeleteOneToMany(entity, criteria);
1001
+ await this.cascadeHandler.cascadeDeleteOneToMany(entity, criteria);
1957
1002
  const whereMap = [];
1958
1003
  for (const key in criteria) {
1959
1004
  const value = criteria[key];
@@ -1968,9 +1013,8 @@ class EntityManager {
1968
1013
  const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${whereSql}`;
1969
1014
  const deleteStart = Date.now();
1970
1015
  this.beginTrackQuery();
1971
- const queryResult = (await transactionManager.query(deleteQuery));
1016
+ const queryResult = (await session.query(deleteQuery));
1972
1017
  this.trackQuery(entity.name, deleteQuery.text ?? String(deleteQuery), Date.now() - deleteStart);
1973
- await transactionManager.commit();
1974
1018
  let affected = 0;
1975
1019
  if (this.isMySqlFamily()) {
1976
1020
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -1978,7 +1022,7 @@ class EntityManager {
1978
1022
  else {
1979
1023
  affected = queryResult?.rowCount ?? 0;
1980
1024
  }
1981
- await this.runHooks(entity, criteria, "afterDelete");
1025
+ await this.cascadeHandler.runHooks(entity, criteria, "afterDelete");
1982
1026
  await this.eventEmitter.emit("afterDelete", { entity, data: criteria });
1983
1027
  await this.notifySubscribers(entity, "afterDelete", {
1984
1028
  entityClass: entity,
@@ -1986,27 +1030,36 @@ class EntityManager {
1986
1030
  manager: this,
1987
1031
  });
1988
1032
  return { affected };
1033
+ });
1034
+ }
1035
+ async deleteMany(entity, ids) {
1036
+ if (ids.length === 0) {
1037
+ return { affected: 0 };
1989
1038
  }
1990
- catch (e) {
1991
- try {
1992
- await transactionManager.rollback();
1993
- }
1994
- catch (rollbackError) {
1995
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
1996
- }
1997
- throw e;
1039
+ const metadata = this.resolver.resolveEntityMetadata(entity);
1040
+ if (!metadata) {
1041
+ throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
1998
1042
  }
1999
- finally {
2000
- try {
2001
- 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;
2002
1054
  }
2003
- catch (closeError) {
2004
- this.logger.error(`Failed to close transaction: ${closeError}`);
1055
+ else {
1056
+ affected = queryResult?.rowCount ?? 0;
2005
1057
  }
2006
- }
1058
+ return { affected };
1059
+ });
2007
1060
  }
2008
1061
  async clear(entity) {
2009
- const metadata = this.resolveEntityMetadata(entity);
1062
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2010
1063
  if (!metadata) {
2011
1064
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2012
1065
  }
@@ -2016,21 +1069,15 @@ class EntityManager {
2016
1069
  await this.driver.clear(metadata.name);
2017
1070
  }
2018
1071
  async softDelete(entity, criteria) {
2019
- const metadata = this.resolveEntityMetadata(entity);
1072
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2020
1073
  if (!metadata) {
2021
1074
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2022
1075
  }
2023
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1076
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
2024
1077
  if (!deletedAtColumn) {
2025
1078
  throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Use delete() instead.`);
2026
1079
  }
2027
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
2028
- try {
2029
- await transactionManager.connect();
2030
- await transactionManager.startTransaction();
2031
- if (this.isMySqlFamily()) {
2032
- await transactionManager.query("SET autocommit = 0");
2033
- }
1080
+ return this.executeInTransaction(async (session) => {
2034
1081
  const whereMap = [];
2035
1082
  for (const key in criteria) {
2036
1083
  const value = criteria[key];
@@ -2044,8 +1091,7 @@ class EntityManager {
2044
1091
  const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2045
1092
  const nowExpr = this.isPostgres() ? (0, sql_template_tag_1.raw)("NOW()") : (0, sql_template_tag_1.raw)("NOW()");
2046
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}`;
2047
- const queryResult = (await transactionManager.query(updateQuery));
2048
- await transactionManager.commit();
1094
+ const queryResult = (await session.query(updateQuery));
2049
1095
  let affected = 0;
2050
1096
  if (this.isMySqlFamily()) {
2051
1097
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -2054,41 +1100,18 @@ class EntityManager {
2054
1100
  affected = queryResult?.rowCount ?? 0;
2055
1101
  }
2056
1102
  return { affected };
2057
- }
2058
- catch (e) {
2059
- try {
2060
- await transactionManager.rollback();
2061
- }
2062
- catch (rollbackError) {
2063
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
2064
- }
2065
- throw e;
2066
- }
2067
- finally {
2068
- try {
2069
- await transactionManager.close();
2070
- }
2071
- catch (closeError) {
2072
- this.logger.error(`Failed to close transaction: ${closeError}`);
2073
- }
2074
- }
1103
+ });
2075
1104
  }
2076
1105
  async restore(entity, criteria) {
2077
- const metadata = this.resolveEntityMetadata(entity);
1106
+ const metadata = this.resolver.resolveEntityMetadata(entity);
2078
1107
  if (!metadata) {
2079
1108
  throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
2080
1109
  }
2081
- const deletedAtColumn = this.getDeletedAtColumn(entity);
1110
+ const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
2082
1111
  if (!deletedAtColumn) {
2083
1112
  throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Cannot restore.`);
2084
1113
  }
2085
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
2086
- try {
2087
- await transactionManager.connect();
2088
- await transactionManager.startTransaction();
2089
- if (this.isMySqlFamily()) {
2090
- await transactionManager.query("SET autocommit = 0");
2091
- }
1114
+ return this.executeInTransaction(async (session) => {
2092
1115
  const whereMap = [];
2093
1116
  for (const key in criteria) {
2094
1117
  const value = criteria[key];
@@ -2101,8 +1124,7 @@ class EntityManager {
2101
1124
  }
2102
1125
  const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2103
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}`;
2104
- const queryResult = (await transactionManager.query(restoreQuery));
2105
- await transactionManager.commit();
1127
+ const queryResult = (await session.query(restoreQuery));
2106
1128
  let affected = 0;
2107
1129
  if (this.isMySqlFamily()) {
2108
1130
  affected = queryResult?.results?.affectedRows ?? 0;
@@ -2111,138 +1133,167 @@ class EntityManager {
2111
1133
  affected = queryResult?.rowCount ?? 0;
2112
1134
  }
2113
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);
2114
1142
  }
2115
- catch (e) {
2116
- try {
2117
- await transactionManager.rollback();
2118
- }
2119
- catch (rollbackError) {
2120
- this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
2121
- }
2122
- throw e;
1143
+ if (!this.driver) {
1144
+ throw new Error("Driver is not initialized.");
2123
1145
  }
2124
- finally {
2125
- try {
2126
- await transactionManager.close();
2127
- }
2128
- catch (closeError) {
2129
- 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;
2130
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;
2131
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
+ });
2132
1180
  }
2133
- async cascadeSaveOneToMany(entity, item, savedParentId) {
2134
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
2135
- for (const rel of oneToManyMeta) {
2136
- const children = item[rel.propertyKey];
2137
- if (!children || !Array.isArray(children) || children.length === 0)
2138
- continue;
2139
- const RelatedEntity = rel.getRelatedEntity();
2140
- if (!(0, CascadeType_1.hasCascade)(rel.cascade, "insert") &&
2141
- !(0, CascadeType_1.hasCascade)(rel.cascade, "update"))
2142
- continue;
2143
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
2144
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
2145
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
2146
- for (const child of children) {
2147
- child[fkColumn] = savedParentId;
2148
- await this.save(RelatedEntity, child);
2149
- }
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}`;
2150
1196
  }
1197
+ throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
2151
1198
  }
2152
- async cascadeSaveManyToOne(entity, item) {
2153
- const manyToOneRelations = this.resolveManyToOneMetadata(entity);
2154
- for (const rel of manyToOneRelations) {
2155
- const relatedValue = item[rel.columnName];
2156
- if (!relatedValue || typeof relatedValue !== "object")
2157
- continue;
2158
- if (!(0, CascadeType_1.hasCascade)(rel.option?.cascade, "insert") &&
2159
- !(0, CascadeType_1.hasCascade)(rel.option?.cascade, "update"))
2160
- continue;
2161
- const RelatedEntity = rel.getMappingEntity();
2162
- const saved = await this.save(RelatedEntity, relatedValue);
2163
- const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
2164
- if (relatedMetadata) {
2165
- const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
2166
- if (relatedPk && rel.joinColumn) {
2167
- item[rel.joinColumn] = saved[relatedPk.name];
2168
- }
2169
- }
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, '""')}"`;
2170
1223
  }
1224
+ return `\`${columnName.replace(/`/g, "``")}\``;
2171
1225
  }
2172
- async cascadeDeleteOneToMany(entity, criteria) {
2173
- const oneToManyMeta = this.resolveOneToManyMetadata(entity);
2174
- for (const rel of oneToManyMeta) {
2175
- if (!(0, CascadeType_1.hasCascade)(rel.cascade, "delete"))
2176
- continue;
2177
- const RelatedEntity = rel.getRelatedEntity();
2178
- const parentMetadata = this.resolveEntityMetadata(entity);
2179
- if (!parentMetadata)
2180
- continue;
2181
- const pk = parentMetadata.columns.find((col) => col.options?.primary);
2182
- if (!pk)
2183
- continue;
2184
- const parents = await this.find(entity, {
2185
- where: criteria,
2186
- });
2187
- if (!parents)
2188
- continue;
2189
- const parentArray = Array.isArray(parents) ? parents : [parents];
2190
- const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
2191
- const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
2192
- const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
2193
- for (const parent of parentArray) {
2194
- const parentId = parent[pk.name];
2195
- if (parentId === undefined || parentId === null)
2196
- continue;
2197
- await this.delete(RelatedEntity, {
2198
- [fkColumn]: parentId,
2199
- });
2200
- }
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.`);
2201
1271
  }
2202
1272
  }
2203
- async aggregate(entity, fn, field, where) {
2204
- const metadata = this.resolveEntityMetadata(entity);
2205
- if (!metadata) {
2206
- 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);
2207
1277
  }
2208
- const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
1278
+ const session = new TransactionSessionManager_1.TransactionSessionManager();
2209
1279
  try {
2210
- await transactionManager.connect();
2211
- await transactionManager.startTransaction();
2212
- if (this.isMySqlFamily()) {
2213
- await transactionManager.query("SET autocommit = 0");
2214
- }
2215
- const tableName = metadata.name;
2216
- const selectExpr = (0, sql_template_tag_1.raw)(`${fn}(${field === "*" ? "*" : this.wrap(field)})`);
2217
- const whereMap = [];
2218
- if (where) {
2219
- for (const key in where) {
2220
- const value = where[key];
2221
- if (value !== undefined && value !== null) {
2222
- whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
2223
- }
2224
- }
2225
- }
2226
- let queryStr;
2227
- if (whereMap.length > 0) {
2228
- const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
2229
- 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);
2230
1282
  }
2231
1283
  else {
2232
- 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();
2233
1285
  }
2234
- const queryResult = (await transactionManager.query(queryStr));
2235
- await transactionManager.commit();
2236
- const { results } = queryResult;
2237
- if (!results || results.length === 0)
2238
- return 0;
2239
- const row = results[0];
2240
- const value = row.result ?? row["result"];
2241
- 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;
2242
1293
  }
2243
1294
  catch (e) {
2244
1295
  try {
2245
- await transactionManager.rollback();
1296
+ await session.rollback();
2246
1297
  }
2247
1298
  catch (rollbackError) {
2248
1299
  this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
@@ -2251,43 +1302,27 @@ class EntityManager {
2251
1302
  }
2252
1303
  finally {
2253
1304
  try {
2254
- await transactionManager.close();
1305
+ await session.close();
2255
1306
  }
2256
1307
  catch (closeError) {
2257
1308
  this.logger.error(`Failed to close transaction: ${closeError}`);
2258
1309
  }
2259
1310
  }
2260
1311
  }
2261
- async count(entity, where) {
2262
- return this.aggregate(entity, "COUNT", "*", where);
2263
- }
2264
- async findAndCount(entity, findOption = {}) {
2265
- const [entities, totalCount] = await Promise.all([
2266
- this.find(entity, findOption),
2267
- this.count(entity, findOption.where),
2268
- ]);
2269
- return [entities, totalCount];
2270
- }
2271
- async sum(entity, field, where) {
2272
- return this.aggregate(entity, "SUM", field, where);
2273
- }
2274
- async avg(entity, field, where) {
2275
- return this.aggregate(entity, "AVG", field, where);
2276
- }
2277
- async min(entity, field, where) {
2278
- return this.aggregate(entity, "MIN", field, where);
2279
- }
2280
- async max(entity, field, where) {
2281
- 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;
2282
1323
  }
2283
1324
  async query(sqlQuery, params) {
2284
- const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
2285
- try {
2286
- await transactionHolder.connect();
2287
- await transactionHolder.startTransaction();
2288
- if (this.isMySqlFamily()) {
2289
- await transactionHolder.query("SET autocommit = 0");
2290
- }
1325
+ return this.executeInTransaction(async (session) => {
2291
1326
  let queryResult;
2292
1327
  if (typeof sqlQuery === "string") {
2293
1328
  if (params && params.length > 0) {
@@ -2297,16 +1332,15 @@ class EntityManager {
2297
1332
  values: params,
2298
1333
  strings: [sqlQuery],
2299
1334
  };
2300
- queryResult = await transactionHolder.query(parameterizedSql);
1335
+ queryResult = await session.query(parameterizedSql);
2301
1336
  }
2302
1337
  else {
2303
- queryResult = await transactionHolder.query(sqlQuery);
1338
+ queryResult = await session.query(sqlQuery);
2304
1339
  }
2305
1340
  }
2306
1341
  else {
2307
- queryResult = await transactionHolder.query(sqlQuery);
1342
+ queryResult = await session.query(sqlQuery);
2308
1343
  }
2309
- await transactionHolder.commit();
2310
1344
  if (queryResult?.results) {
2311
1345
  return queryResult.results ?? [];
2312
1346
  }
@@ -2314,24 +1348,7 @@ class EntityManager {
2314
1348
  return queryResult;
2315
1349
  }
2316
1350
  return [];
2317
- }
2318
- catch (e) {
2319
- try {
2320
- await transactionHolder.rollback();
2321
- }
2322
- catch (rollbackError) {
2323
- this.logger.error(`Failed to rollback raw query transaction: ${rollbackError}`);
2324
- }
2325
- throw e;
2326
- }
2327
- finally {
2328
- try {
2329
- await transactionHolder.close();
2330
- }
2331
- catch (closeError) {
2332
- this.logger.error(`Failed to close raw query transaction: ${closeError}`);
2333
- }
2334
- }
1351
+ });
2335
1352
  }
2336
1353
  getRepository(entity) {
2337
1354
  return BaseRepository_1.BaseRepository.of(entity, this);