@mikro-orm/core 7.0.0-dev.22 → 7.0.0-dev.24

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 (70) hide show
  1. package/EntityManager.d.ts +12 -2
  2. package/EntityManager.js +40 -53
  3. package/README.md +1 -2
  4. package/connections/Connection.d.ts +4 -2
  5. package/connections/Connection.js +2 -2
  6. package/decorators/Entity.d.ts +15 -0
  7. package/decorators/Indexed.d.ts +2 -2
  8. package/decorators/ManyToMany.d.ts +2 -0
  9. package/decorators/ManyToOne.d.ts +2 -0
  10. package/decorators/OneToOne.d.ts +2 -0
  11. package/decorators/Transactional.d.ts +1 -0
  12. package/decorators/Transactional.js +3 -3
  13. package/drivers/IDatabaseDriver.d.ts +4 -0
  14. package/entity/ArrayCollection.d.ts +3 -1
  15. package/entity/ArrayCollection.js +7 -2
  16. package/entity/Collection.js +3 -2
  17. package/entity/EntityFactory.d.ts +6 -0
  18. package/entity/EntityFactory.js +17 -6
  19. package/entity/EntityHelper.js +5 -1
  20. package/entity/EntityLoader.js +27 -19
  21. package/entity/Reference.d.ts +5 -0
  22. package/entity/Reference.js +16 -0
  23. package/entity/WrappedEntity.js +1 -1
  24. package/entity/defineEntity.d.ts +537 -0
  25. package/entity/defineEntity.js +690 -0
  26. package/entity/index.d.ts +2 -0
  27. package/entity/index.js +2 -0
  28. package/entity/utils.d.ts +7 -0
  29. package/entity/utils.js +15 -3
  30. package/enums.d.ts +15 -2
  31. package/enums.js +13 -0
  32. package/errors.d.ts +6 -0
  33. package/errors.js +14 -0
  34. package/hydration/ObjectHydrator.js +1 -1
  35. package/index.d.ts +1 -1
  36. package/metadata/EntitySchema.js +10 -2
  37. package/metadata/MetadataDiscovery.d.ts +0 -1
  38. package/metadata/MetadataDiscovery.js +27 -18
  39. package/package.json +3 -3
  40. package/platforms/Platform.d.ts +3 -1
  41. package/serialization/SerializationContext.js +13 -10
  42. package/types/BooleanType.d.ts +1 -1
  43. package/types/DecimalType.js +1 -1
  44. package/types/DoubleType.js +1 -1
  45. package/typings.d.ts +32 -10
  46. package/typings.js +21 -4
  47. package/unit-of-work/ChangeSetComputer.js +3 -1
  48. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  49. package/unit-of-work/ChangeSetPersister.js +14 -10
  50. package/unit-of-work/UnitOfWork.d.ts +2 -1
  51. package/unit-of-work/UnitOfWork.js +36 -13
  52. package/utils/Configuration.d.ts +7 -1
  53. package/utils/Configuration.js +1 -0
  54. package/utils/ConfigurationLoader.js +2 -2
  55. package/utils/Cursor.js +3 -0
  56. package/utils/DataloaderUtils.d.ts +6 -1
  57. package/utils/DataloaderUtils.js +37 -20
  58. package/utils/EntityComparator.d.ts +6 -2
  59. package/utils/EntityComparator.js +29 -8
  60. package/utils/QueryHelper.d.ts +6 -0
  61. package/utils/QueryHelper.js +47 -4
  62. package/utils/RawQueryFragment.d.ts +34 -0
  63. package/utils/RawQueryFragment.js +35 -1
  64. package/utils/TransactionManager.d.ts +65 -0
  65. package/utils/TransactionManager.js +199 -0
  66. package/utils/Utils.d.ts +2 -2
  67. package/utils/Utils.js +31 -7
  68. package/utils/index.d.ts +1 -0
  69. package/utils/index.js +1 -0
  70. package/utils/upsert-utils.js +9 -1
@@ -4,6 +4,7 @@ import { GroupOperator, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
6
  import { RawQueryFragment, isRaw } from './RawQueryFragment.js';
7
+ /** @internal */
7
8
  export class QueryHelper {
8
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
9
10
  static processParams(params) {
@@ -33,11 +34,52 @@ export class QueryHelper {
33
34
  });
34
35
  return params;
35
36
  }
37
+ /**
38
+ * converts `{ account: { $or: [ [Object], [Object] ] } }`
39
+ * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
40
+ */
41
+ static liftGroupOperators(where, meta, metadata, key) {
42
+ if (!Utils.isPlainObject(where)) {
43
+ return undefined;
44
+ }
45
+ const keys = Object.keys(where);
46
+ const groupOperator = keys.find(k => {
47
+ return Utils.isGroupOperator(k) && Array.isArray(where[k]) && where[k].every(cond => {
48
+ return Utils.isPlainObject(cond) && Object.keys(cond).every(k2 => {
49
+ if (Utils.isOperator(k2, false)) {
50
+ if (k2 === '$not') {
51
+ return Object.keys(cond[k2]).every(k3 => meta.primaryKeys.includes(k3));
52
+ }
53
+ return true;
54
+ }
55
+ return meta.primaryKeys.includes(k2);
56
+ });
57
+ });
58
+ });
59
+ if (groupOperator) {
60
+ return groupOperator;
61
+ }
62
+ for (const k of keys) {
63
+ const value = where[k];
64
+ const prop = meta.properties[k];
65
+ if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
66
+ continue;
67
+ }
68
+ const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
69
+ if (op) {
70
+ delete where[k];
71
+ where[op] = value[op].map((v) => {
72
+ return { [k]: v };
73
+ });
74
+ }
75
+ }
76
+ return undefined;
77
+ }
36
78
  static inlinePrimaryKeyObjects(where, meta, metadata, key) {
37
79
  if (Array.isArray(where)) {
38
80
  where.forEach((item, i) => {
39
81
  if (this.inlinePrimaryKeyObjects(item, meta, metadata, key)) {
40
- where[i] = Utils.getPrimaryKeyValues(item, meta.primaryKeys, false);
82
+ where[i] = Utils.getPrimaryKeyValues(item, meta, false);
41
83
  }
42
84
  });
43
85
  }
@@ -47,7 +89,7 @@ export class QueryHelper {
47
89
  if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
48
90
  return !!key && !GroupOperator[key] && key !== '$not' && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
49
91
  if (Utils.isOperator(v, false)) {
50
- return false;
92
+ return true;
51
93
  }
52
94
  if (meta.properties[k].primary && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[k].kind)) {
53
95
  return this.inlinePrimaryKeyObjects(where[k], meta.properties[k].targetMeta, metadata, v);
@@ -58,7 +100,7 @@ export class QueryHelper {
58
100
  Object.keys(where).forEach(k => {
59
101
  const meta2 = metadata.find(meta.properties[k]?.type) || meta;
60
102
  if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
61
- where[k] = Utils.getPrimaryKeyValues(where[k], meta2.primaryKeys, true);
103
+ where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
62
104
  }
63
105
  });
64
106
  return false;
@@ -69,6 +111,7 @@ export class QueryHelper {
69
111
  const meta = metadata.find(entityName);
70
112
  // inline PK-only objects in M:N queries, so we don't join the target entity when not needed
71
113
  if (meta && root) {
114
+ QueryHelper.liftGroupOperators(where, meta, metadata);
72
115
  QueryHelper.inlinePrimaryKeyObjects(where, meta, metadata);
73
116
  }
74
117
  if (platform.getConfig().get('ignoreUndefinedInQuery') && where && typeof where === 'object') {
@@ -118,7 +161,7 @@ export class QueryHelper {
118
161
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
119
162
  }
120
163
  const isJsonProperty = prop?.customType instanceof JsonType && Utils.isPlainObject(value) && !isRaw(value) && Object.keys(value)[0] !== '$eq';
121
- if (isJsonProperty) {
164
+ if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
122
165
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
123
166
  }
124
167
  if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
@@ -72,6 +72,24 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
72
72
  * ```ts
73
73
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
74
74
  * ```
75
+ *
76
+ * The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
77
+ *
78
+ * ```ts
79
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
80
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
81
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
82
+ * @Entity({ schema: 'library' })
83
+ * export class Author { ... }
84
+ * ```
85
+ *
86
+ * You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
87
+ *
88
+ * ```ts
89
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
90
+ * @Entity({ schema: 'library' })
91
+ * export class Author { ... }
92
+ * ```
75
93
  */
76
94
  export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
77
95
  /**
@@ -96,3 +114,19 @@ export declare namespace sql {
96
114
  var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
97
115
  }
98
116
  export declare function createSqlFunction<T extends object, R = string>(func: string, key: string | ((alias: string) => string)): R;
117
+ /**
118
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
119
+ *
120
+ * Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
121
+ *
122
+ * ```ts
123
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
124
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
125
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
126
+ * @Entity({ schema: 'library' })
127
+ * export class Author { ... }
128
+ * ```
129
+ */
130
+ export declare function quote(expParts: readonly string[], ...values: (string | {
131
+ toString(): string;
132
+ })[]): any;
@@ -18,7 +18,7 @@ export class RawQueryFragment {
18
18
  }
19
19
  as(alias) {
20
20
  // TODO: to be removed in v7
21
- /* istanbul ignore next */
21
+ /* v8 ignore next 3 */
22
22
  if (alias.startsWith('`') || alias.startsWith('"')) {
23
23
  return new RawQueryFragment(`${this.sql} as ${alias}`, this.params);
24
24
  }
@@ -147,6 +147,24 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
147
147
  * ```ts
148
148
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
149
149
  * ```
150
+ *
151
+ * The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
152
+ *
153
+ * ```ts
154
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
155
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
156
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
157
+ * @Entity({ schema: 'library' })
158
+ * export class Author { ... }
159
+ * ```
160
+ *
161
+ * You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
162
+ *
163
+ * ```ts
164
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
165
+ * @Entity({ schema: 'library' })
166
+ * export class Author { ... }
167
+ * ```
150
168
  */
151
169
  export function raw(sql, params) {
152
170
  if (sql instanceof RawQueryFragment) {
@@ -205,3 +223,19 @@ sql.ref = (...keys) => raw('??', [keys.join('.')]);
205
223
  sql.now = (length) => raw('current_timestamp' + (length == null ? '' : `(${length})`));
206
224
  sql.lower = (key) => createSqlFunction('lower', key);
207
225
  sql.upper = (key) => createSqlFunction('upper', key);
226
+ /**
227
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
228
+ *
229
+ * Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
230
+ *
231
+ * ```ts
232
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
233
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
234
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
235
+ * @Entity({ schema: 'library' })
236
+ * export class Author { ... }
237
+ * ```
238
+ */
239
+ export function quote(expParts, ...values) {
240
+ return raw(expParts.join('??'), values);
241
+ }
@@ -0,0 +1,65 @@
1
+ import type { EntityManager } from '../EntityManager.js';
2
+ import { type TransactionOptions } from '../enums.js';
3
+ /**
4
+ * Manages transaction lifecycle and propagation for EntityManager.
5
+ */
6
+ export declare class TransactionManager {
7
+ private readonly em;
8
+ constructor(em: EntityManager);
9
+ /**
10
+ * Main entry point for handling transactional operations with propagation support.
11
+ */
12
+ handle<T>(cb: (em: EntityManager) => T | Promise<T>, options?: TransactionOptions): Promise<T>;
13
+ /**
14
+ * Executes the callback with the specified propagation type.
15
+ */
16
+ private executeWithPropagation;
17
+ /**
18
+ * Suspends the current transaction and returns the suspended resources.
19
+ */
20
+ private suspendTransaction;
21
+ /**
22
+ * Resumes a previously suspended transaction.
23
+ */
24
+ private resumeTransaction;
25
+ /**
26
+ * Executes operation without transaction context.
27
+ */
28
+ private executeWithoutTransaction;
29
+ /**
30
+ * Creates new independent transaction, suspending any existing one.
31
+ */
32
+ private executeWithNewTransaction;
33
+ /**
34
+ * Creates new transaction context.
35
+ */
36
+ private createNewTransaction;
37
+ /**
38
+ * Executes nested transaction with savepoint.
39
+ */
40
+ private executeNestedTransaction;
41
+ /**
42
+ * Creates a fork of the EntityManager with the given options.
43
+ */
44
+ private createFork;
45
+ /**
46
+ * Determines if changes should be propagated to the upper context.
47
+ */
48
+ private shouldPropagateToUpperContext;
49
+ /**
50
+ * Merges entities from fork to parent EntityManager.
51
+ */
52
+ private mergeEntitiesToParent;
53
+ /**
54
+ * Registers a deletion handler to unset entity identities after flush.
55
+ */
56
+ private registerDeletionHandler;
57
+ /**
58
+ * Processes transaction execution.
59
+ */
60
+ private processTransaction;
61
+ /**
62
+ * Executes transaction workflow with entity synchronization.
63
+ */
64
+ private executeTransactionFlow;
65
+ }
@@ -0,0 +1,199 @@
1
+ import { TransactionPropagation } from '../enums.js';
2
+ import { TransactionEventBroadcaster } from '../events/TransactionEventBroadcaster.js';
3
+ import { TransactionContext } from '../utils/TransactionContext.js';
4
+ import { ChangeSetType } from '../unit-of-work/ChangeSet.js';
5
+ import { TransactionStateError } from '../errors.js';
6
+ /**
7
+ * Manages transaction lifecycle and propagation for EntityManager.
8
+ */
9
+ export class TransactionManager {
10
+ em;
11
+ constructor(em) {
12
+ this.em = em;
13
+ }
14
+ /**
15
+ * Main entry point for handling transactional operations with propagation support.
16
+ */
17
+ async handle(cb, options = {}) {
18
+ const em = this.em.getContext(false);
19
+ // Set NESTED as the default propagation type
20
+ options.propagation ??= TransactionPropagation.NESTED;
21
+ // Set the context to the current transaction context if not already set
22
+ options.ctx ??= em.getTransactionContext();
23
+ const hasExistingTransaction = !!em.getTransactionContext();
24
+ return this.executeWithPropagation(options.propagation, em, cb, options, hasExistingTransaction);
25
+ }
26
+ /**
27
+ * Executes the callback with the specified propagation type.
28
+ */
29
+ async executeWithPropagation(propagation, em, cb, options, hasExistingTransaction) {
30
+ switch (propagation) {
31
+ case TransactionPropagation.NOT_SUPPORTED:
32
+ return this.executeWithoutTransaction(em, cb, options);
33
+ case TransactionPropagation.REQUIRES_NEW:
34
+ return this.executeWithNewTransaction(em, cb, options, hasExistingTransaction);
35
+ case TransactionPropagation.REQUIRED:
36
+ if (hasExistingTransaction) {
37
+ return cb(em);
38
+ }
39
+ return this.createNewTransaction(em, cb, options);
40
+ case TransactionPropagation.NESTED:
41
+ if (hasExistingTransaction) {
42
+ return this.executeNestedTransaction(em, cb, options);
43
+ }
44
+ return this.createNewTransaction(em, cb, options);
45
+ case TransactionPropagation.SUPPORTS:
46
+ if (hasExistingTransaction) {
47
+ return cb(em);
48
+ }
49
+ return this.executeWithoutTransaction(em, cb, options);
50
+ case TransactionPropagation.MANDATORY:
51
+ if (!hasExistingTransaction) {
52
+ throw TransactionStateError.requiredTransactionNotFound(propagation);
53
+ }
54
+ return cb(em);
55
+ case TransactionPropagation.NEVER:
56
+ if (hasExistingTransaction) {
57
+ throw TransactionStateError.transactionNotAllowed(propagation);
58
+ }
59
+ return this.executeWithoutTransaction(em, cb, options);
60
+ default:
61
+ throw TransactionStateError.invalidPropagation(propagation);
62
+ }
63
+ }
64
+ /**
65
+ * Suspends the current transaction and returns the suspended resources.
66
+ */
67
+ suspendTransaction(em) {
68
+ const suspended = em.getTransactionContext();
69
+ em.resetTransactionContext();
70
+ return suspended;
71
+ }
72
+ /**
73
+ * Resumes a previously suspended transaction.
74
+ */
75
+ resumeTransaction(em, suspended) {
76
+ if (suspended != null) {
77
+ em.setTransactionContext(suspended);
78
+ }
79
+ }
80
+ /**
81
+ * Executes operation without transaction context.
82
+ */
83
+ async executeWithoutTransaction(em, cb, options) {
84
+ const suspended = this.suspendTransaction(em);
85
+ const fork = this.createFork(em, { ...options, disableTransactions: true });
86
+ const propagateToUpperContext = this.shouldPropagateToUpperContext(em);
87
+ try {
88
+ return await this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
89
+ }
90
+ finally {
91
+ this.resumeTransaction(em, suspended);
92
+ }
93
+ }
94
+ /**
95
+ * Creates new independent transaction, suspending any existing one.
96
+ */
97
+ async executeWithNewTransaction(em, cb, options, hasExistingTransaction) {
98
+ const fork = this.createFork(em, options);
99
+ let suspended = null;
100
+ // Suspend existing transaction if present
101
+ if (hasExistingTransaction) {
102
+ suspended = this.suspendTransaction(em);
103
+ }
104
+ const newOptions = { ...options, ctx: undefined };
105
+ try {
106
+ return await this.processTransaction(em, fork, cb, newOptions);
107
+ }
108
+ finally {
109
+ if (suspended != null) {
110
+ this.resumeTransaction(em, suspended);
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Creates new transaction context.
116
+ */
117
+ async createNewTransaction(em, cb, options) {
118
+ const fork = this.createFork(em, options);
119
+ return this.processTransaction(em, fork, cb, options);
120
+ }
121
+ /**
122
+ * Executes nested transaction with savepoint.
123
+ */
124
+ async executeNestedTransaction(em, cb, options) {
125
+ const fork = this.createFork(em, options);
126
+ // Pass existing context to create savepoint
127
+ const nestedOptions = { ...options, ctx: em.getTransactionContext() };
128
+ return this.processTransaction(em, fork, cb, nestedOptions);
129
+ }
130
+ /**
131
+ * Creates a fork of the EntityManager with the given options.
132
+ */
133
+ createFork(em, options) {
134
+ return em.fork({
135
+ clear: options.clear ?? false,
136
+ flushMode: options.flushMode,
137
+ cloneEventManager: true,
138
+ disableTransactions: options.ignoreNestedTransactions,
139
+ loggerContext: options.loggerContext,
140
+ });
141
+ }
142
+ /**
143
+ * Determines if changes should be propagated to the upper context.
144
+ */
145
+ shouldPropagateToUpperContext(em) {
146
+ return !em.global || this.em.config.get('allowGlobalContext');
147
+ }
148
+ /**
149
+ * Merges entities from fork to parent EntityManager.
150
+ */
151
+ mergeEntitiesToParent(fork, parent) {
152
+ for (const entity of fork.getUnitOfWork(false).getIdentityMap()) {
153
+ parent.merge(entity, { disableContextResolution: true, keepIdentity: true, refresh: true });
154
+ }
155
+ }
156
+ /**
157
+ * Registers a deletion handler to unset entity identities after flush.
158
+ */
159
+ registerDeletionHandler(fork, parent) {
160
+ fork.getEventManager().registerSubscriber({
161
+ afterFlush: (args) => {
162
+ const deletionChangeSets = args.uow.getChangeSets()
163
+ .filter(cs => cs.type === ChangeSetType.DELETE || cs.type === ChangeSetType.DELETE_EARLY);
164
+ for (const cs of deletionChangeSets) {
165
+ parent.getUnitOfWork(false).unsetIdentity(cs.entity);
166
+ }
167
+ },
168
+ });
169
+ }
170
+ /**
171
+ * Processes transaction execution.
172
+ */
173
+ async processTransaction(em, fork, cb, options) {
174
+ const propagateToUpperContext = this.shouldPropagateToUpperContext(em);
175
+ const eventBroadcaster = new TransactionEventBroadcaster(fork, undefined);
176
+ return TransactionContext.create(fork, () => fork.getConnection().transactional(async (trx) => {
177
+ fork.setTransactionContext(trx);
178
+ return this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
179
+ }, { ...options, eventBroadcaster }));
180
+ }
181
+ /**
182
+ * Executes transaction workflow with entity synchronization.
183
+ */
184
+ async executeTransactionFlow(fork, cb, propagateToUpperContext, parentEm) {
185
+ if (!propagateToUpperContext) {
186
+ const ret = await cb(fork);
187
+ await fork.flush();
188
+ return ret;
189
+ }
190
+ // Setup: Register deletion handler before execution
191
+ this.registerDeletionHandler(fork, parentEm);
192
+ // Execute callback and flush
193
+ const ret = await cb(fork);
194
+ await fork.flush();
195
+ // Synchronization: Merge entities back to the parent
196
+ this.mergeEntitiesToParent(fork, parentEm);
197
+ return ret;
198
+ }
199
+ }
package/utils/Utils.d.ts CHANGED
@@ -134,14 +134,14 @@ export declare class Utils {
134
134
  static getCompositeKeyHash<T>(data: EntityData<T>, meta: EntityMetadata<T>, convertCustomTypes?: boolean, platform?: Platform, flat?: boolean): string;
135
135
  static getPrimaryKeyHash(pks: (string | Buffer | Date)[]): string;
136
136
  static splitPrimaryKeys<T extends object>(key: string): EntityKey<T>[];
137
- static getPrimaryKeyValues<T>(entity: T, primaryKeys: string[], allowScalar?: boolean, convertCustomTypes?: boolean): any;
137
+ static getPrimaryKeyValues<T>(entity: T, primaryKeys: string[] | EntityMetadata<T>, allowScalar?: boolean, convertCustomTypes?: boolean): any;
138
138
  static getPrimaryKeyCond<T>(entity: T, primaryKeys: EntityKey<T>[]): Record<string, Primary<T>> | null;
139
139
  /**
140
140
  * Maps nested FKs from `[1, 2, 3]` to `[1, [2, 3]]`.
141
141
  */
142
142
  static mapFlatCompositePrimaryKey(fk: Primary<any>[], prop: EntityProperty, fieldNames?: string[], idx?: number): Primary<any> | Primary<any>[];
143
143
  static getPrimaryKeyCondFromArray<T extends object>(pks: Primary<T>[], meta: EntityMetadata<T>): Record<string, Primary<T>>;
144
- static getOrderedPrimaryKeys<T>(id: Primary<T> | Record<string, Primary<T>>, meta: EntityMetadata<T>, platform?: Platform, convertCustomTypes?: boolean): Primary<T>[];
144
+ static getOrderedPrimaryKeys<T>(id: Primary<T> | Record<string, Primary<T>>, meta: EntityMetadata<T>, platform?: Platform, convertCustomTypes?: boolean, allowScalar?: boolean): Primary<T>[];
145
145
  /**
146
146
  * Checks whether given object is an entity instance.
147
147
  */
package/utils/Utils.js CHANGED
@@ -483,6 +483,7 @@ export class Utils {
483
483
  static splitPrimaryKeys(key) {
484
484
  return key.split(this.PK_SEPARATOR);
485
485
  }
486
+ // TODO v7: remove support for `primaryKeys: string[]`
486
487
  static getPrimaryKeyValues(entity, primaryKeys, allowScalar = false, convertCustomTypes = false) {
487
488
  /* v8 ignore next 3 */
488
489
  if (entity == null) {
@@ -494,9 +495,24 @@ export class Utils {
494
495
  }
495
496
  return val;
496
497
  }
497
- const pk = Utils.isEntity(entity, true)
498
- ? helper(entity).getPrimaryKey(convertCustomTypes)
499
- : primaryKeys.reduce((o, pk) => { o[pk] = entity[pk]; return o; }, {});
498
+ const meta = Array.isArray(primaryKeys) ? undefined : primaryKeys;
499
+ primaryKeys = Array.isArray(primaryKeys) ? primaryKeys : meta.primaryKeys;
500
+ let pk;
501
+ if (Utils.isEntity(entity, true)) {
502
+ pk = helper(entity).getPrimaryKey(convertCustomTypes);
503
+ }
504
+ else {
505
+ pk = primaryKeys.reduce((o, pk) => {
506
+ const targetMeta = meta?.properties[pk].targetMeta;
507
+ if (targetMeta && Utils.isPlainObject(entity[pk])) {
508
+ o[pk] = Utils.getPrimaryKeyValues(entity[pk], targetMeta, allowScalar, convertCustomTypes);
509
+ }
510
+ else {
511
+ o[pk] = entity[pk];
512
+ }
513
+ return o;
514
+ }, {});
515
+ }
500
516
  if (primaryKeys.length > 1) {
501
517
  return toArray(pk);
502
518
  }
@@ -546,7 +562,7 @@ export class Utils {
546
562
  return o;
547
563
  }, {});
548
564
  }
549
- static getOrderedPrimaryKeys(id, meta, platform, convertCustomTypes = false) {
565
+ static getOrderedPrimaryKeys(id, meta, platform, convertCustomTypes = false, allowScalar = false) {
550
566
  const data = (Utils.isPrimaryKey(id) ? { [meta.primaryKeys[0]]: id } : id);
551
567
  const pks = meta.primaryKeys.map((pk, idx) => {
552
568
  const prop = meta.properties[pk];
@@ -556,11 +572,14 @@ export class Utils {
556
572
  value = prop.customType.convertToJSValue(value, platform);
557
573
  }
558
574
  if (prop.kind !== ReferenceKind.SCALAR && prop.targetMeta) {
559
- const value2 = this.getOrderedPrimaryKeys(value, prop.targetMeta, platform, convertCustomTypes);
575
+ const value2 = this.getOrderedPrimaryKeys(value, prop.targetMeta, platform, convertCustomTypes, allowScalar);
560
576
  value = value2.length > 1 ? value2 : value2[0];
561
577
  }
562
578
  return value;
563
579
  });
580
+ if (allowScalar && pks.length === 1) {
581
+ return pks[0];
582
+ }
564
583
  // we need to flatten the PKs as composite PKs can be build from another composite PKs
565
584
  // and this method is used to get the PK hash in identity map, that expects flat array
566
585
  return Utils.flatten(pks);
@@ -919,8 +938,13 @@ export class Utils {
919
938
  /* v8 ignore next 5 */
920
939
  }
921
940
  catch {
922
- // this works in production build where we do not have the `src` folder
923
- return this.requireFrom('../package.json', import.meta.dirname).version;
941
+ try {
942
+ // this works in production build where we do not have the `src` folder
943
+ return this.requireFrom('../package.json', import.meta.dirname).version;
944
+ }
945
+ catch {
946
+ return 'N/A';
947
+ }
924
948
  }
925
949
  }
926
950
  static createFunction(context, code) {
package/utils/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './DataloaderUtils.js';
5
5
  export * from './Utils.js';
6
6
  export * from './RequestContext.js';
7
7
  export * from './TransactionContext.js';
8
+ export * from './TransactionManager.js';
8
9
  export * from './QueryHelper.js';
9
10
  export * from './NullHighlighter.js';
10
11
  export * from './EntityComparator.js';
package/utils/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './DataloaderUtils.js';
5
5
  export * from './Utils.js';
6
6
  export * from './RequestContext.js';
7
7
  export * from './TransactionContext.js';
8
+ export * from './TransactionManager.js';
8
9
  export * from './QueryHelper.js';
9
10
  export * from './NullHighlighter.js';
10
11
  export * from './EntityComparator.js';
@@ -71,7 +71,15 @@ export function getOnConflictReturningFields(meta, data, uniqueFields, options)
71
71
  if (!meta) {
72
72
  return '*';
73
73
  }
74
- const keys = meta.comparableProps.filter(p => !p.lazy && !p.embeddable && Array.isArray(uniqueFields) && !uniqueFields.includes(p.name)).map(p => p.name);
74
+ const keys = meta.comparableProps.filter(p => {
75
+ if (p.lazy || p.embeddable) {
76
+ return false;
77
+ }
78
+ if (p.autoincrement) {
79
+ return true;
80
+ }
81
+ return Array.isArray(uniqueFields) && !uniqueFields.includes(p.name);
82
+ }).map(p => p.name);
75
83
  if (meta.versionProperty) {
76
84
  keys.push(meta.versionProperty);
77
85
  }