@mikro-orm/core 6.4.17-dev.5 → 6.4.17-dev.51

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 (45) hide show
  1. package/EntityManager.d.ts +7 -0
  2. package/EntityManager.js +18 -8
  3. package/decorators/Indexed.d.ts +2 -2
  4. package/decorators/ManyToMany.d.ts +2 -0
  5. package/decorators/ManyToOne.d.ts +2 -0
  6. package/decorators/OneToOne.d.ts +2 -0
  7. package/decorators/Transactional.d.ts +1 -0
  8. package/decorators/Transactional.js +3 -3
  9. package/drivers/IDatabaseDriver.d.ts +2 -0
  10. package/entity/ArrayCollection.d.ts +3 -1
  11. package/entity/ArrayCollection.js +7 -2
  12. package/entity/EntityFactory.d.ts +6 -0
  13. package/entity/EntityFactory.js +12 -1
  14. package/entity/EntityHelper.js +4 -1
  15. package/entity/EntityLoader.js +16 -14
  16. package/entity/Reference.d.ts +5 -0
  17. package/entity/Reference.js +16 -0
  18. package/entity/WrappedEntity.js +1 -1
  19. package/entity/defineEntity.d.ts +528 -0
  20. package/entity/defineEntity.js +684 -0
  21. package/entity/index.d.ts +1 -0
  22. package/entity/index.js +1 -0
  23. package/hydration/ObjectHydrator.js +1 -1
  24. package/index.d.ts +1 -1
  25. package/index.mjs +2 -0
  26. package/metadata/EntitySchema.js +10 -2
  27. package/metadata/MetadataDiscovery.d.ts +0 -1
  28. package/metadata/MetadataDiscovery.js +7 -11
  29. package/package.json +3 -3
  30. package/platforms/Platform.d.ts +3 -1
  31. package/typings.d.ts +16 -8
  32. package/typings.js +10 -2
  33. package/unit-of-work/ChangeSetComputer.js +3 -1
  34. package/unit-of-work/UnitOfWork.d.ts +1 -0
  35. package/unit-of-work/UnitOfWork.js +17 -10
  36. package/utils/Configuration.d.ts +6 -0
  37. package/utils/Configuration.js +1 -0
  38. package/utils/ConfigurationLoader.js +1 -1
  39. package/utils/EntityComparator.js +6 -3
  40. package/utils/QueryHelper.js +3 -3
  41. package/utils/RawQueryFragment.d.ts +34 -0
  42. package/utils/RawQueryFragment.js +35 -0
  43. package/utils/Utils.d.ts +2 -2
  44. package/utils/Utils.js +24 -5
  45. package/utils/upsert-utils.js +9 -1
package/entity/index.js CHANGED
@@ -27,3 +27,4 @@ __exportStar(require("./Reference"), exports);
27
27
  __exportStar(require("./BaseEntity"), exports);
28
28
  __exportStar(require("./WrappedEntity"), exports);
29
29
  __exportStar(require("./wrap"), exports);
30
+ __exportStar(require("./defineEntity"), exports);
@@ -65,7 +65,7 @@ class ObjectHydrator extends Hydrator_1.Hydrator {
65
65
  const ret = [];
66
66
  const idx = this.tmpIndex++;
67
67
  const nullVal = this.config.get('forceUndefined') ? 'undefined' : 'null';
68
- if (prop.getter && !prop.setter) {
68
+ if (prop.getter && !prop.setter && prop.persist === false) {
69
69
  return [];
70
70
  }
71
71
  if (prop.ref) {
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @packageDocumentation
3
3
  * @module core
4
4
  */
5
- export { Constructor, ConnectionType, Dictionary, PrimaryKeyProp, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, EntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions, FilterObject, IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, EagerProps, HiddenProps, RequiredEntityData, CheckCallback, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, UmzugMigration, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, Opt, EntityType, FromEntityType, Selected, IsSubset, NoInfer, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, Hidden, FilterValue, MergeLoaded, MergeSelected, Config, DefineConfig, TypeConfig, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, } from './typings';
5
+ export { Constructor, ConnectionType, Dictionary, PrimaryKeyProp, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, EntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions, FilterObject, IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, EagerProps, HiddenProps, RequiredEntityData, CheckCallback, IndexCallback, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, UmzugMigration, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, Opt, EntityType, FromEntityType, Selected, IsSubset, NoInfer, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, Hidden, FilterValue, MergeLoaded, MergeSelected, Config, DefineConfig, TypeConfig, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, } from './typings';
6
6
  export * from './enums';
7
7
  export * from './errors';
8
8
  export * from './exceptions';
package/index.mjs CHANGED
@@ -182,11 +182,13 @@ export const compareBuffers = mod.compareBuffers;
182
182
  export const compareObjects = mod.compareObjects;
183
183
  export const createSqlFunction = mod.createSqlFunction;
184
184
  export const defineConfig = mod.defineConfig;
185
+ export const defineEntity = mod.defineEntity;
185
186
  export const equals = mod.equals;
186
187
  export const getOnConflictFields = mod.getOnConflictFields;
187
188
  export const getOnConflictReturningFields = mod.getOnConflictReturningFields;
188
189
  export const helper = mod.helper;
189
190
  export const parseJsonSafe = mod.parseJsonSafe;
191
+ export const quote = mod.quote;
190
192
  export const raw = mod.raw;
191
193
  export const ref = mod.ref;
192
194
  export const rel = mod.rel;
@@ -102,6 +102,8 @@ class EntitySchema {
102
102
  if (prop.fieldNames && !prop.joinColumns) {
103
103
  prop.joinColumns = prop.fieldNames;
104
104
  }
105
+ // By default, the foreign key constraint is created on the relation
106
+ utils_1.Utils.defaultValue(prop, 'createForeignKeyConstraint', true);
105
107
  this.addProperty(name, type, prop);
106
108
  }
107
109
  addManyToMany(name, type, options) {
@@ -111,6 +113,8 @@ class EntitySchema {
111
113
  }
112
114
  if (options.owner) {
113
115
  utils_1.Utils.renameKey(options, 'mappedBy', 'inversedBy');
116
+ // By default, the foreign key constraint is created on the relation
117
+ utils_1.Utils.defaultValue(options, 'createForeignKeyConstraint', true);
114
118
  }
115
119
  const prop = this.createProperty(enums_1.ReferenceKind.MANY_TO_MANY, options);
116
120
  this.addProperty(name, type, prop);
@@ -123,8 +127,12 @@ class EntitySchema {
123
127
  const prop = this.createProperty(enums_1.ReferenceKind.ONE_TO_ONE, options);
124
128
  utils_1.Utils.defaultValue(prop, 'owner', !!prop.inversedBy || !prop.mappedBy);
125
129
  utils_1.Utils.defaultValue(prop, 'unique', prop.owner);
126
- if (prop.owner && options.mappedBy) {
127
- utils_1.Utils.renameKey(prop, 'mappedBy', 'inversedBy');
130
+ if (prop.owner) {
131
+ if (options.mappedBy) {
132
+ utils_1.Utils.renameKey(prop, 'mappedBy', 'inversedBy');
133
+ }
134
+ // By default, the foreign key constraint is created on the relation
135
+ utils_1.Utils.defaultValue(prop, 'createForeignKeyConstraint', true);
128
136
  }
129
137
  if (prop.joinColumns && !prop.fieldNames) {
130
138
  prop.fieldNames = prop.joinColumns;
@@ -54,7 +54,6 @@ export declare class MetadataDiscovery {
54
54
  private initAutoincrement;
55
55
  private initCheckConstraints;
56
56
  private initGeneratedColumn;
57
- private createColumnMappingObject;
58
57
  private getDefaultVersionValue;
59
58
  private inferDefaultValue;
60
59
  private initDefaultValue;
@@ -657,6 +657,7 @@ class MetadataDiscovery {
657
657
  autoincrement: false,
658
658
  updateRule: prop.updateRule,
659
659
  deleteRule: prop.deleteRule,
660
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
660
661
  };
661
662
  if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
662
663
  ret.updateRule ??= 'no action';
@@ -968,7 +969,7 @@ class MetadataDiscovery {
968
969
  }
969
970
  }
970
971
  initCheckConstraints(meta) {
971
- const map = this.createColumnMappingObject(meta);
972
+ const map = meta.createColumnMappingObject();
972
973
  for (const check of meta.checks) {
973
974
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
974
975
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -991,19 +992,11 @@ class MetadataDiscovery {
991
992
  }
992
993
  return;
993
994
  }
994
- const map = this.createColumnMappingObject(meta);
995
+ const map = meta.createColumnMappingObject();
995
996
  if (prop.generated instanceof Function) {
996
997
  prop.generated = prop.generated(map);
997
998
  }
998
999
  }
999
- createColumnMappingObject(meta) {
1000
- return Object.values(meta.properties).reduce((o, prop) => {
1001
- if (prop.fieldNames) {
1002
- o[prop.name] = prop.fieldNames[0];
1003
- }
1004
- return o;
1005
- }, {});
1006
- }
1007
1000
  getDefaultVersionValue(meta, prop) {
1008
1001
  if (typeof prop.defaultRaw !== 'undefined') {
1009
1002
  return prop.defaultRaw;
@@ -1057,7 +1050,7 @@ class MetadataDiscovery {
1057
1050
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1058
1051
  return;
1059
1052
  }
1060
- if (prop.customType instanceof types_1.ArrayType && Array.isArray(prop.default)) {
1053
+ if (Array.isArray(prop.default) && prop.customType) {
1061
1054
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1062
1055
  }
1063
1056
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1111,6 +1104,9 @@ class MetadataDiscovery {
1111
1104
  if (prop.kind === enums_1.ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1112
1105
  prop.customType = new types_1.JsonType();
1113
1106
  }
1107
+ if (prop.kind === enums_1.ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1108
+ prop.customType = new types_1.JsonType();
1109
+ }
1114
1110
  if (!prop.customType && prop.array && prop.items) {
1115
1111
  prop.customType = new types_1.EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1116
1112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "6.4.17-dev.5",
3
+ "version": "6.4.17-dev.51",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -60,11 +60,11 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "dataloader": "2.2.3",
63
- "dotenv": "16.5.0",
63
+ "dotenv": "17.0.1",
64
64
  "esprima": "4.0.1",
65
65
  "fs-extra": "11.3.0",
66
66
  "globby": "11.1.0",
67
- "mikro-orm": "6.4.17-dev.5",
67
+ "mikro-orm": "6.4.17-dev.51",
68
68
  "reflect-metadata": "0.2.2"
69
69
  }
70
70
  }
@@ -173,7 +173,9 @@ export declare abstract class Platform {
173
173
  getExtension<T>(extensionName: string, extensionKey: string, moduleName: string, em: EntityManager): T;
174
174
  getSchemaGenerator(driver: IDatabaseDriver, em?: EntityManager): ISchemaGenerator;
175
175
  processDateProperty(value: unknown): string | number | Date;
176
- quoteIdentifier(id: string, quote?: string): string;
176
+ quoteIdentifier(id: string | {
177
+ toString: () => string;
178
+ }, quote?: string): string;
177
179
  quoteValue(value: any): string;
178
180
  escape(value: any): string;
179
181
  formatQuery(sql: string, params: readonly any[]): string;
package/typings.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { SerializationContext, SerializeOptions } from './serialization';
6
6
  import type { EntitySchema, MetadataStorage } from './metadata';
7
7
  import type { Type, types } from './types';
8
8
  import type { Platform } from './platforms';
9
- import type { Configuration } from './utils';
9
+ import type { Configuration, RawQueryFragment } from './utils';
10
10
  import type { EntityManager } from './EntityManager';
11
11
  import type { EmbeddedPrefixMode } from './decorators/Embedded';
12
12
  import type { EventSubscriber } from './events';
@@ -287,8 +287,14 @@ export type EntityDTO<T, C extends TypeConfig = never> = {
287
287
  } & {
288
288
  [K in keyof T as DTOOptionalKeys<T, K>]?: EntityDTOProp<T, T[K], C> | AddOptional<T[K]>;
289
289
  };
290
- type CheckKey<T> = IsUnknown<T> extends false ? keyof T : string;
291
- export type CheckCallback<T> = (columns: Record<CheckKey<T>, string>) => string;
290
+ type PropertyName<T> = IsUnknown<T> extends false ? keyof T : string;
291
+ type TableName = {
292
+ name: string;
293
+ schema?: string;
294
+ toString: () => string;
295
+ };
296
+ export type IndexCallback<T> = (table: TableName, columns: Record<PropertyName<T>, string>) => string | RawQueryFragment;
297
+ export type CheckCallback<T> = (columns: Record<PropertyName<T>, string>) => string;
292
298
  export type GeneratedColumnCallback<T> = (columns: Record<keyof T, string>) => string;
293
299
  export interface CheckConstraint<T = any> {
294
300
  name?: string;
@@ -384,6 +390,7 @@ export interface EntityProperty<Owner = any, Target = any> {
384
390
  optional?: boolean;
385
391
  ignoreSchemaChanges?: ('type' | 'extra' | 'default')[];
386
392
  deferMode?: DeferMode;
393
+ createForeignKeyConstraint: boolean;
387
394
  foreignKeyName?: string;
388
395
  }
389
396
  export declare class EntityMetadata<T = any> {
@@ -395,6 +402,7 @@ export declare class EntityMetadata<T = any> {
395
402
  removeProperty(name: string, sync?: boolean): void;
396
403
  getPrimaryProps(): EntityProperty<T>[];
397
404
  getPrimaryProp(): EntityProperty<T>;
405
+ createColumnMappingObject(): Dictionary<any>;
398
406
  get tableName(): string;
399
407
  set tableName(name: string);
400
408
  sync(initIndexes?: boolean): void;
@@ -446,18 +454,18 @@ export interface EntityMetadata<T = any> {
446
454
  uniqueProps: EntityProperty<T>[];
447
455
  getterProps: EntityProperty<T>[];
448
456
  indexes: {
449
- properties: EntityKey<T> | EntityKey<T>[];
457
+ properties?: EntityKey<T> | EntityKey<T>[];
450
458
  name?: string;
451
459
  type?: string;
452
460
  options?: Dictionary;
453
- expression?: string;
461
+ expression?: string | IndexCallback<T>;
454
462
  }[];
455
463
  uniques: {
456
- properties: EntityKey<T> | EntityKey<T>[];
464
+ properties?: EntityKey<T> | EntityKey<T>[];
457
465
  name?: string;
458
466
  options?: Dictionary;
459
- expression?: string;
460
- deferMode?: DeferMode;
467
+ expression?: string | IndexCallback<T>;
468
+ deferMode?: DeferMode | `${DeferMode}`;
461
469
  }[];
462
470
  checks: CheckConstraint<T>[];
463
471
  repositoryClass?: string;
package/typings.js CHANGED
@@ -53,6 +53,14 @@ class EntityMetadata {
53
53
  getPrimaryProp() {
54
54
  return this.properties[this.primaryKeys[0]];
55
55
  }
56
+ createColumnMappingObject() {
57
+ return Object.values(this.properties).reduce((o, prop) => {
58
+ if (prop.fieldNames) {
59
+ o[prop.name] = prop.fieldNames[0];
60
+ }
61
+ return o;
62
+ }, {});
63
+ }
56
64
  get tableName() {
57
65
  return this.collection;
58
66
  }
@@ -72,7 +80,7 @@ class EntityMetadata {
72
80
  // `prop.userDefined` is either `undefined` or `false`
73
81
  const discriminator = this.root.discriminatorColumn === prop.name && prop.userDefined === false;
74
82
  // even if we don't have a setter, do not ignore value from database!
75
- const onlyGetter = prop.getter && !prop.setter;
83
+ const onlyGetter = prop.getter && !prop.setter && prop.persist === false;
76
84
  return !prop.inherited && prop.hydrate !== false && !discriminator && !prop.embedded && !onlyGetter;
77
85
  });
78
86
  this.trackingProps = this.hydrateProps
@@ -115,7 +123,7 @@ class EntityMetadata {
115
123
  wrapped.__data[prop.name] = entity_1.Reference.wrapReference(val, prop);
116
124
  // when propagation from inside hydration, we set the FK to the entity data immediately
117
125
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
118
- wrapped.__originalEntityData[prop.name] = Utils_1.Utils.getPrimaryKeyValues(val, prop.targetMeta.primaryKeys, true);
126
+ wrapped.__originalEntityData[prop.name] = Utils_1.Utils.getPrimaryKeyValues(val, prop.targetMeta, true);
119
127
  }
120
128
  else {
121
129
  wrapped.__touched = !hydrator.isRunning();
@@ -144,7 +144,9 @@ class ChangeSetComputer {
144
144
  if (!target.isDirty() && changeSet.type !== ChangeSet_1.ChangeSetType.CREATE) {
145
145
  return;
146
146
  }
147
- this.collectionUpdates.add(target);
147
+ if (target.isDirty()) {
148
+ this.collectionUpdates.add(target);
149
+ }
148
150
  if (prop.owner && !this.platform.usesPivotTable()) {
149
151
  changeSet.payload[prop.name] = target.getItems(false).map((item) => item.__helper.__identifier ?? item.__helper.getPrimaryKey());
150
152
  }
@@ -102,6 +102,7 @@ export declare class UnitOfWork {
102
102
  private commitDeleteChangeSets;
103
103
  private commitExtraUpdates;
104
104
  private commitCollectionUpdates;
105
+ private filterCollectionUpdates;
105
106
  /**
106
107
  * Orders change sets so FK constrains are maintained, ensures stable order (needed for node < 11)
107
108
  */
@@ -88,7 +88,7 @@ class UnitOfWork {
88
88
  }
89
89
  wrapped.__loadedProperties.add(key);
90
90
  if ([enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils_1.Utils.isPlainObject(data[prop.name])) {
91
- data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
91
+ data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
92
92
  }
93
93
  else if (prop.kind === enums_1.ReferenceKind.EMBEDDED && !prop.object && Utils_1.Utils.isPlainObject(data[prop.name])) {
94
94
  for (const p of prop.targetMeta.props) {
@@ -96,7 +96,7 @@ class UnitOfWork {
96
96
  const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
97
97
  data[prefix + p.name] = data[prop.name][p.name];
98
98
  }
99
- data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
99
+ data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
100
100
  }
101
101
  if (forceUndefined) {
102
102
  if (data[key] === null) {
@@ -303,6 +303,7 @@ class UnitOfWork {
303
303
  cs.entity.__helper.__processing = true;
304
304
  }
305
305
  await this.eventManager.dispatchEvent(enums_1.EventType.onFlush, { em: this.em, uow: this });
306
+ this.filterCollectionUpdates();
306
307
  // nothing to do, do not start transaction
307
308
  if (this.changeSets.size === 0 && this.collectionUpdates.size === 0 && this.extraUpdates.size === 0) {
308
309
  return void await this.eventManager.dispatchEvent(enums_1.EventType.afterFlush, { em: this.em, uow: this });
@@ -886,23 +887,29 @@ class UnitOfWork {
886
887
  }
887
888
  }
888
889
  async commitCollectionUpdates(ctx) {
889
- const collectionUpdates = [];
890
+ this.filterCollectionUpdates();
891
+ await this.em.getDriver().syncCollections(this.collectionUpdates, { ctx, schema: this.em.schema });
890
892
  for (const coll of this.collectionUpdates) {
893
+ coll.takeSnapshot();
894
+ }
895
+ }
896
+ filterCollectionUpdates() {
897
+ for (const coll of this.collectionUpdates) {
898
+ let skip = true;
891
899
  if (coll.property.owner || coll.getItems(false).filter(item => !item.__helper.__initialized).length > 0) {
892
900
  if (this.platform.usesPivotTable()) {
893
- collectionUpdates.push(coll);
901
+ skip = false;
894
902
  }
895
903
  }
896
904
  else if (coll.property.kind === enums_1.ReferenceKind.ONE_TO_MANY && coll.getSnapshot() === undefined) {
897
- collectionUpdates.push(coll);
905
+ skip = false;
898
906
  }
899
907
  else if (coll.property.kind === enums_1.ReferenceKind.MANY_TO_MANY && !coll.property.owner) {
900
- collectionUpdates.push(coll);
908
+ skip = false;
909
+ }
910
+ if (skip) {
911
+ this.collectionUpdates.delete(coll);
901
912
  }
902
- }
903
- await this.em.getDriver().syncCollections(collectionUpdates, { ctx, schema: this.em.schema });
904
- for (const coll of this.collectionUpdates) {
905
- coll.takeSnapshot();
906
913
  }
907
914
  }
908
915
  /**
@@ -77,6 +77,7 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
77
77
  upsertManaged: true;
78
78
  forceEntityConstructor: false;
79
79
  forceUndefined: false;
80
+ processOnCreateHooksEarly: false;
80
81
  ensureDatabase: true;
81
82
  ensureIndexes: false;
82
83
  batchSize: number;
@@ -345,6 +346,11 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver, EM
345
346
  upsertManaged: boolean;
346
347
  forceEntityConstructor: boolean | (Constructor<AnyEntity> | string)[];
347
348
  forceUndefined: boolean;
349
+ /**
350
+ * Property `onCreate` hooks are normally executed during `flush` operation.
351
+ * With this option, they will be processed early inside `em.create()` method.
352
+ */
353
+ processOnCreateHooksEarly: boolean;
348
354
  forceUtcTimezone?: boolean;
349
355
  timezone?: string;
350
356
  ensureDatabase: boolean | EnsureDatabaseOptions;
@@ -71,6 +71,7 @@ class Configuration {
71
71
  upsertManaged: true,
72
72
  forceEntityConstructor: false,
73
73
  forceUndefined: false,
74
+ processOnCreateHooksEarly: false,
74
75
  ensureDatabase: true,
75
76
  ensureIndexes: false,
76
77
  batchSize: 300,
@@ -213,7 +213,7 @@ class ConfigurationLoader {
213
213
  const baseDir = options instanceof Configuration_1.Configuration ? options.get('baseDir') : options?.baseDir;
214
214
  const path = process.env.MIKRO_ORM_ENV ?? ((baseDir ?? process.cwd()) + '/.env');
215
215
  const env = {};
216
- dotenv_1.default.config({ path, processEnv: env });
216
+ dotenv_1.default.config({ path, processEnv: env, quiet: true });
217
217
  // only propagate known env vars
218
218
  for (const key of Object.keys(env)) {
219
219
  if (key.startsWith('MIKRO_ORM_')) {
@@ -534,9 +534,12 @@ class EntityComparator {
534
534
  context.set('compareBuffers', Utils_1.compareBuffers);
535
535
  context.set('compareObjects', Utils_1.compareObjects);
536
536
  context.set('equals', Utils_1.equals);
537
- meta.comparableProps.forEach(prop => {
538
- lines.push(this.getPropertyComparator(prop, context));
539
- });
537
+ for (const prop of meta.comparableProps) {
538
+ // skip properties that are not hydrated
539
+ if (prop.hydrate !== false) {
540
+ lines.push(this.getPropertyComparator(prop, context));
541
+ }
542
+ }
540
543
  const code = `// compiled comparator for entity ${meta.className}\n`
541
544
  + `return function(last, current) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
542
545
  const comparator = Utils_1.Utils.createFunction(context, code);
@@ -40,7 +40,7 @@ class QueryHelper {
40
40
  if (Array.isArray(where)) {
41
41
  where.forEach((item, i) => {
42
42
  if (this.inlinePrimaryKeyObjects(item, meta, metadata, key)) {
43
- where[i] = Utils_1.Utils.getPrimaryKeyValues(item, meta.primaryKeys, false);
43
+ where[i] = Utils_1.Utils.getPrimaryKeyValues(item, meta, false);
44
44
  }
45
45
  });
46
46
  }
@@ -61,7 +61,7 @@ class QueryHelper {
61
61
  Object.keys(where).forEach(k => {
62
62
  const meta2 = metadata.find(meta.properties[k]?.type) || meta;
63
63
  if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
64
- where[k] = Utils_1.Utils.getPrimaryKeyValues(where[k], meta2.primaryKeys, true);
64
+ where[k] = Utils_1.Utils.getPrimaryKeyValues(where[k], meta2, true);
65
65
  }
66
66
  });
67
67
  return false;
@@ -121,7 +121,7 @@ class QueryHelper {
121
121
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
122
122
  }
123
123
  const isJsonProperty = prop?.customType instanceof JsonType_1.JsonType && Utils_1.Utils.isPlainObject(value) && !platform.isRaw(value) && Object.keys(value)[0] !== '$eq';
124
- if (isJsonProperty) {
124
+ if (isJsonProperty && prop?.kind !== enums_1.ReferenceKind.EMBEDDED) {
125
125
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
126
126
  }
127
127
  if (Array.isArray(value) && !Utils_1.Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
@@ -70,6 +70,24 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
70
70
  * ```ts
71
71
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
72
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
+ * ```
73
91
  */
74
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>): R;
75
93
  /**
@@ -94,3 +112,19 @@ export declare namespace sql {
94
112
  var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
95
113
  }
96
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;
@@ -4,6 +4,7 @@ exports.ALIAS_REPLACEMENT_RE = exports.ALIAS_REPLACEMENT = exports.RawQueryFragm
4
4
  exports.raw = raw;
5
5
  exports.sql = sql;
6
6
  exports.createSqlFunction = createSqlFunction;
7
+ exports.quote = quote;
7
8
  const node_async_hooks_1 = require("node:async_hooks");
8
9
  const node_util_1 = require("node:util");
9
10
  const Utils_1 = require("./Utils");
@@ -150,6 +151,24 @@ exports.ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
150
151
  * ```ts
151
152
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
152
153
  * ```
154
+ *
155
+ * 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:
156
+ *
157
+ * ```ts
158
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
159
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
160
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
161
+ * @Entity({ schema: 'library' })
162
+ * export class Author { ... }
163
+ * ```
164
+ *
165
+ * 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:
166
+ *
167
+ * ```ts
168
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
169
+ * @Entity({ schema: 'library' })
170
+ * export class Author { ... }
171
+ * ```
153
172
  */
154
173
  function raw(sql, params) {
155
174
  if (sql instanceof RawQueryFragment) {
@@ -207,3 +226,19 @@ sql.ref = (...keys) => raw('??', [keys.join('.')]);
207
226
  sql.now = (length) => raw('current_timestamp' + (length == null ? '' : `(${length})`));
208
227
  sql.lower = (key) => createSqlFunction('lower', key);
209
228
  sql.upper = (key) => createSqlFunction('upper', key);
229
+ /**
230
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
231
+ *
232
+ * 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.
233
+ *
234
+ * ```ts
235
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
236
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
237
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
238
+ * @Entity({ schema: 'library' })
239
+ * export class Author { ... }
240
+ * ```
241
+ */
242
+ function quote(expParts, ...values) {
243
+ return raw(expParts.join('??'), values);
244
+ }
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
@@ -498,6 +498,7 @@ class Utils {
498
498
  static splitPrimaryKeys(key) {
499
499
  return key.split(this.PK_SEPARATOR);
500
500
  }
501
+ // TODO v7: remove support for `primaryKeys: string[]`
501
502
  static getPrimaryKeyValues(entity, primaryKeys, allowScalar = false, convertCustomTypes = false) {
502
503
  /* istanbul ignore next */
503
504
  if (entity == null) {
@@ -509,9 +510,24 @@ class Utils {
509
510
  }
510
511
  return val;
511
512
  }
512
- const pk = Utils.isEntity(entity, true)
513
- ? (0, wrap_1.helper)(entity).getPrimaryKey(convertCustomTypes)
514
- : primaryKeys.reduce((o, pk) => { o[pk] = entity[pk]; return o; }, {});
513
+ const meta = Array.isArray(primaryKeys) ? undefined : primaryKeys;
514
+ primaryKeys = Array.isArray(primaryKeys) ? primaryKeys : meta.primaryKeys;
515
+ let pk;
516
+ if (Utils.isEntity(entity, true)) {
517
+ pk = (0, wrap_1.helper)(entity).getPrimaryKey(convertCustomTypes);
518
+ }
519
+ else {
520
+ pk = primaryKeys.reduce((o, pk) => {
521
+ const targetMeta = meta?.properties[pk].targetMeta;
522
+ if (targetMeta && Utils.isPlainObject(entity[pk])) {
523
+ o[pk] = Utils.getPrimaryKeyValues(entity[pk], targetMeta, allowScalar, convertCustomTypes);
524
+ }
525
+ else {
526
+ o[pk] = entity[pk];
527
+ }
528
+ return o;
529
+ }, {});
530
+ }
515
531
  if (primaryKeys.length > 1) {
516
532
  return toArray(pk);
517
533
  }
@@ -561,7 +577,7 @@ class Utils {
561
577
  return o;
562
578
  }, {});
563
579
  }
564
- static getOrderedPrimaryKeys(id, meta, platform, convertCustomTypes = false) {
580
+ static getOrderedPrimaryKeys(id, meta, platform, convertCustomTypes = false, allowScalar = false) {
565
581
  const data = (Utils.isPrimaryKey(id) ? { [meta.primaryKeys[0]]: id } : id);
566
582
  const pks = meta.primaryKeys.map((pk, idx) => {
567
583
  const prop = meta.properties[pk];
@@ -571,11 +587,14 @@ class Utils {
571
587
  value = prop.customType.convertToJSValue(value, platform);
572
588
  }
573
589
  if (prop.kind !== enums_1.ReferenceKind.SCALAR && prop.targetMeta) {
574
- const value2 = this.getOrderedPrimaryKeys(value, prop.targetMeta, platform, convertCustomTypes);
590
+ const value2 = this.getOrderedPrimaryKeys(value, prop.targetMeta, platform, convertCustomTypes, allowScalar);
575
591
  value = value2.length > 1 ? value2 : value2[0];
576
592
  }
577
593
  return value;
578
594
  });
595
+ if (allowScalar && pks.length === 1) {
596
+ return pks[0];
597
+ }
579
598
  // we need to flatten the PKs as composite PKs can be build from another composite PKs
580
599
  // and this method is used to get the PK hash in identity map, that expects flat array
581
600
  return Utils.flatten(pks);
@@ -75,7 +75,15 @@ function getOnConflictReturningFields(meta, data, uniqueFields, options) {
75
75
  if (!meta) {
76
76
  return '*';
77
77
  }
78
- const keys = meta.comparableProps.filter(p => !p.lazy && !p.embeddable && Array.isArray(uniqueFields) && !uniqueFields.includes(p.name)).map(p => p.name);
78
+ const keys = meta.comparableProps.filter(p => {
79
+ if (p.lazy || p.embeddable) {
80
+ return false;
81
+ }
82
+ if (p.autoincrement) {
83
+ return true;
84
+ }
85
+ return Array.isArray(uniqueFields) && !uniqueFields.includes(p.name);
86
+ }).map(p => p.name);
79
87
  if (meta.versionProperty) {
80
88
  keys.push(meta.versionProperty);
81
89
  }