@mikro-orm/core 7.0.0-dev.6 → 7.0.0-dev.60

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 (122) hide show
  1. package/EntityManager.d.ts +85 -32
  2. package/EntityManager.js +281 -178
  3. package/MikroORM.d.ts +8 -8
  4. package/MikroORM.js +31 -74
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +2 -1
  7. package/cache/FileCacheAdapter.js +5 -4
  8. package/connections/Connection.d.ts +11 -7
  9. package/connections/Connection.js +16 -13
  10. package/decorators/Embedded.d.ts +5 -11
  11. package/decorators/Entity.d.ts +18 -3
  12. package/decorators/Indexed.d.ts +2 -2
  13. package/decorators/ManyToMany.d.ts +2 -0
  14. package/decorators/ManyToOne.d.ts +4 -0
  15. package/decorators/OneToOne.d.ts +4 -0
  16. package/decorators/Property.d.ts +53 -9
  17. package/decorators/Transactional.d.ts +3 -1
  18. package/decorators/Transactional.js +6 -3
  19. package/decorators/index.d.ts +1 -1
  20. package/drivers/DatabaseDriver.d.ts +11 -5
  21. package/drivers/DatabaseDriver.js +13 -4
  22. package/drivers/IDatabaseDriver.d.ts +29 -5
  23. package/entity/ArrayCollection.d.ts +6 -4
  24. package/entity/ArrayCollection.js +26 -9
  25. package/entity/BaseEntity.d.ts +0 -1
  26. package/entity/BaseEntity.js +0 -3
  27. package/entity/Collection.d.ts +3 -4
  28. package/entity/Collection.js +34 -17
  29. package/entity/EntityAssigner.d.ts +1 -1
  30. package/entity/EntityAssigner.js +9 -1
  31. package/entity/EntityFactory.d.ts +7 -0
  32. package/entity/EntityFactory.js +40 -22
  33. package/entity/EntityHelper.js +25 -8
  34. package/entity/EntityLoader.d.ts +5 -4
  35. package/entity/EntityLoader.js +69 -36
  36. package/entity/EntityRepository.d.ts +1 -1
  37. package/entity/EntityValidator.js +2 -2
  38. package/entity/Reference.d.ts +9 -7
  39. package/entity/Reference.js +32 -5
  40. package/entity/WrappedEntity.d.ts +0 -2
  41. package/entity/WrappedEntity.js +1 -5
  42. package/entity/defineEntity.d.ts +555 -0
  43. package/entity/defineEntity.js +529 -0
  44. package/entity/index.d.ts +2 -0
  45. package/entity/index.js +2 -0
  46. package/entity/utils.d.ts +7 -0
  47. package/entity/utils.js +15 -3
  48. package/enums.d.ts +18 -5
  49. package/enums.js +13 -0
  50. package/errors.d.ts +6 -1
  51. package/errors.js +14 -4
  52. package/events/EventSubscriber.d.ts +3 -1
  53. package/hydration/ObjectHydrator.d.ts +4 -4
  54. package/hydration/ObjectHydrator.js +35 -24
  55. package/index.d.ts +2 -1
  56. package/index.js +1 -1
  57. package/logging/DefaultLogger.d.ts +1 -1
  58. package/logging/SimpleLogger.d.ts +1 -1
  59. package/metadata/EntitySchema.d.ts +8 -4
  60. package/metadata/EntitySchema.js +40 -20
  61. package/metadata/MetadataDiscovery.d.ts +5 -7
  62. package/metadata/MetadataDiscovery.js +150 -155
  63. package/metadata/MetadataStorage.js +1 -1
  64. package/metadata/MetadataValidator.js +4 -3
  65. package/metadata/discover-entities.d.ts +5 -0
  66. package/metadata/discover-entities.js +39 -0
  67. package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
  68. package/naming-strategy/AbstractNamingStrategy.js +7 -1
  69. package/naming-strategy/NamingStrategy.d.ts +11 -1
  70. package/package.json +14 -7
  71. package/platforms/Platform.d.ts +5 -8
  72. package/platforms/Platform.js +4 -17
  73. package/serialization/EntitySerializer.d.ts +2 -0
  74. package/serialization/EntitySerializer.js +29 -11
  75. package/serialization/EntityTransformer.js +22 -12
  76. package/serialization/SerializationContext.js +14 -11
  77. package/types/BigIntType.d.ts +9 -6
  78. package/types/BigIntType.js +3 -0
  79. package/types/BlobType.d.ts +0 -1
  80. package/types/BlobType.js +0 -3
  81. package/types/BooleanType.d.ts +2 -1
  82. package/types/BooleanType.js +3 -0
  83. package/types/DecimalType.d.ts +6 -4
  84. package/types/DecimalType.js +1 -1
  85. package/types/DoubleType.js +1 -1
  86. package/types/JsonType.d.ts +1 -1
  87. package/types/JsonType.js +7 -2
  88. package/types/Type.d.ts +2 -1
  89. package/types/Type.js +1 -1
  90. package/types/Uint8ArrayType.d.ts +0 -1
  91. package/types/Uint8ArrayType.js +0 -3
  92. package/types/index.d.ts +1 -1
  93. package/typings.d.ts +94 -50
  94. package/typings.js +31 -31
  95. package/unit-of-work/ChangeSetComputer.js +8 -3
  96. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  97. package/unit-of-work/ChangeSetPersister.js +37 -16
  98. package/unit-of-work/UnitOfWork.d.ts +8 -1
  99. package/unit-of-work/UnitOfWork.js +110 -53
  100. package/utils/AbstractSchemaGenerator.js +3 -1
  101. package/utils/Configuration.d.ts +201 -184
  102. package/utils/Configuration.js +143 -151
  103. package/utils/ConfigurationLoader.d.ts +9 -22
  104. package/utils/ConfigurationLoader.js +53 -76
  105. package/utils/Cursor.d.ts +3 -3
  106. package/utils/Cursor.js +3 -0
  107. package/utils/DataloaderUtils.d.ts +15 -5
  108. package/utils/DataloaderUtils.js +53 -7
  109. package/utils/EntityComparator.d.ts +8 -4
  110. package/utils/EntityComparator.js +105 -58
  111. package/utils/QueryHelper.d.ts +9 -1
  112. package/utils/QueryHelper.js +66 -5
  113. package/utils/RawQueryFragment.d.ts +36 -4
  114. package/utils/RawQueryFragment.js +34 -13
  115. package/utils/TransactionManager.d.ts +65 -0
  116. package/utils/TransactionManager.js +223 -0
  117. package/utils/Utils.d.ts +13 -12
  118. package/utils/Utils.js +106 -66
  119. package/utils/index.d.ts +1 -0
  120. package/utils/index.js +1 -0
  121. package/utils/upsert-utils.d.ts +7 -2
  122. package/utils/upsert-utils.js +52 -1
@@ -1,9 +1,10 @@
1
- import type { Primary, Ref } from '../typings.js';
1
+ import type { Constructor, Primary, Ref } from '../typings.js';
2
2
  import { Collection, type InitCollectionOptions } from '../entity/Collection.js';
3
3
  import { type EntityManager } from '../EntityManager.js';
4
- import type DataLoader from 'dataloader';
5
4
  import { type LoadReferenceOptions } from '../entity/Reference.js';
5
+ type BatchLoadFn<K, V> = (keys: readonly K[]) => PromiseLike<ArrayLike<V | Error>>;
6
6
  export declare class DataloaderUtils {
7
+ private static DataLoader?;
7
8
  /**
8
9
  * Groups identified references by entity and returns a Map with the
9
10
  * class name as the index and the corresponding primary keys as the value.
@@ -13,7 +14,7 @@ export declare class DataloaderUtils {
13
14
  * Returns the reference dataloader batchLoadFn, which aggregates references by entity,
14
15
  * makes one query per entity and maps each input reference to the corresponding result.
15
16
  */
16
- static getRefBatchLoadFn(em: EntityManager): DataLoader.BatchLoadFn<[Ref<any>, Omit<LoadReferenceOptions<any, any>, 'dataloader'>?], any>;
17
+ static getRefBatchLoadFn(em: EntityManager): BatchLoadFn<[Ref<any>, Omit<LoadReferenceOptions<any, any>, 'dataloader'>?], any>;
17
18
  /**
18
19
  * Groups collections by entity and returns a Map whose keys are the entity names and whose values are filter Maps
19
20
  * which we can use to narrow down the find query to return just the items of the collections that have been dataloaded.
@@ -34,8 +35,17 @@ export declare class DataloaderUtils {
34
35
  */
35
36
  static getColFilter<T, S extends T>(collection: Collection<any>): (result: T) => result is S;
36
37
  /**
37
- * Returns the collection dataloader batchLoadFn, which aggregates collections by entity,
38
+ * Returns the 1:M collection dataloader batchLoadFn, which aggregates collections by entity,
38
39
  * makes one query per entity and maps each input collection to the corresponding result.
39
40
  */
40
- static getColBatchLoadFn(em: EntityManager): DataLoader.BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
41
+ static getColBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
42
+ /**
43
+ * Returns the M:N collection dataloader batchLoadFn, which aggregates collections by entity,
44
+ * makes one query per entity and maps each input collection to the corresponding result.
45
+ */
46
+ static getManyToManyColBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
47
+ static getDataLoader(): Promise<Constructor<{
48
+ load: (...args: unknown[]) => Promise<unknown>;
49
+ }>>;
41
50
  }
51
+ export {};
@@ -1,8 +1,9 @@
1
1
  import { Collection } from '../entity/Collection.js';
2
2
  import { helper } from '../entity/wrap.js';
3
- import { ReferenceKind } from '../enums.js';
4
3
  import { Reference } from '../entity/Reference.js';
4
+ import { Utils } from './Utils.js';
5
5
  export class DataloaderUtils {
6
+ static DataLoader;
6
7
  /**
7
8
  * Groups identified references by entity and returns a Map with the
8
9
  * class name as the index and the corresponding primary keys as the value.
@@ -118,7 +119,6 @@ export class DataloaderUtils {
118
119
  // We need to populate the inverse side of the relationship in order to be able to later retrieve the PK(s) from its item(s)
119
120
  populate: [
120
121
  ...(opts.populate === false ? [] : opts.populate ?? []),
121
- ...(opts.ref ? [':ref'] : []),
122
122
  ...Array.from(filterMap.keys()).filter(
123
123
  // We need to do so only if the inverse side is a collection, because we can already retrieve the PK from a reference without having to load it
124
124
  prop => em.getMetadata(className).properties[prop]?.ref !== true),
@@ -149,15 +149,11 @@ export class DataloaderUtils {
149
149
  else if (target) {
150
150
  return target === collection.owner;
151
151
  }
152
- // FIXME https://github.com/mikro-orm/mikro-orm/issues/6031
153
- if (!target && collection.property.kind === ReferenceKind.MANY_TO_MANY) {
154
- throw new Error(`Inverse side is required for M:N relations with dataloader: ${collection.owner.constructor.name}.${collection.property.name}`);
155
- }
156
152
  return false;
157
153
  };
158
154
  }
159
155
  /**
160
- * Returns the collection dataloader batchLoadFn, which aggregates collections by entity,
156
+ * Returns the 1:M collection dataloader batchLoadFn, which aggregates collections by entity,
161
157
  * makes one query per entity and maps each input collection to the corresponding result.
162
158
  */
163
159
  static getColBatchLoadFn(em) {
@@ -179,4 +175,54 @@ export class DataloaderUtils {
179
175
  });
180
176
  };
181
177
  }
178
+ /**
179
+ * Returns the M:N collection dataloader batchLoadFn, which aggregates collections by entity,
180
+ * makes one query per entity and maps each input collection to the corresponding result.
181
+ */
182
+ static getManyToManyColBatchLoadFn(em) {
183
+ return async (collsWithOpts) => {
184
+ const groups = new Map();
185
+ for (const [col, opts] of collsWithOpts) {
186
+ const key = `${col.property.targetMeta.className}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
187
+ const value = groups.get(key) ?? [];
188
+ value.push([col, opts ?? {}]);
189
+ groups.set(key, value);
190
+ }
191
+ const ret = [];
192
+ for (const group of groups.values()) {
193
+ const prop = group[0][0].property;
194
+ const options = {};
195
+ const wrap = (cond) => ({ [prop.name]: cond });
196
+ const orderBy = Utils.asArray(group[0][1]?.orderBy).map(o => wrap(o));
197
+ const populate = wrap(group[0][1]?.populate);
198
+ const owners = group.map(c => c[0].owner);
199
+ const $or = [];
200
+ // a bit of a hack, but we need to prefix the key, since we have only a column name, not a property name
201
+ const alias = em.config.getNamingStrategy().aliasName(prop.pivotEntity, 0);
202
+ const fk = `${alias}.${Utils.getPrimaryKeyHash(prop.joinColumns)}`;
203
+ for (const c of group) {
204
+ $or.push({ $and: [c[1]?.where ?? {}, { [fk]: c[0].owner }] });
205
+ options.refresh ??= c[1]?.refresh;
206
+ }
207
+ options.where = wrap({ $or });
208
+ const r = await em.getEntityLoader().findChildrenFromPivotTable(owners, prop, options, orderBy, populate, group[0][1]?.ref);
209
+ ret.push(...r);
210
+ }
211
+ return ret;
212
+ };
213
+ }
214
+ static async getDataLoader() {
215
+ if (this.DataLoader) {
216
+ return this.DataLoader;
217
+ }
218
+ try {
219
+ const mod = await import('dataloader' + '');
220
+ const DataLoader = mod.default;
221
+ return (this.DataLoader ??= DataLoader);
222
+ }
223
+ catch {
224
+ /* v8 ignore next 2 */
225
+ throw new Error('DataLoader is not found, make sure `dataloader` package is installed in your project\'s dependencies.');
226
+ }
227
+ }
182
228
  }
@@ -1,6 +1,8 @@
1
- import type { EntityData, EntityDictionary, EntityMetadata, EntityProperty, IMetadataStorage } from '../typings.js';
1
+ import type { EntityData, EntityDictionary, EntityMetadata, EntityName, EntityProperty, IMetadataStorage } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
- type Comparator<T> = (a: T, b: T) => EntityData<T>;
3
+ type Comparator<T> = (a: T, b: T, options?: {
4
+ includeInverseSides?: boolean;
5
+ }) => EntityData<T>;
4
6
  type ResultMapper<T> = (result: EntityData<T>) => EntityData<T> | null;
5
7
  type SnapshotGenerator<T> = (entity: T) => EntityData<T>;
6
8
  type CompositeKeyPart = string | CompositeKeyPart[];
@@ -18,7 +20,9 @@ export declare class EntityComparator {
18
20
  /**
19
21
  * Computes difference between two entities.
20
22
  */
21
- diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T>): EntityData<T>;
23
+ diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T>, options?: {
24
+ includeInverseSides?: boolean;
25
+ }): EntityData<T>;
22
26
  matching<T>(entityName: string, a: EntityData<T>, b: EntityData<T>): boolean;
23
27
  /**
24
28
  * Removes ORM specific code from entities and prepares it for serializing. Used before change set computation.
@@ -44,7 +48,7 @@ export declare class EntityComparator {
44
48
  /**
45
49
  * @internal Highly performance-sensitive method.
46
50
  */
47
- getSnapshotGenerator<T>(entityName: string): SnapshotGenerator<T>;
51
+ getSnapshotGenerator<T>(entityName: EntityName<T>): SnapshotGenerator<T>;
48
52
  /**
49
53
  * @internal
50
54
  */
@@ -20,9 +20,9 @@ export class EntityComparator {
20
20
  /**
21
21
  * Computes difference between two entities.
22
22
  */
23
- diffEntities(entityName, a, b) {
23
+ diffEntities(entityName, a, b, options) {
24
24
  const comparator = this.getEntityComparator(entityName);
25
- return Utils.callCompiledFunction(comparator, a, b);
25
+ return Utils.callCompiledFunction(comparator, a, b, options);
26
26
  }
27
27
  matching(entityName, a, b) {
28
28
  const diff = this.diffEntities(entityName, a, b);
@@ -155,6 +155,7 @@ export class EntityComparator {
155
155
  const lines = [];
156
156
  const context = new Map();
157
157
  context.set('getCompositeKeyValue', (val) => Utils.flatten(Utils.getCompositeKeyValue(val, meta, 'convertToDatabaseValue', this.platform)));
158
+ context.set('getPrimaryKeyHash', (val) => Utils.getPrimaryKeyHash(Utils.asArray(val)));
158
159
  if (meta.primaryKeys.length > 1) {
159
160
  lines.push(` const pks = entity.__helper.__pk ? getCompositeKeyValue(entity.__helper.__pk) : [`);
160
161
  meta.primaryKeys.forEach(pk => {
@@ -170,14 +171,23 @@ export class EntityComparator {
170
171
  }
171
172
  else {
172
173
  const pk = meta.primaryKeys[0];
173
- if (meta.properties[pk].kind !== ReferenceKind.SCALAR) {
174
+ const prop = meta.properties[pk];
175
+ if (prop.kind !== ReferenceKind.SCALAR) {
174
176
  lines.push(` if (entity${this.wrap(pk)} != null && (entity${this.wrap(pk)}.__entity || entity${this.wrap(pk)}.__reference)) return entity${this.wrap(pk)}.__helper.getSerializedPrimaryKey();`);
175
177
  }
176
178
  const serializedPrimaryKey = meta.props.find(p => p.serializedPrimaryKey);
177
179
  if (serializedPrimaryKey) {
178
180
  lines.push(` return '' + entity.${serializedPrimaryKey.name};`);
179
181
  }
180
- lines.push(` return '' + entity.${meta.primaryKeys[0]};`);
182
+ else if (prop.customType) {
183
+ const convertorKey = this.registerCustomType(meta.properties[pk], context);
184
+ const idx = this.tmpIndex++;
185
+ lines.push(` const val_${idx} = convertToDatabaseValue_${convertorKey}(entity${this.wrap(pk)});`);
186
+ lines.push(` return getPrimaryKeyHash(val_${idx});`);
187
+ }
188
+ else {
189
+ lines.push(` return '' + entity${this.wrap(pk)};`);
190
+ }
181
191
  }
182
192
  const code = `// compiled pk serializer for entity ${meta.className}\n`
183
193
  + `return function(entity) {\n${lines.join('\n')}\n}`;
@@ -189,6 +199,7 @@ export class EntityComparator {
189
199
  * @internal Highly performance-sensitive method.
190
200
  */
191
201
  getSnapshotGenerator(entityName) {
202
+ entityName = Utils.className(entityName);
192
203
  const exists = this.snapshotGenerators.get(entityName);
193
204
  if (exists) {
194
205
  return exists;
@@ -286,57 +297,73 @@ export class EntityComparator {
286
297
  lines.push(`${padding} }`);
287
298
  };
288
299
  lines.push(` const mapped = {};`);
289
- meta.props.forEach(prop => {
290
- if (!prop.fieldNames) {
291
- return;
292
- }
293
- if (prop.targetMeta && prop.fieldNames.length > 1) {
294
- lines.push(` if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
295
- lines.push(` } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
296
- lines.push(` ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
297
- lines.push(...prop.fieldNames.map(field => ` ${this.propName(field, 'mapped')} = true;`));
298
- lines.push(` } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
299
- lines.push(...prop.fieldNames.map(field => ` ${this.propName(field, 'mapped')} = true;`), ' }');
300
- return;
301
- }
302
- if (prop.embedded && (meta.embeddable || meta.properties[prop.embedded[0]].object)) {
303
- return;
304
- }
305
- if (prop.runtimeType === 'boolean') {
306
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
307
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : !!${this.propName(prop.fieldNames[0])};`);
308
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
309
- lines.push(` }`);
310
- }
311
- else if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
312
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
313
- context.set('parseDate', (value) => this.platform.parseDate(value));
314
- parseDate('ret' + this.wrap(prop.name), this.propName(prop.fieldNames[0]));
315
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
316
- lines.push(` }`);
317
- }
318
- else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
319
- const idx = this.tmpIndex++;
320
- context.set(`mapEmbeddedResult_${idx}`, (data) => {
321
- const item = parseJsonSafe(data);
322
- if (Array.isArray(item)) {
323
- return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
324
- }
325
- return item == null ? item : this.getResultMapper(prop.type)(item);
326
- });
327
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
328
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
329
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
330
- lines.push(` }`);
300
+ const mapEntityProperties = (meta, padding = '') => {
301
+ for (const prop of meta.props) {
302
+ if (!prop.fieldNames) {
303
+ continue;
304
+ }
305
+ if (prop.targetMeta && prop.fieldNames.length > 1) {
306
+ lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
307
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
308
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
309
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
310
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
311
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
312
+ continue;
313
+ }
314
+ if (prop.embedded && (meta.embeddable || meta.properties[prop.embedded[0]].object)) {
315
+ continue;
316
+ }
317
+ if (prop.runtimeType === 'boolean') {
318
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
319
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : !!${this.propName(prop.fieldNames[0])};`);
320
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
321
+ lines.push(`${padding} }`);
322
+ }
323
+ else if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
324
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
325
+ context.set('parseDate', (value) => this.platform.parseDate(value));
326
+ parseDate('ret' + this.wrap(prop.name), this.propName(prop.fieldNames[0]), padding);
327
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
328
+ lines.push(`${padding} }`);
329
+ }
330
+ else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
331
+ const idx = this.tmpIndex++;
332
+ context.set(`mapEmbeddedResult_${idx}`, (data) => {
333
+ const item = parseJsonSafe(data);
334
+ if (Array.isArray(item)) {
335
+ return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
336
+ }
337
+ return item == null ? item : this.getResultMapper(prop.type)(item);
338
+ });
339
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
340
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
341
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
342
+ lines.push(`${padding} }`);
343
+ }
344
+ else if (prop.kind !== ReferenceKind.EMBEDDED) {
345
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
346
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])};`);
347
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
348
+ lines.push(`${padding} }`);
349
+ }
331
350
  }
332
- else if (prop.kind !== ReferenceKind.EMBEDDED) {
333
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
334
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])};`);
335
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
351
+ };
352
+ if (meta.polymorphs && meta.discriminatorColumn) {
353
+ for (const polymorph of meta.polymorphs) {
354
+ const first = polymorph === meta.polymorphs[0];
355
+ lines.push(` ${first ? '' : 'else '}if (${this.propName(meta.discriminatorColumn)} == '${polymorph.discriminatorValue}') {`);
356
+ mapEntityProperties(polymorph, ' ');
336
357
  lines.push(` }`);
337
358
  }
338
- });
339
- lines.push(` for (let k in result) { if (Object.hasOwn(result, k) && !mapped[k]) ret[k] = result[k]; }`);
359
+ lines.push(` else {`);
360
+ mapEntityProperties(meta, ' ');
361
+ lines.push(` }`);
362
+ }
363
+ else {
364
+ mapEntityProperties(meta);
365
+ }
366
+ lines.push(` for (let k in result) { if (Object.hasOwn(result, k) && !mapped[k] && ret[k] === undefined) ret[k] = result[k]; }`);
340
367
  const code = `// compiled mapper for entity ${meta.className}\n`
341
368
  + `return function(result) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
342
369
  const resultMapper = Utils.createFunction(context, code);
@@ -391,11 +418,21 @@ export class EntityComparator {
391
418
  }
392
419
  getEmbeddedPropertySnapshot(meta, prop, context, level, path, dataKey, object = prop.object) {
393
420
  const padding = ' '.repeat(level * 2);
421
+ const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
394
422
  let ret = `${level === 1 ? '' : '\n'}`;
395
423
  if (object) {
396
- const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
397
424
  ret += `${padding}if (${nullCond}) ret${dataKey} = null;\n`;
398
425
  }
426
+ else {
427
+ ret += `${padding}if (${nullCond}) {\n`;
428
+ ret += meta.props.filter(p => p.embedded?.[0] === prop.name
429
+ // object for JSON embeddable
430
+ && (p.object || (p.persist !== false))).map(childProp => {
431
+ const childDataKey = meta.embeddable || prop.object ? dataKey + this.wrap(childProp.embedded[1]) : this.wrap(childProp.name);
432
+ return `${padding} ret${childDataKey} = null;`;
433
+ }).join('\n') + `\n`;
434
+ ret += `${padding}}\n`;
435
+ }
399
436
  const cond = `entity${path.map(k => this.wrap(k)).join('')} != null`;
400
437
  ret += `${padding}if (${cond}) {\n`;
401
438
  if (object) {
@@ -515,17 +552,27 @@ export class EntityComparator {
515
552
  context.set('compareBuffers', compareBuffers);
516
553
  context.set('compareObjects', compareObjects);
517
554
  context.set('equals', equals);
518
- meta.comparableProps.forEach(prop => {
555
+ for (const prop of meta.comparableProps) {
519
556
  lines.push(this.getPropertyComparator(prop, context));
520
- });
557
+ }
558
+ // also compare 1:1 inverse sides, important for `factory.mergeData`
559
+ lines.push(`if (options?.includeInverseSides) {`);
560
+ for (const prop of meta.bidirectionalRelations) {
561
+ if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && prop.hydrate !== false) {
562
+ lines.push(this.getPropertyComparator(prop, context));
563
+ }
564
+ }
565
+ lines.push(`}`);
521
566
  const code = `// compiled comparator for entity ${meta.className}\n`
522
- + `return function(last, current) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
567
+ + `return function(last, current, options) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
523
568
  const comparator = Utils.createFunction(context, code);
524
569
  this.comparators.set(entityName, comparator);
525
570
  return comparator;
526
571
  }
527
572
  getGenericComparator(prop, cond) {
528
- return ` if (current${prop} == null && last${prop} == null) {\n\n` +
573
+ return ` if (current${prop} === null && last${prop} === undefined) {\n` +
574
+ ` diff${prop} = current${prop};\n` +
575
+ ` } else if (current${prop} == null && last${prop} == null) {\n\n` +
529
576
  ` } else if ((current${prop} != null && last${prop} == null) || (current${prop} == null && last${prop} != null)) {\n` +
530
577
  ` diff${prop} = current${prop};\n` +
531
578
  ` } else if (${cond}) {\n` +
@@ -591,7 +638,7 @@ export class EntityComparator {
591
638
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
592
639
  */
593
640
  static isComparable(prop, root) {
594
- const virtual = prop.persist === false || prop.generated;
641
+ const virtual = prop.persist === false || (prop.generated && !prop.primary);
595
642
  const inverse = prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
596
643
  const discriminator = prop.name === root.discriminatorColumn;
597
644
  const collection = prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY;
@@ -1,13 +1,21 @@
1
1
  import type { Dictionary, EntityMetadata, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
3
  import type { MetadataStorage } from '../metadata/MetadataStorage.js';
4
+ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
5
+ /** @internal */
4
6
  export declare class QueryHelper {
5
7
  static readonly SUPPORTED_OPERATORS: string[];
6
8
  static processParams(params: unknown): any;
7
9
  static processObjectParams<T extends object>(params?: T): T;
10
+ /**
11
+ * converts `{ account: { $or: [ [Object], [Object] ] } }`
12
+ * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
13
+ */
14
+ static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
8
15
  static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
9
16
  static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
10
- static getActiveFilters(entityName: string, options: Dictionary<boolean | Dictionary> | string[] | boolean, filters: Dictionary<FilterDef>): FilterDef[];
17
+ static getActiveFilters(entityName: string, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
18
+ static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
11
19
  static isFilterActive(entityName: string, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
12
20
  static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
13
21
  private static isSupportedOperator;
@@ -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
  }
@@ -45,9 +87,9 @@ export class QueryHelper {
45
87
  return false;
46
88
  }
47
89
  if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
48
- return !!key && !GroupOperator[key] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
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') {
@@ -159,6 +202,24 @@ export class QueryHelper {
159
202
  return filters[f];
160
203
  });
161
204
  }
205
+ static mergePropertyFilters(propFilters, options) {
206
+ if (!options || !propFilters || options === true || propFilters === true) {
207
+ return options ?? propFilters;
208
+ }
209
+ if (Array.isArray(propFilters)) {
210
+ propFilters = propFilters.reduce((o, item) => {
211
+ o[item] = true;
212
+ return o;
213
+ }, {});
214
+ }
215
+ if (Array.isArray(options)) {
216
+ options = options.reduce((o, item) => {
217
+ o[item] = true;
218
+ return o;
219
+ }, {});
220
+ }
221
+ return Utils.mergeConfig({}, propFilters, options);
222
+ }
162
223
  static isFilterActive(entityName, filterName, filter, options) {
163
224
  if (filter.entity && !filter.entity.includes(entityName)) {
164
225
  return false;
@@ -10,8 +10,6 @@ export declare class RawQueryFragment {
10
10
  valueOf(): string;
11
11
  toJSON(): string;
12
12
  toString(): string;
13
- /** @internal */
14
- assign(): void;
15
13
  clone(): RawQueryFragment;
16
14
  static run<T>(cb: (...args: any[]) => Promise<T>): Promise<T>;
17
15
  /**
@@ -72,8 +70,26 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
72
70
  * ```ts
73
71
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
74
72
  * ```
73
+ *
74
+ * 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:
75
+ *
76
+ * ```ts
77
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
78
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
79
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
80
+ * @Entity({ schema: 'library' })
81
+ * export class Author { ... }
82
+ * ```
83
+ *
84
+ * 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:
85
+ *
86
+ * ```ts
87
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
88
+ * @Entity({ schema: 'library' })
89
+ * export class Author { ... }
90
+ * ```
75
91
  */
76
- 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;
92
+ 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>): NoInfer<R>;
77
93
  /**
78
94
  * Alternative to the `raw()` helper allowing to use it as a tagged template function for the simple cases.
79
95
  *
@@ -90,9 +106,25 @@ export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> |
90
106
  */
91
107
  export declare function sql(sql: readonly string[], ...values: unknown[]): any;
92
108
  export declare namespace sql {
93
- var ref: <T extends object>(...keys: string[]) => RawQueryFragment;
109
+ var ref: <T extends object>(...keys: string[]) => NoInfer<RawQueryFragment>;
94
110
  var now: (length?: number) => string;
95
111
  var lower: <T extends object>(key: string | ((alias: string) => string)) => string;
96
112
  var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
97
113
  }
98
114
  export declare function createSqlFunction<T extends object, R = string>(func: string, key: string | ((alias: string) => string)): R;
115
+ /**
116
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
117
+ *
118
+ * 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.
119
+ *
120
+ * ```ts
121
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
122
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
123
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
124
+ * @Entity({ schema: 'library' })
125
+ * export class Author { ... }
126
+ * ```
127
+ */
128
+ export declare function quote(expParts: readonly string[], ...values: (string | {
129
+ toString(): string;
130
+ })[]): any;