@mikro-orm/core 7.1.0-dev.1 → 7.1.0-dev.10

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 (46) hide show
  1. package/EntityManager.d.ts +63 -11
  2. package/EntityManager.js +163 -18
  3. package/drivers/DatabaseDriver.d.ts +1 -0
  4. package/drivers/DatabaseDriver.js +3 -0
  5. package/drivers/IDatabaseDriver.d.ts +30 -1
  6. package/entity/BaseEntity.d.ts +2 -2
  7. package/entity/Collection.d.ts +6 -3
  8. package/entity/Collection.js +15 -4
  9. package/entity/EntityFactory.js +20 -1
  10. package/entity/EntityLoader.d.ts +3 -1
  11. package/entity/EntityLoader.js +39 -16
  12. package/entity/EntityRepository.d.ts +27 -9
  13. package/entity/EntityRepository.js +12 -0
  14. package/entity/WrappedEntity.d.ts +2 -2
  15. package/entity/defineEntity.d.ts +40 -9
  16. package/entity/defineEntity.js +0 -6
  17. package/entity/utils.js +4 -5
  18. package/errors.d.ts +2 -0
  19. package/errors.js +4 -0
  20. package/index.d.ts +2 -2
  21. package/index.js +1 -1
  22. package/metadata/MetadataDiscovery.d.ts +11 -0
  23. package/metadata/MetadataDiscovery.js +127 -9
  24. package/metadata/MetadataValidator.js +9 -0
  25. package/metadata/types.d.ts +10 -2
  26. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  27. package/naming-strategy/NamingStrategy.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/platforms/Platform.d.ts +5 -0
  30. package/platforms/Platform.js +7 -0
  31. package/serialization/EntitySerializer.d.ts +18 -6
  32. package/serialization/EntitySerializer.js +31 -5
  33. package/typings.d.ts +108 -6
  34. package/typings.js +19 -2
  35. package/utils/AbstractMigrator.d.ts +11 -1
  36. package/utils/AbstractMigrator.js +203 -0
  37. package/utils/Configuration.d.ts +7 -0
  38. package/utils/Configuration.js +1 -0
  39. package/utils/DataloaderUtils.d.ts +10 -1
  40. package/utils/DataloaderUtils.js +78 -0
  41. package/utils/QueryHelper.d.ts +16 -0
  42. package/utils/QueryHelper.js +15 -0
  43. package/utils/Utils.d.ts +1 -1
  44. package/utils/Utils.js +1 -1
  45. package/utils/fs-utils.d.ts +2 -0
  46. package/utils/fs-utils.js +7 -1
@@ -316,11 +316,30 @@ export class EntityFactory {
316
316
  else if (!onCreateOnly && prop.default != null && !isRaw(prop.default) && entity[prop.name] === undefined) {
317
317
  entity[prop.name] = prop.default;
318
318
  }
319
+ else if (!onCreateOnly &&
320
+ this.#config.get('initNullableProperties') &&
321
+ prop.nullable &&
322
+ prop.default == null &&
323
+ !prop.defaultRaw &&
324
+ entity[prop.name] === undefined) {
325
+ entity[prop.name] = (this.#config.get('forceUndefined') ? undefined : null);
326
+ }
319
327
  if (prop.kind === ReferenceKind.EMBEDDED && entity[prop.name]) {
320
328
  const items = prop.array ? entity[prop.name] : [entity[prop.name]];
321
329
  for (const item of items) {
322
330
  // Embedded sub-properties need all defaults since the DB can't apply them within JSON columns.
323
- this.assignDefaultValues(item, prop.targetMeta);
331
+ // For polymorphic embeddables, resolve the actual subtype to avoid setting
332
+ // properties from other subtypes (e.g. Cat's canMeow on a Dog instance).
333
+ let targetMeta = prop.targetMeta;
334
+ if (targetMeta.polymorphs && targetMeta.discriminatorColumn) {
335
+ const discValue = item[targetMeta.discriminatorColumn];
336
+ // eslint-disable-next-line eqeqeq
337
+ const resolved = targetMeta.polymorphs.find(m => m.discriminatorValue == discValue);
338
+ if (resolved) {
339
+ targetMeta = resolved;
340
+ }
341
+ }
342
+ this.assignDefaultValues(item, targetMeta);
324
343
  }
325
344
  }
326
345
  }
@@ -1,4 +1,4 @@
1
- import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
1
+ import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateHintOptions, PopulateOptions } from '../typings.js';
2
2
  import type { EntityManager } from '../EntityManager.js';
3
3
  import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
4
4
  import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
@@ -37,6 +37,8 @@ export interface EntityLoaderOptions<Entity, Fields extends string = never, Excl
37
37
  connectionType?: ConnectionType;
38
38
  /** Logging options for the query. */
39
39
  logging?: LoggingOptions;
40
+ /** Per-relation populate overrides (limit, offset, orderBy). */
41
+ populateHints?: Record<string, PopulateHintOptions>;
40
42
  }
41
43
  /** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
42
44
  export declare class EntityLoader {
@@ -177,8 +177,9 @@ export class EntityLoader {
177
177
  Utils.isObject(orderBy[prop.name]))
178
178
  .flatMap(orderBy => orderBy[prop.name]);
179
179
  const where = await this.extractChildCondition(options, prop);
180
+ const mergedOrderBy = populate.orderBy ? Utils.asArray(populate.orderBy) : innerOrderBy;
180
181
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
181
- const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
182
+ const pivotOrderBy = QueryHelper.mergeOrderBy(mergedOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
182
183
  const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
183
184
  return Utils.flatten(res);
184
185
  }
@@ -188,10 +189,12 @@ export class EntityLoader {
188
189
  const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
189
190
  ...options,
190
191
  where,
191
- orderBy: innerOrderBy,
192
+ orderBy: mergedOrderBy,
192
193
  }, !!(ref || prop.mapToPk));
193
- const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
194
- this.initializeCollections(filtered, prop, field, items, customOrder, partial);
194
+ const hasLimit = populate.limit != null;
195
+ const isPartial = partial || hasLimit;
196
+ const customOrder = mergedOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
197
+ this.initializeCollections(filtered, prop, field, items, customOrder, isPartial, hasLimit);
195
198
  return items;
196
199
  }
197
200
  async populateScalar(meta, filtered, options) {
@@ -269,15 +272,15 @@ export class EntityLoader {
269
272
  }));
270
273
  return allItems;
271
274
  }
272
- initializeCollections(filtered, prop, field, children, customOrder, partial) {
275
+ initializeCollections(filtered, prop, field, children, customOrder, partial, readonly) {
273
276
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
274
- this.initializeOneToMany(filtered, children, prop, field, partial);
277
+ this.initializeOneToMany(filtered, children, prop, field, partial, readonly);
275
278
  }
276
279
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
277
- this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
280
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly);
278
281
  }
279
282
  }
280
- initializeOneToMany(filtered, children, prop, field, partial) {
283
+ initializeOneToMany(filtered, children, prop, field, partial, readonly) {
281
284
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
282
285
  const map = {};
283
286
  for (const entity of filtered) {
@@ -293,14 +296,14 @@ export class EntityLoader {
293
296
  }
294
297
  for (const entity of filtered) {
295
298
  const key = helper(entity).getSerializedPrimaryKey();
296
- entity[field].hydrate(map[key], undefined, partial);
299
+ entity[field].hydrate(map[key], undefined, partial, readonly);
297
300
  }
298
301
  }
299
- initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
302
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly) {
300
303
  if (prop.mappedBy) {
301
304
  for (const entity of filtered) {
302
305
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
303
- entity[field].hydrate(items, true, partial);
306
+ entity[field].hydrate(items, true, partial, readonly);
304
307
  }
305
308
  }
306
309
  else {
@@ -311,7 +314,7 @@ export class EntityLoader {
311
314
  if (!customOrder) {
312
315
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
313
316
  }
314
- entity[field].hydrate(items, true, partial);
317
+ entity[field].hydrate(items, true, partial, readonly);
315
318
  }
316
319
  }
317
320
  }
@@ -375,7 +378,7 @@ export class EntityLoader {
375
378
  where = { $and: [where, prop.where] };
376
379
  }
377
380
  const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
378
- const items = await this.#em.find(meta.class, where, {
381
+ const findOptions = {
379
382
  filters,
380
383
  convertCustomTypes,
381
384
  lockMode,
@@ -394,7 +397,15 @@ export class EntityLoader {
394
397
  refresh: refresh && !children.every(item => options.visited.has(item)),
395
398
  // @ts-ignore not a public option, will be propagated to the populate call
396
399
  visited: options.visited,
397
- });
400
+ };
401
+ if (populate.limit != null) {
402
+ findOptions._partitionLimit = {
403
+ partitionBy: fk,
404
+ limit: populate.limit,
405
+ offset: populate.offset,
406
+ };
407
+ }
408
+ const items = await this.#em.find(meta.class, where, findOptions);
398
409
  // For targetKey relations, wire up loaded entities to parent references
399
410
  // This is needed because the references were created under alternate key,
400
411
  // but loaded entities are stored under PK, so they don't automatically merge
@@ -582,11 +593,18 @@ export class EntityLoader {
582
593
  const options2 = { ...options, fields, exclude, populateFilter };
583
594
  ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
584
595
  options2.populate = populate?.children ?? [];
596
+ if (populate?.limit != null) {
597
+ options2._partitionLimit = {
598
+ limit: populate.limit,
599
+ offset: populate.offset,
600
+ };
601
+ }
585
602
  if (!Utils.isEmpty(prop.where)) {
586
603
  where = { $and: [where, prop.where] };
587
604
  }
588
605
  const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy, this.#em.getTransactionContext(), options2, pivotJoin);
589
606
  const children = [];
607
+ const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
590
608
  for (let i = 0; i < filtered.length; i++) {
591
609
  const entity = filtered[i];
592
610
  const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
@@ -596,7 +614,11 @@ export class EntityLoader {
596
614
  schema: options.schema ?? this.#em.config.get('schema'),
597
615
  });
598
616
  }
599
- const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
617
+ // Union-target items carry their concrete class via `constructor` — dispatch to the right factory call.
618
+ const targetClass = isUnionTargetMN && item.constructor !== Object
619
+ ? item.constructor
620
+ : prop.targetMeta.class;
621
+ const entity = this.#em.getEntityFactory().create(targetClass, item, {
600
622
  refresh,
601
623
  merge: true,
602
624
  convertCustomTypes: true,
@@ -604,7 +626,8 @@ export class EntityLoader {
604
626
  });
605
627
  return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
606
628
  });
607
- entity[prop.name].hydrate(items, true);
629
+ const hasLimit = populate?.limit != null;
630
+ entity[prop.name].hydrate(items, true, hasLimit, hasLimit);
608
631
  children.push(items);
609
632
  }
610
633
  return children;
@@ -1,8 +1,8 @@
1
1
  import type { PopulatePath } from '../enums.js';
2
2
  import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
3
3
  import type { AssignOptions } from './EntityAssigner.js';
4
- import type { EntityData, EntityName, Primary, Loaded, FilterQuery, EntityDictionary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement } from '../typings.js';
5
- import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
4
+ import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityName, FilterQuery, Loaded, Primary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement, IndexFilterQuery, WithUsingOptions } from '../typings.js';
5
+ import type { CountByOptions, CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
6
6
  import type { EntityLoaderOptions } from './EntityLoader.js';
7
7
  import type { Cursor } from '../utils/Cursor.js';
8
8
  /** Repository class providing a type-safe API for querying and persisting a specific entity type. */
@@ -13,13 +13,17 @@ export declare class EntityRepository<Entity extends object> {
13
13
  /**
14
14
  * Finds first entity matching your `where` query.
15
15
  */
16
- findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOneOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
16
+ findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOptions<Entity, Hint, Fields, Excludes> & {
17
+ using?: Using | Using[];
18
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
17
19
  /**
18
20
  * Finds first entity matching your `where` query. If nothing is found, it will throw an error.
19
21
  * You can override the factory for creating this method via `options.failHandler` locally
20
22
  * or via `Configuration.findOneOrFailHandler` globally.
21
23
  */
22
- findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
24
+ findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes> & {
25
+ using?: Using | Using[];
26
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
23
27
  /**
24
28
  * Creates or updates the entity, based on whether it is already present in the database.
25
29
  * This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
@@ -72,24 +76,28 @@ export declare class EntityRepository<Entity extends object> {
72
76
  /**
73
77
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
74
78
  */
75
- find<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
79
+ find<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
80
+ using?: Using | Using[];
81
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
76
82
  /**
77
83
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
78
84
  * where first element is the array of entities, and the second is the count.
79
85
  */
80
- findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOptions<Entity, Hint, Fields, Excludes>): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
86
+ findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
87
+ using?: Using | Using[];
88
+ }): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
81
89
  /**
82
90
  * @inheritDoc EntityManager.findByCursor
83
91
  */
84
- findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true>(options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
92
+ findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true, Using extends string = never>(options: WithUsingOptions<FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, Entity, Using>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
85
93
  /**
86
94
  * Finds all entities of given type. You can pass additional options via the `options` parameter.
87
95
  */
88
- findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
96
+ findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<FindAllOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
89
97
  /**
90
98
  * @inheritDoc EntityManager.stream
91
99
  */
92
- stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
100
+ stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<StreamOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
93
101
  /**
94
102
  * @inheritDoc EntityManager.insert
95
103
  */
@@ -199,6 +207,16 @@ export declare class EntityRepository<Entity extends object> {
199
207
  * Returns total number of entities matching your `where` query.
200
208
  */
201
209
  count<Hint extends string = never>(where?: FilterQuery<Entity>, options?: CountOptions<Entity, Hint>): Promise<number>;
210
+ /**
211
+ * Counts entities grouped by one or more properties.
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * const counts = await repo.countBy('status');
216
+ * // { 'active': 5, 'inactive': 2 }
217
+ * ```
218
+ */
219
+ countBy(groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
202
220
  /** Returns the entity class name associated with this repository. */
203
221
  getEntityName(): string;
204
222
  /**
@@ -194,6 +194,18 @@ export class EntityRepository {
194
194
  async count(where = {}, options = {}) {
195
195
  return this.getEntityManager().count(this.entityName, where, options);
196
196
  }
197
+ /**
198
+ * Counts entities grouped by one or more properties.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * const counts = await repo.countBy('status');
203
+ * // { 'active': 5, 'inactive': 2 }
204
+ * ```
205
+ */
206
+ async countBy(groupBy, options) {
207
+ return this.getEntityManager().countBy(this.entityName, groupBy, options);
208
+ }
197
209
  /** Returns the entity class name associated with this repository. */
198
210
  getEntityName() {
199
211
  return Utils.className(this.entityName);
@@ -1,6 +1,6 @@
1
1
  import type { PopulatePath } from '../enums.js';
2
2
  import type { EntityManager } from '../EntityManager.js';
3
- import type { Dictionary, EntityData, EntityDictionary, EntityMetadata, IHydrator, EntityKey, PopulateOptions, Primary, AutoPath, Ref, AddEager, LoadedReference, EntityDTO, Loaded, SerializeDTO, FromEntityType, IsSubset, MergeSelected } from '../typings.js';
3
+ import type { Dictionary, EntityData, EntityDictionary, EntityMetadata, IHydrator, EntityKey, PopulateOptions, Primary, AutoPath, Ref, AddEager, LoadedReference, EntityDTO, Loaded, ExtractFieldsHint, ResolveSerializeFields, SerializeDTO, SerializeFieldsKeepPK, FromEntityType, IsSubset, MergeSelected } from '../typings.js';
4
4
  import { Reference } from './Reference.js';
5
5
  import { type AssignOptions } from './EntityAssigner.js';
6
6
  import type { EntityLoaderOptions } from './EntityLoader.js';
@@ -54,7 +54,7 @@ export declare class WrappedEntity<Entity extends object> {
54
54
  /** Converts the entity to a plain object representation, optionally excluding specified fields. */
55
55
  toObject<Ignored extends EntityKey<Entity> = never>(ignoreFields?: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
56
56
  /** Serializes the entity with control over which relations and fields to include or exclude. */
57
- serialize<Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Entity, Hint, Exclude>): SerializeDTO<Entity, Hint, Exclude>;
57
+ serialize<Hint extends string = never, Exclude extends string = never, Fields extends string = never>(options?: SerializeOptions<FromEntityType<Entity>, Hint, Exclude, Fields>): SerializeDTO<FromEntityType<Entity>, Hint, Exclude, never, ResolveSerializeFields<Fields, ExtractFieldsHint<Entity>>, SerializeFieldsKeepPK<Fields>>;
58
58
  /** Converts the entity to a plain object, including all properties regardless of serialization rules. */
59
59
  toPOJO(): EntityDTO<Entity>;
60
60
  /** Serializes the entity using its `toJSON` method (supports `JSON.stringify`). */
@@ -1,6 +1,6 @@
1
1
  import type { EntityManager } from '../EntityManager.js';
2
2
  import type { ColumnType, PropertyOptions, ReferenceOptions, EnumOptions, EmbeddedOptions, ManyToOneOptions, OneToManyOptions, OneToOneOptions, ManyToManyOptions, IndexColumnOptions } from '../metadata/types.js';
3
- import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, EntityRepositoryType, Hidden, Opt, Primary, EntityClass, EntitySchemaWithMeta, InferEntity, MaybeReturnType, Ref, IndexCallback, FormulaCallback, EntityCtor, IsNever, IWrappedEntity, DefineConfig, Config, MaybePromise } from '../typings.js';
3
+ import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, EntityRepositoryType, Hidden, Opt, Primary, EntityClass, EntitySchemaWithMeta, InferEntity, MaybeReturnType, Ref, IndexCallback, TriggerCallback, FormulaCallback, EntityCtor, IsNever, IWrappedEntity, DefineConfig, Config, MaybePromise, IndexHints } from '../typings.js';
4
4
  import type { Raw } from '../utils/RawQueryFragment.js';
5
5
  import type { ScalarReference } from './Reference.js';
6
6
  import type { SerializeOptions } from '../serialization/EntitySerializer.js';
@@ -104,8 +104,16 @@ export interface PropertyChain<Value, Options> {
104
104
  customOrder(...customOrder: string[] | number[] | boolean[]): PropertyChain<Value, Options>;
105
105
  extra(extra: string): PropertyChain<Value, Options>;
106
106
  ignoreSchemaChanges(...ignoreSchemaChanges: ('type' | 'extra' | 'default')[]): PropertyChain<Value, Options>;
107
- index(index?: boolean | string): PropertyChain<Value, Options>;
108
- unique(unique?: boolean | string): PropertyChain<Value, Options>;
107
+ /** Explicitly specify index on a property. When a string name is passed, it enables type-safe `using` in `FindOptions`. */
108
+ index<N extends string>(name: N): PropertyChain<Value, Omit<Options, 'index'> & {
109
+ index: N;
110
+ }>;
111
+ index(index?: boolean): PropertyChain<Value, Options>;
112
+ /** Set column as unique. When a string name is passed, it enables type-safe `using` in `FindOptions`. (SQL only) */
113
+ unique<N extends string>(name: N): PropertyChain<Value, Omit<Options, 'unique'> & {
114
+ unique: N;
115
+ }>;
116
+ unique(unique?: boolean): PropertyChain<Value, Options>;
109
117
  comment(comment: string): PropertyChain<Value, Options>;
110
118
  accessor(accessor?: string | boolean): PropertyChain<Value, Options>;
111
119
  eager(eager?: boolean): HasKind<Options, 'm:1' | '1:m' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
@@ -127,9 +135,13 @@ export interface PropertyChain<Value, Options> {
127
135
  pivotEntity(pivotEntity: () => EntityName): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
128
136
  fixedOrder(fixedOrder?: boolean): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
129
137
  fixedOrderColumn(fixedOrderColumn: string): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
130
- array(): HasKind<Options, 'embedded' | 'enum'> extends true ? PropertyChain<Value, Omit<Options, 'array'> & {
138
+ array(): Options extends {
139
+ kind: infer K extends string;
140
+ } ? K extends 'embedded' | 'enum' ? PropertyChain<Value, Omit<Options, 'array'> & {
131
141
  array: true;
132
- }> : never;
142
+ }> : never : PropertyChain<Value, Omit<Options, 'array'> & {
143
+ array: true;
144
+ }>;
133
145
  prefix(prefix: string | boolean): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
134
146
  prefixMode(prefixMode: EmbeddedPrefixMode): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
135
147
  object(object?: boolean): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
@@ -334,12 +346,20 @@ export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys
334
346
  concurrencyCheck(concurrencyCheck?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
335
347
  /**
336
348
  * Explicitly specify index on a property.
349
+ * When a string name is passed, it is captured as a literal type for use with the `using` option in `FindOptions`.
337
350
  */
338
- index(index?: boolean | string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
351
+ index<N extends string>(name: N): Pick<UniversalPropertyOptionsBuilder<Value, Omit<Options, 'index'> & {
352
+ index: N;
353
+ }, IncludeKeys>, IncludeKeys>;
354
+ index(index?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
339
355
  /**
340
356
  * Set column as unique for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
357
+ * When a string name is passed, it is captured as a literal type for use with the `using` option in `FindOptions`.
341
358
  */
342
- unique(unique?: boolean | string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
359
+ unique<N extends string>(name: N): Pick<UniversalPropertyOptionsBuilder<Value, Omit<Options, 'unique'> & {
360
+ unique: N;
361
+ }, IncludeKeys>, IncludeKeys>;
362
+ unique(unique?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
343
363
  /**
344
364
  * Specify column with check constraints. (Postgres driver only)
345
365
  *
@@ -556,7 +576,7 @@ export type PropertyBuilders = {
556
576
  /** Own keys + base entity keys (when TBase is not `never`). Guards against `keyof never = string | number | symbol`. */
557
577
  type AllKeys<TProperties, TBase> = keyof TProperties | (IsNever<TBase> extends true ? never : keyof TBase);
558
578
  /** Metadata descriptor for `defineEntity()`, combining entity options with property definitions. */
559
- export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminatorColumn' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'repository' | 'filters' | 'orderBy'> {
579
+ export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminatorColumn' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
560
580
  name: TName;
561
581
  tableName?: TTableName;
562
582
  extends?: {
@@ -609,6 +629,15 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
609
629
  fillFactor?: number;
610
630
  disabled?: boolean;
611
631
  }[];
632
+ triggers?: {
633
+ name?: string;
634
+ timing: 'before' | 'after' | 'instead of';
635
+ events: ('insert' | 'update' | 'delete' | 'truncate')[];
636
+ forEach?: 'row' | 'statement';
637
+ body?: string | Raw | TriggerCallback<InferEntityFromProperties<TProperties, TPK, TBase>>;
638
+ when?: string;
639
+ expression?: string;
640
+ }[];
612
641
  }
613
642
  /** Defines an entity schema using property builders, with full type inference from the property definitions. */
614
643
  export declare function defineEntity<const TName extends string, const TTableName extends string, const TProperties extends Record<string, any>, const TPK extends (keyof TProperties)[] | undefined = undefined, const TBase = never, const TRepository = never, const TForceObject extends boolean = false>(meta: EntityMetadataWithProperties<TName, TTableName, TProperties, TPK, TBase, TRepository, TForceObject>): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>, TBase, TProperties>;
@@ -665,7 +694,9 @@ export type InferEntityFromProperties<Properties extends Record<string, any>, PK
665
694
  [Config]?: DefineConfig<{
666
695
  forceObject: true;
667
696
  }>;
668
- } : {});
697
+ } : {}) & {
698
+ [IndexHints]?: [Properties];
699
+ };
669
700
  type InferCombinedPrimaryKey<Properties extends Record<string, any>, PK, Base> = PK extends undefined ? CombinePrimaryKeys<InferPrimaryKey<Properties>, ExtractBasePrimaryKey<Base>> : PK;
670
701
  type ExtractBasePrimaryKey<Base> = Base extends {
671
702
  [PrimaryKeyProp]?: infer BasePK;
@@ -191,15 +191,9 @@ export class UniversalPropertyOptionsBuilder {
191
191
  concurrencyCheck(concurrencyCheck = true) {
192
192
  return this.assignOptions({ concurrencyCheck });
193
193
  }
194
- /**
195
- * Explicitly specify index on a property.
196
- */
197
194
  index(index = true) {
198
195
  return this.assignOptions({ index });
199
196
  }
200
- /**
201
- * Set column as unique for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
202
- */
203
197
  unique(unique = true) {
204
198
  return this.assignOptions({ unique });
205
199
  }
package/entity/utils.js CHANGED
@@ -94,11 +94,10 @@ export function applyPopulateHints(populate, hints) {
94
94
  if (!entry) {
95
95
  continue;
96
96
  }
97
- if (hint.strategy != null) {
98
- entry.strategy = hint.strategy;
99
- }
100
- if (hint.joinType != null) {
101
- entry.joinType = hint.joinType;
97
+ for (const key of Object.keys(hint)) {
98
+ if (hint[key] != null) {
99
+ entry[key] = hint[key];
100
+ }
102
101
  }
103
102
  }
104
103
  }
package/errors.d.ts CHANGED
@@ -74,6 +74,8 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
74
74
  static viewEntityWithoutExpression(meta: EntityMetadata): MetadataError;
75
75
  static mixedInheritanceStrategies(root: EntityMetadata, child: EntityMetadata): MetadataError;
76
76
  static tptNotSupportedByDriver(meta: EntityMetadata): MetadataError;
77
+ /** Thrown when database triggers are defined on an entity using a driver that does not support them. */
78
+ static triggersNotSupportedByDriver(meta: EntityMetadata): MetadataError;
77
79
  private static fromMessage;
78
80
  }
79
81
  /** Error thrown when an entity lookup fails to find the expected result. */
package/errors.js CHANGED
@@ -243,6 +243,10 @@ export class MetadataError extends ValidationError {
243
243
  static tptNotSupportedByDriver(meta) {
244
244
  return new MetadataError(`Entity ${meta.className} uses TPT (Table-Per-Type) inheritance which is not supported by the current driver. TPT requires SQL JOINs and is only available with SQL drivers.`);
245
245
  }
246
+ /** Thrown when database triggers are defined on an entity using a driver that does not support them. */
247
+ static triggersNotSupportedByDriver(meta) {
248
+ return new MetadataError(`Entity ${meta.className} defines database triggers which are not supported by the current driver. Triggers are only available with SQL drivers.`);
249
+ }
246
250
  static fromMessage(meta, prop, message) {
247
251
  return new MetadataError(`${meta.className}.${prop.name} ${message}`);
248
252
  }
package/index.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * @packageDocumentation
3
3
  * @module core
4
4
  */
5
- export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, } from './typings.js';
6
- export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, IndexCallback, FormulaCallback, FormulaTable, SchemaTable, SchemaColumns, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
5
+ export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, IndexHints, } from './typings.js';
6
+ export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IndexFilterQuery, ExtractIndexHints, IndexName, IndexColumns, WithUsingOptions, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, TriggerCallback, IndexCallback, FormulaCallback, FormulaTable, SchemaTable, SchemaColumns, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, TriggerDef, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
7
7
  export * from './enums.js';
8
8
  export * from './errors.js';
9
9
  export * from './exceptions.js';
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @packageDocumentation
3
3
  * @module core
4
4
  */
5
- export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, } from './typings.js';
5
+ export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, IndexHints, } from './typings.js';
6
6
  export * from './enums.js';
7
7
  export * from './errors.js';
8
8
  export * from './exceptions.js';
@@ -54,6 +54,16 @@ export declare class MetadataDiscovery {
54
54
  * Define properties for a polymorphic pivot table.
55
55
  */
56
56
  private definePolymorphicPivotProperties;
57
+ /**
58
+ * Mirror of definePolymorphicPivotProperties for union-target M:N
59
+ * (e.g. Post.attachments -> Image | Video via shared pivot with a target-side discriminator).
60
+ *
61
+ * Pivot shape:
62
+ * (owner_fk..., discriminator_column, target_fk...)
63
+ * - owner side is a normal M:1 to the single owner entity
64
+ * - target side is a discriminator column + per-target-type virtual M:1 relations
65
+ */
66
+ private defineUnionTargetPolymorphicPivotProperties;
57
67
  /**
58
68
  * Create a virtual M:1 relation from pivot to a polymorphic owner entity.
59
69
  * This enables single-query join loading for inverse-side polymorphic M:N.
@@ -105,6 +115,7 @@ export declare class MetadataDiscovery {
105
115
  private initAutoincrement;
106
116
  private createSchemaTable;
107
117
  private initCheckConstraints;
118
+ private initTriggers;
108
119
  private initGeneratedColumn;
109
120
  private getDefaultVersionValue;
110
121
  private inferDefaultValue;