@mikro-orm/core 7.1.0-dev.0 → 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 (47) hide show
  1. package/EntityManager.d.ts +63 -11
  2. package/EntityManager.js +163 -18
  3. package/MikroORM.d.ts +3 -3
  4. package/drivers/DatabaseDriver.d.ts +1 -0
  5. package/drivers/DatabaseDriver.js +3 -0
  6. package/drivers/IDatabaseDriver.d.ts +30 -1
  7. package/entity/BaseEntity.d.ts +2 -2
  8. package/entity/Collection.d.ts +6 -3
  9. package/entity/Collection.js +15 -4
  10. package/entity/EntityFactory.js +20 -1
  11. package/entity/EntityLoader.d.ts +3 -1
  12. package/entity/EntityLoader.js +39 -16
  13. package/entity/EntityRepository.d.ts +27 -9
  14. package/entity/EntityRepository.js +12 -0
  15. package/entity/WrappedEntity.d.ts +2 -2
  16. package/entity/defineEntity.d.ts +40 -9
  17. package/entity/defineEntity.js +0 -6
  18. package/entity/utils.js +4 -5
  19. package/errors.d.ts +2 -0
  20. package/errors.js +4 -0
  21. package/index.d.ts +2 -2
  22. package/index.js +1 -1
  23. package/metadata/MetadataDiscovery.d.ts +11 -0
  24. package/metadata/MetadataDiscovery.js +127 -9
  25. package/metadata/MetadataValidator.js +9 -0
  26. package/metadata/types.d.ts +10 -2
  27. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  28. package/naming-strategy/NamingStrategy.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/platforms/Platform.d.ts +5 -0
  31. package/platforms/Platform.js +7 -0
  32. package/serialization/EntitySerializer.d.ts +18 -6
  33. package/serialization/EntitySerializer.js +31 -5
  34. package/typings.d.ts +116 -6
  35. package/typings.js +19 -2
  36. package/utils/AbstractMigrator.d.ts +19 -1
  37. package/utils/AbstractMigrator.js +219 -0
  38. package/utils/Configuration.d.ts +10 -3
  39. package/utils/Configuration.js +1 -0
  40. package/utils/DataloaderUtils.d.ts +10 -1
  41. package/utils/DataloaderUtils.js +78 -0
  42. package/utils/QueryHelper.d.ts +16 -0
  43. package/utils/QueryHelper.js +15 -0
  44. package/utils/Utils.d.ts +1 -1
  45. package/utils/Utils.js +1 -1
  46. package/utils/fs-utils.d.ts +2 -0
  47. package/utils/fs-utils.js +7 -1
package/typings.d.ts CHANGED
@@ -47,7 +47,7 @@ export type AsyncFunction<R = any, T = Dictionary> = (args: T) => Promise<T>;
47
47
  export type Compute<T> = {
48
48
  [K in keyof T]: T[K];
49
49
  } & {};
50
- type InternalKeys = 'EntityRepositoryType' | 'PrimaryKeyProp' | 'OptionalProps' | 'EagerProps' | 'HiddenProps' | '__selectedType' | '__loadedType';
50
+ type InternalKeys = 'EntityRepositoryType' | 'PrimaryKeyProp' | 'OptionalProps' | 'EagerProps' | 'HiddenProps' | 'IndexHints' | '__selectedType' | '__loadedType';
51
51
  /** Filters out function, symbol, and internal keys from an entity type. When `B = true`, also excludes scalar keys. */
52
52
  export type CleanKeys<T, K extends keyof T, B extends boolean = false> = T[K] & {} extends Function ? never : K extends symbol | InternalKeys ? never : B extends true ? T[K] & {} extends Scalar ? never : K : K;
53
53
  /** Extracts keys of `T` whose values are functions. */
@@ -150,6 +150,44 @@ export declare const EntityName: unique symbol;
150
150
  export type InferEntityName<T> = T extends {
151
151
  [EntityName]?: infer Name;
152
152
  } ? (Name extends string ? Name : never) : never;
153
+ /**
154
+ * Symbol used to declare index-to-column mappings on an entity type.
155
+ * For decorator entities, declare as a phantom property:
156
+ * ```typescript
157
+ * [IndexHints]?: { idx_email: 'email'; idx_name_age: 'name' | 'age' };
158
+ * ```
159
+ * For `defineEntity` entities, index hints are inferred automatically from
160
+ * named indexes (property-level `.index('name')` and entity-level `indexes`/`uniques`).
161
+ */
162
+ export declare const IndexHints: unique symbol;
163
+ /**
164
+ * Extracts the index hints map from an entity type. Returns `never` when no hints are declared.
165
+ * For decorator entities, `[IndexHints]` contains a pre-computed `{ idxName: 'prop' }` map.
166
+ * For `defineEntity` entities, `[IndexHints]` contains `[Properties]` (a tuple wrapping the
167
+ * raw property builders), which is lazily converted to the index map when first accessed.
168
+ */
169
+ export type ExtractIndexHints<T> = T extends {
170
+ [IndexHints]?: infer H;
171
+ } ? H extends [infer P extends Record<string, any>] ? InferPropertyIndexMap<P> : H : never;
172
+ /**
173
+ * Extracts `{ indexName: propertyKey }` from property builder options.
174
+ * Checks `.index('name')` and `.unique('name')` on each property in a single pass.
175
+ */
176
+ export type InferPropertyIndexMap<Properties extends Record<string, any>> = {
177
+ [K in keyof Properties as MaybeReturnType<Properties[K]> extends {
178
+ '~options': {
179
+ index: infer N extends string;
180
+ };
181
+ } ? N : MaybeReturnType<Properties[K]> extends {
182
+ '~options': {
183
+ unique: infer N extends string;
184
+ };
185
+ } ? N : never]: K & string;
186
+ };
187
+ /** Union of declared index names on an entity. Falls back to `string` when no `[IndexHints]` are declared. */
188
+ export type IndexName<T> = [ExtractIndexHints<T>] extends [never] ? string : (keyof ExtractIndexHints<T> & string) | (string & {});
189
+ /** Properties covered by the named index on entity T. Falls back to all entity keys when the index is unknown. */
190
+ export type IndexColumns<T, Name extends string> = ExtractIndexHints<T> extends Record<Name, infer Cols> ? Cols & string : EntityKey<T>;
153
191
  /**
154
192
  * Branded type that marks a property as optional in `em.create()`.
155
193
  * Use as a property type wrapper: `createdAt: Opt<Date>` instead of listing in `[OptionalProps]`.
@@ -328,6 +366,18 @@ export type ObjectQuery<T> = OperatorMap<T> & FilterObject<T>;
328
366
  * Accepts an object query, a primary key value, entity props with operators, or an array of filters.
329
367
  */
330
368
  export type FilterQuery<T> = ObjectQuery<T> | NonNullable<ExpandScalar<Primary<T>>> | NonNullable<EntityProps<T> & OperatorMap<T>> | FilterQuery<T>[];
369
+ /**
370
+ * `FilterQuery` restricted to only properties covered by the specified index(es).
371
+ * Used when `using` option is set in `FindOptions` to enforce type-safe index usage.
372
+ */
373
+ export type IndexFilterQuery<T, Using extends string> = [Using] extends [never] ? FilterQuery<T> : (OperatorMap<T> & {
374
+ -readonly [K in Extract<EntityKey<T>, IndexColumns<T, Using>>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ElemMatchFilter<FilterObjectProp<T, K>> | null;
375
+ }) | NonNullable<ExpandScalar<Primary<T>>> | IndexFilterQuery<T, Using>[];
376
+ /** Replaces `where` and `using` on an options type with index-aware variants when `Using` is specified. */
377
+ export type WithUsingOptions<Opts, Entity, Using extends string> = Omit<Opts, 'where' | 'using'> & {
378
+ using?: Using | Using[];
379
+ where?: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>;
380
+ };
331
381
  /** Public interface for the entity wrapper, accessible via `wrap(entity)`. Provides helper methods for entity state management. */
332
382
  export interface IWrappedEntity<Entity extends object> {
333
383
  isInitialized(): boolean;
@@ -341,7 +391,7 @@ export interface IWrappedEntity<Entity extends object> {
341
391
  toObject<Ignored extends EntityKey<Entity>>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
342
392
  toJSON(...args: any[]): EntityDTO<Entity>;
343
393
  toPOJO(): EntityDTO<Entity>;
344
- serialize<Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>): SerializeDTO<Naked, Hint, Exclude>;
394
+ serialize<Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never, Fields extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude, Fields>): SerializeDTO<Naked, Hint, Exclude, never, ResolveSerializeFields<Fields, ExtractFieldsHint<Entity>>, SerializeFieldsKeepPK<Fields>>;
345
395
  setSerializationContext<Hint extends string = never, Fields extends string = never, Exclude extends string = never>(options: LoadHint<Entity, Hint, Fields, Exclude>): void;
346
396
  assign<Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Convert extends boolean = false, Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> = EntityData<Naked, Convert> | Partial<EntityDTO<Naked>>>(data: Data & IsSubset<EntityData<Naked, Convert>, Data>, options?: AssignOptions<Convert>): MergeSelected<Entity, Naked, keyof Data & string>;
347
397
  getSchema(): string | undefined;
@@ -534,17 +584,32 @@ export type EntityDTOFlat<T, C extends TypeConfig = never> = {
534
584
  type SerializeTopHints<H extends string> = H extends `${infer Top}.${string}` ? Top : H;
535
585
  type SerializeSubHints<K extends string, H extends string> = H extends `${K}.${infer Rest}` ? Rest : never;
536
586
  type SerializeSubFields<K extends string, F extends string> = K extends F ? '*' : [F extends `${K & string}.${infer Rest}` ? Rest : never] extends [never] ? '*' : F extends `${K & string}.${infer Rest}` ? Rest : never;
537
- type SerializeFieldsFilter<T, K extends keyof T, F extends string> = K extends Prefix<T, F> | PrimaryProperty<T> ? K : never;
587
+ type SerializeFieldsFilter<T, K extends keyof T, F extends string, KeepPK extends boolean = true> = K extends Prefix<T, F> | (KeepPK extends true ? PrimaryProperty<T> : never) ? K : never;
538
588
  type SerializePropValue<T, K extends keyof T, H extends string, C extends TypeConfig = never> = K & string extends SerializeTopHints<H> ? NonNullable<T[K]> extends CollectionShape<infer U> ? SerializeDTO<U & object, SerializeSubHints<K & string, H>, never, C>[] : SerializeDTO<ExpandProperty<T[K]>, SerializeSubHints<K & string, H>, never, C> | Extract<T[K], null | undefined> : EntityDTOProp<T, T[K], C>;
539
- type SerializePropValueWithFields<T, K extends keyof T, H extends string, C extends TypeConfig, F extends string> = K & string extends SerializeTopHints<H> ? NonNullable<T[K]> extends CollectionShape<infer U> ? SerializeDTO<U & object, SerializeSubHints<K & string, H>, never, C, SerializeSubFields<K & string, F>>[] : SerializeDTO<ExpandProperty<T[K]>, SerializeSubHints<K & string, H>, never, C, SerializeSubFields<K & string, F>> | Extract<T[K], null | undefined> : EntityDTOProp<T, T[K], C>;
589
+ type SerializePropValueWithFields<T, K extends keyof T, H extends string, C extends TypeConfig, F extends string, KeepPK extends boolean = true> = K & string extends SerializeTopHints<H> ? NonNullable<T[K]> extends CollectionShape<infer U> ? SerializeDTO<U & object, SerializeSubHints<K & string, H>, never, C, SerializeSubFields<K & string, F>, KeepPK>[] : SerializeDTO<ExpandProperty<T[K]>, SerializeSubHints<K & string, H>, never, C, SerializeSubFields<K & string, F>, KeepPK> | Extract<T[K], null | undefined> : EntityDTOProp<T, T[K], C>;
590
+ /**
591
+ * Resolves a user-provided `Fields` generic to the value passed to `SerializeDTO`'s `F` parameter:
592
+ * `never` (no `fields` option) falls back to `Default` (typically `'*'` or the entity's `ExtractFieldsHint`).
593
+ */
594
+ export type ResolveSerializeFields<Fields extends string, Default extends string = '*'> = [Fields] extends [never] ? Default : Fields;
595
+ /**
596
+ * Whether the explicit `serialize()` path should keep primary keys in the output. When the user does not provide
597
+ * a `fields` option (`Fields = never`), behave like `toObject()` and keep PKs; when they do, treat it as a strict
598
+ * whitelist so PKs are dropped unless listed.
599
+ */
600
+ export type SerializeFieldsKeepPK<Fields extends string> = [Fields] extends [never] ? true : false;
540
601
  /**
541
602
  * Return type of `serialize()`. Combines Loaded + EntityDTO in a single pass for better performance.
542
603
  * Respects populate hints (`H`), exclude hints (`E`), and fields hints (`F`).
604
+ *
605
+ * `KeepPK` controls whether primary keys are auto-included when narrowing by `F`. The `toObject()` path keeps the
606
+ * default `true` (PKs always present); the explicit `serialize()` path passes `false` so the user-provided `fields`
607
+ * option is a strict whitelist that can drop PKs from the output.
543
608
  */
544
- export type SerializeDTO<T, H extends string = never, E extends string = never, C extends TypeConfig = never, F extends string = '*'> = string extends H ? EntityDTOFlat<T, C> : [F] extends ['*'] ? {
609
+ export type SerializeDTO<T, H extends string = never, E extends string = never, C extends TypeConfig = never, F extends string = '*', KeepPK extends boolean = true> = string extends H ? EntityDTOFlat<T, C> : [F] extends ['*'] ? {
545
610
  [K in keyof T as ExcludeHidden<T, K> & CleanKeys<T, K> & (IsNever<E> extends true ? K : Exclude<K, E>)]: SerializePropValue<T, K, H, C> | Extract<T[K], null | undefined>;
546
611
  } : {
547
- [K in keyof T as ExcludeHidden<T, K> & CleanKeys<T, K> & (IsNever<E> extends true ? K : Exclude<K, E>) & SerializeFieldsFilter<T, K, F>]: SerializePropValueWithFields<T, K, H, C, F> | Extract<T[K], null | undefined>;
612
+ [K in keyof T as ExcludeHidden<T, K> & CleanKeys<T, K> & (IsNever<E> extends true ? K : Exclude<K, E>) & SerializeFieldsFilter<T, K, F, KeepPK>]: SerializePropValueWithFields<T, K, H, C, F, KeepPK> | Extract<T[K], null | undefined>;
548
613
  };
549
614
  type TargetKeys<T> = T extends EntityClass<infer P> ? keyof P : keyof T;
550
615
  type PropertyName<T> = IsUnknown<T> extends false ? TargetKeys<T> : string;
@@ -591,6 +656,8 @@ export type IndexCallback<T> = (columns: Record<PropertyName<T>, string>, table:
591
656
  export type FormulaCallback<T> = (columns: FormulaColumns<T>, table: FormulaTable) => string | Raw;
592
657
  /** Callback for CHECK constraint expressions. Receives column mappings and table info. */
593
658
  export type CheckCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
659
+ /** Callback for trigger body expressions. Receives column mappings and table info. */
660
+ export type TriggerCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
594
661
  /** Callback for generated (computed) column expressions. Receives column mappings and table info. */
595
662
  export type GeneratedColumnCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
596
663
  /** Definition of a CHECK constraint on a table or property. */
@@ -599,6 +666,23 @@ export interface CheckConstraint<T = any> {
599
666
  property?: string;
600
667
  expression: string | Raw | CheckCallback<T>;
601
668
  }
669
+ /** Definition of a database trigger on a table. */
670
+ export interface TriggerDef<T = any> {
671
+ /** Trigger name. Auto-generated if omitted. */
672
+ name?: string;
673
+ /** When the trigger fires relative to the event. */
674
+ timing: 'before' | 'after' | 'instead of';
675
+ /** Which DML events activate the trigger. */
676
+ events: ('insert' | 'update' | 'delete' | 'truncate')[];
677
+ /** Whether the trigger fires once per row or per statement. Defaults to `'row'`. */
678
+ forEach?: 'row' | 'statement';
679
+ /** SQL body of the trigger. Can be a string, Raw query, or callback receiving column name mappings. */
680
+ body?: string | Raw | TriggerCallback<T>;
681
+ /** Optional SQL WHEN condition for the trigger. */
682
+ when?: string;
683
+ /** Raw DDL escape hatch — full CREATE TRIGGER statement. Mutually exclusive with `body`. */
684
+ expression?: string;
685
+ }
602
686
  /** Branded string that accepts any string value while preserving autocompletion for known literals. */
603
687
  export type AnyString = string & {};
604
688
  /** Describes a single property (column, relation, or embedded) within an entity's metadata. */
@@ -818,6 +902,7 @@ export interface EntityMetadata<Entity = any, Class extends EntityCtor<Entity> =
818
902
  disabled?: boolean;
819
903
  }[];
820
904
  checks: CheckConstraint<Entity>[];
905
+ triggers: TriggerDef<Entity>[];
821
906
  repositoryClass?: string;
822
907
  repository: () => EntityClass<EntityRepository<any>>;
823
908
  hooks: {
@@ -1045,6 +1130,11 @@ export interface IMigrator {
1045
1130
  * Executes down migrations to the given point. Without parameter it will migrate one version down.
1046
1131
  */
1047
1132
  down(options?: string | string[] | Omit<MigrateOptions, 'from'>): Promise<MigrationInfo[]>;
1133
+ /**
1134
+ * Combines multiple executed migrations into a single migration file.
1135
+ * Concatenates source code without touching the database schema.
1136
+ */
1137
+ rollup(migrations?: string[]): Promise<MigrationResult>;
1048
1138
  /**
1049
1139
  * Registers event handler.
1050
1140
  */
@@ -1053,6 +1143,14 @@ export interface IMigrator {
1053
1143
  * Removes event handler.
1054
1144
  */
1055
1145
  off(event: MigratorEvent, listener: (event: MigrationInfo) => MaybePromise<void>): IMigrator;
1146
+ /**
1147
+ * Marks a migration as executed without actually running it.
1148
+ */
1149
+ logMigration(name: string): Promise<void>;
1150
+ /**
1151
+ * Removes a migration from the executed list without reverting it.
1152
+ */
1153
+ unlogMigration(name: string): Promise<void>;
1056
1154
  /**
1057
1155
  * @internal
1058
1156
  */
@@ -1119,11 +1217,23 @@ export type PopulateOptions<T> = {
1119
1217
  children?: PopulateOptions<T[keyof T]>[];
1120
1218
  /** When true, ignores `mapToPk` on the property and returns full entity data instead of just PKs. */
1121
1219
  dataOnly?: boolean;
1220
+ /** Limit the number of items loaded per parent entity. Collections will be marked as partial and readonly. */
1221
+ limit?: number;
1222
+ /** Offset for per-parent limiting (used with `limit`). */
1223
+ offset?: number;
1224
+ /** Order by clause for per-parent limiting. Takes precedence over nested `FindOptions.orderBy`. */
1225
+ orderBy?: QueryOrderMap<T[keyof T]>;
1122
1226
  };
1123
1227
  /** Inline options that can be appended to populate hint strings (e.g., strategy, join type). */
1124
1228
  export type PopulateHintOptions = {
1125
1229
  strategy?: LoadStrategy.JOINED | LoadStrategy.SELECT_IN | 'joined' | 'select-in';
1126
1230
  joinType?: 'inner join' | 'left join';
1231
+ /** Limit the number of items loaded per parent entity. Collections will be marked as partial and readonly. */
1232
+ limit?: number;
1233
+ /** Offset for per-parent limiting (used with `limit`). */
1234
+ offset?: number;
1235
+ /** Order by clause for per-parent limiting. Takes precedence over nested `FindOptions.orderBy`. */
1236
+ orderBy?: QueryOrderMap<any>;
1127
1237
  };
1128
1238
  type ExtractType<T> = T extends CollectionShape<infer U> ? U : T extends ReferenceShape<infer U> ? U : T extends Ref<infer U> ? U : T extends readonly (infer U)[] ? U : T;
1129
1239
  type ExtractStringKeys<T> = {
package/typings.js CHANGED
@@ -4,7 +4,6 @@ import { EntityHelper } from './entity/EntityHelper.js';
4
4
  import { helper } from './entity/wrap.js';
5
5
  import { Utils } from './utils/Utils.js';
6
6
  import { EntityComparator } from './utils/EntityComparator.js';
7
- import { BaseEntity } from './entity/BaseEntity.js';
8
7
  /** Symbol used to declare a custom repository type on an entity class (e.g., `[EntityRepositoryType]?: BookRepository`). */
9
8
  export const EntityRepositoryType = Symbol('EntityRepositoryType');
10
9
  /** Symbol used to declare the primary key property name(s) on an entity (e.g., `[PrimaryKeyProp]?: 'id'`). */
@@ -22,6 +21,16 @@ export const Config = Symbol('Config');
22
21
  /** Symbol used to declare the entity name as a string literal type (used by `defineEntity`). */
23
22
  // eslint-disable-next-line @typescript-eslint/no-redeclare
24
23
  export const EntityName = Symbol('EntityName');
24
+ /**
25
+ * Symbol used to declare index-to-column mappings on an entity type.
26
+ * For decorator entities, declare as a phantom property:
27
+ * ```typescript
28
+ * [IndexHints]?: { idx_email: 'email'; idx_name_age: 'name' | 'age' };
29
+ * ```
30
+ * For `defineEntity` entities, index hints are inferred automatically from
31
+ * named indexes (property-level `.index('name')` and entity-level `indexes`/`uniques`).
32
+ */
33
+ export const IndexHints = Symbol('IndexHints');
25
34
  /**
26
35
  * Runtime metadata for an entity, holding its properties, relations, indexes, hooks, and more.
27
36
  * Created during metadata discovery and used throughout the ORM lifecycle.
@@ -39,12 +48,20 @@ export class EntityMetadata {
39
48
  this.indexes = [];
40
49
  this.uniques = [];
41
50
  this.checks = [];
51
+ this.triggers = [];
42
52
  this.referencingProperties = [];
43
53
  this.concurrencyCheckKeys = new Set();
44
54
  Object.assign(this, meta);
45
55
  const name = meta.className ?? meta.name;
46
56
  if (!this.class && name) {
47
- const Class = this.extends === BaseEntity ? { [name]: class extends BaseEntity {
57
+ let Parent;
58
+ if (typeof this.extends === 'function') {
59
+ Parent = this.extends;
60
+ }
61
+ else if (this.extends != null && typeof this.extends.class === 'function') {
62
+ Parent = this.extends.class;
63
+ }
64
+ const Class = Parent ? { [name]: class extends Parent {
48
65
  } }[name] : { [name]: class {
49
66
  } }[name];
50
67
  this.class = Class;
@@ -1,4 +1,4 @@
1
- import type { Constructor, IMigrationGenerator, IMigrationRunner, IMigrator, IMigratorStorage, MaybePromise, Migration, MigrationInfo, MigrationRow, MigratorEvent } from '../typings.js';
1
+ import type { Constructor, IMigrationGenerator, IMigrationRunner, IMigrator, IMigratorStorage, MaybePromise, Migration, MigrationInfo, MigrationResult, MigrationRow, MigratorEvent } from '../typings.js';
2
2
  import type { Transaction } from '../connections/Connection.js';
3
3
  import type { Configuration, MigrationsOptions } from './Configuration.js';
4
4
  import type { EntityManagerType, IDatabaseDriver } from '../drivers/IDatabaseDriver.js';
@@ -70,7 +70,25 @@ export declare abstract class AbstractMigrator<D extends IDatabaseDriver> implem
70
70
  * @inheritDoc
71
71
  */
72
72
  down(options?: string | string[] | Omit<MigrateOptions, 'from'>): Promise<MigrationInfo[]>;
73
+ /**
74
+ * @inheritDoc
75
+ */
76
+ rollup(migrations?: string[]): Promise<MigrationResult>;
77
+ /**
78
+ * Extracts the body of a method from migration source code using brace counting.
79
+ * Returns the raw lines between the opening and closing braces, or empty string if not found.
80
+ * @internal
81
+ */
82
+ private extractMethodBody;
73
83
  abstract getStorage(): IMigratorStorage;
84
+ /**
85
+ * @inheritDoc
86
+ */
87
+ logMigration(name: string): Promise<void>;
88
+ /**
89
+ * @inheritDoc
90
+ */
91
+ unlogMigration(name: string): Promise<void>;
74
92
  protected init(): Promise<void>;
75
93
  protected initPaths(): Promise<void>;
76
94
  protected initServices(): void;
@@ -63,6 +63,225 @@ export class AbstractMigrator {
63
63
  async down(options) {
64
64
  return this.runMigrations('down', options);
65
65
  }
66
+ /**
67
+ * @inheritDoc
68
+ */
69
+ async rollup(migrations) {
70
+ await this.init();
71
+ const { fs } = await import('@mikro-orm/core/fs-utils');
72
+ const all = await this.discoverMigrations();
73
+ const executedSet = new Set(await this.storage.executed());
74
+ let toRollup;
75
+ if (migrations && migrations.length > 0) {
76
+ const requested = new Set(migrations.map(m => this.getMigrationFilename(m)));
77
+ toRollup = all.filter(m => requested.has(m.name));
78
+ const found = new Set(toRollup.map(m => m.name));
79
+ const notFound = [...requested].filter(name => !found.has(name));
80
+ if (notFound.length > 0) {
81
+ throw new Error(`Migrations not found: ${notFound.join(', ')}`);
82
+ }
83
+ const notExecuted = toRollup.filter(m => !executedSet.has(m.name));
84
+ if (notExecuted.length > 0) {
85
+ throw new Error(`Cannot roll up migrations that have not been executed: ${notExecuted.map(m => m.name).join(', ')}`);
86
+ }
87
+ }
88
+ else {
89
+ toRollup = all.filter(m => executedSet.has(m.name));
90
+ }
91
+ if (toRollup.length < 2) {
92
+ throw new Error('At least 2 executed migrations are required for rollup');
93
+ }
94
+ const withoutPath = toRollup.filter(m => !m.path);
95
+ if (withoutPath.length > 0) {
96
+ throw new Error(`Cannot roll up migrations without file paths (class-based migrations): ${withoutPath.map(m => m.name).join(', ')}`);
97
+ }
98
+ const upBodies = [];
99
+ const downBodies = [];
100
+ const placeholder = `__mikro_orm_rollup_${Date.now()}__`;
101
+ for (const migration of toRollup) {
102
+ const source = await fs.readFile(migration.path);
103
+ const upBody = this.extractMethodBody(source, 'up');
104
+ const downBody = this.extractMethodBody(source, 'down');
105
+ if (upBody) {
106
+ upBodies.push(` // --- merged from ${migration.name} ---\n${upBody}`);
107
+ }
108
+ if (downBody) {
109
+ downBodies.unshift(` // --- merged from ${migration.name} ---\n${downBody}`);
110
+ }
111
+ }
112
+ const diff = {
113
+ up: [placeholder],
114
+ down: downBodies.length > 0 ? [placeholder] : [],
115
+ };
116
+ const [templateCode, fileName] = await this.generator.generate(diff);
117
+ const placeholderRe = new RegExp(`^.*${placeholder}.*$`, 'm');
118
+ let code = templateCode.replace(placeholderRe, upBodies.join('\n'));
119
+ if (downBodies.length > 0) {
120
+ code = code.replace(placeholderRe, downBodies.join('\n'));
121
+ }
122
+ await fs.writeFile(fs.normalizePath(this.absolutePath, fileName), code, { flush: true });
123
+ const updateStorage = async () => {
124
+ for (const migration of toRollup) {
125
+ await this.storage.unlogMigration({ name: migration.name });
126
+ }
127
+ await this.storage.logMigration({ name: fileName.replace(/\.[jt]s$/, '') });
128
+ };
129
+ if (this.options.transactional) {
130
+ await this.driver.getConnection().transactional(async (trx) => {
131
+ this.storage.setMasterMigration(trx);
132
+ try {
133
+ await updateStorage();
134
+ }
135
+ finally {
136
+ this.storage.unsetMasterMigration();
137
+ }
138
+ });
139
+ }
140
+ else {
141
+ await updateStorage();
142
+ }
143
+ await Promise.all(toRollup.map(migration => fs.unlink(migration.path)));
144
+ return { fileName, code, diff: { up: [], down: [] } };
145
+ }
146
+ /**
147
+ * Extracts the body of a method from migration source code using brace counting.
148
+ * Returns the raw lines between the opening and closing braces, or empty string if not found.
149
+ * @internal
150
+ */
151
+ extractMethodBody(source, methodName) {
152
+ const lines = source.split('\n');
153
+ // match method declarations, not occurrences in comments/strings — require preceding whitespace or keyword
154
+ const methodPattern = new RegExp(`^\\s+(?:override\\s+|async\\s+)*${methodName}\\s*\\(`);
155
+ let methodLine = -1;
156
+ for (let i = 0; i < lines.length; i++) {
157
+ if (methodPattern.test(lines[i])) {
158
+ methodLine = i;
159
+ break;
160
+ }
161
+ }
162
+ if (methodLine === -1) {
163
+ return '';
164
+ }
165
+ let braceCount = 0;
166
+ let bodyStart = -1;
167
+ let bodyEnd = -1;
168
+ let bodyStartCol = -1;
169
+ let bodyEndCol = -1;
170
+ let inBacktick = false;
171
+ let inBlockComment = false;
172
+ // stack tracks brace depth at which each template expression `${...}` was entered
173
+ const templateExprStack = [];
174
+ for (let i = methodLine; i < lines.length; i++) {
175
+ const line = lines[i];
176
+ for (let j = 0; j < line.length; j++) {
177
+ // handle multi-line block comments
178
+ if (inBlockComment) {
179
+ if (line[j] === '*' && j + 1 < line.length && line[j + 1] === '/') {
180
+ inBlockComment = false;
181
+ j++;
182
+ }
183
+ continue;
184
+ }
185
+ // handle multi-line template literals
186
+ if (inBacktick) {
187
+ if (line[j] === '\\') {
188
+ j++;
189
+ }
190
+ else if (line[j] === '`') {
191
+ inBacktick = false;
192
+ }
193
+ else if (line[j] === '$' && j + 1 < line.length && line[j + 1] === '{') {
194
+ // entering template expression — resume brace counting
195
+ templateExprStack.push(braceCount);
196
+ inBacktick = false;
197
+ j++; // skip the {
198
+ braceCount++;
199
+ }
200
+ continue;
201
+ }
202
+ const ch = line[j];
203
+ // single/double quoted strings (single-line only)
204
+ if (ch === "'" || ch === '"') {
205
+ const quote = ch;
206
+ j++;
207
+ while (j < line.length) {
208
+ if (line[j] === '\\') {
209
+ j++;
210
+ }
211
+ else if (line[j] === quote) {
212
+ break;
213
+ }
214
+ j++;
215
+ }
216
+ continue;
217
+ }
218
+ // template literal start
219
+ if (ch === '`') {
220
+ inBacktick = true;
221
+ continue;
222
+ }
223
+ // single-line comment
224
+ if (ch === '/' && j + 1 < line.length && line[j + 1] === '/') {
225
+ break;
226
+ }
227
+ // block comment start
228
+ if (ch === '/' && j + 1 < line.length && line[j + 1] === '*') {
229
+ inBlockComment = true;
230
+ j++;
231
+ continue;
232
+ }
233
+ if (ch === '{') {
234
+ if (braceCount === 0) {
235
+ bodyStart = i;
236
+ bodyStartCol = j + 1;
237
+ }
238
+ braceCount++;
239
+ }
240
+ else if (ch === '}') {
241
+ braceCount--;
242
+ // closing a template expression — re-enter backtick mode
243
+ if (templateExprStack.length > 0 && braceCount === templateExprStack[templateExprStack.length - 1]) {
244
+ templateExprStack.pop();
245
+ inBacktick = true;
246
+ continue;
247
+ }
248
+ if (braceCount === 0) {
249
+ bodyEnd = i;
250
+ bodyEndCol = j;
251
+ break;
252
+ }
253
+ }
254
+ }
255
+ if (bodyEnd !== -1) {
256
+ break;
257
+ }
258
+ }
259
+ if (bodyStart === -1 || bodyEnd === -1) {
260
+ return '';
261
+ }
262
+ // single-line method body: extract content between braces on the same line
263
+ if (bodyStart === bodyEnd) {
264
+ const content = lines[bodyStart].slice(bodyStartCol, bodyEndCol).trim();
265
+ return content ? ` ${content}` : '';
266
+ }
267
+ return lines.slice(bodyStart + 1, bodyEnd).join('\n');
268
+ }
269
+ /**
270
+ * @inheritDoc
271
+ */
272
+ async logMigration(name) {
273
+ await this.init();
274
+ await this.storage.ensureTable?.();
275
+ await this.storage.logMigration({ name });
276
+ }
277
+ /**
278
+ * @inheritDoc
279
+ */
280
+ async unlogMigration(name) {
281
+ await this.init();
282
+ await this.storage.ensureTable?.();
283
+ await this.storage.unlogMigration({ name });
284
+ }
66
285
  async init() {
67
286
  if (this.initialized) {
68
287
  return;
@@ -18,7 +18,7 @@ import type { MikroORM } from '../MikroORM.js';
18
18
  /** Holds and validates all ORM configuration options, providing access to drivers, loggers, cache adapters, and other services. */
19
19
  export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>> {
20
20
  #private;
21
- constructor(options: Partial<Options>, validate?: boolean);
21
+ constructor(options: Partial<Options<any, any, any>>, validate?: boolean);
22
22
  /** Returns the database platform instance. */
23
23
  getPlatform(): ReturnType<D['getPlatform']>;
24
24
  /**
@@ -99,7 +99,7 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
99
99
  /**
100
100
  * Type helper to make it easier to use `mikro-orm.config.js`.
101
101
  */
102
- export declare function defineConfig<D extends IDatabaseDriver = IDatabaseDriver, EM extends EntityManager<D> = EntityManager<D>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Partial<Options<D, EM, Entities>>;
102
+ export declare function defineConfig<D extends IDatabaseDriver = IDatabaseDriver, EM extends EntityManager<D> = EntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Partial<Options<D, EM, Entities>>;
103
103
  /**
104
104
  * Connection configuration options for database connections.
105
105
  * @see https://mikro-orm.io/docs/configuration#connection
@@ -352,7 +352,7 @@ export interface MetadataDiscoveryOptions {
352
352
  * MikroORM configuration options.
353
353
  * @see https://mikro-orm.io/docs/configuration
354
354
  */
355
- export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM extends EntityManager<Driver> & Driver[typeof EntityManagerType] = EntityManager<Driver> & Driver[typeof EntityManagerType], Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends ConnectionOptions {
355
+ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM extends EntityManager<Driver> & Driver[typeof EntityManagerType] = EntityManager<Driver> & Driver[typeof EntityManagerType], Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends ConnectionOptions {
356
356
  /** Connection pool configuration. */
357
357
  pool: PoolConfig;
358
358
  /**
@@ -529,6 +529,13 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
529
529
  * @default false
530
530
  */
531
531
  forceUndefined: boolean;
532
+ /**
533
+ * Initialize nullable properties to `null` (or `undefined` when `forceUndefined` is set)
534
+ * during `em.create()` when they are not provided in the data. Without this option,
535
+ * nullable properties remain `undefined` until the entity is loaded from the database.
536
+ * @default false
537
+ */
538
+ initNullableProperties: boolean;
532
539
  /**
533
540
  * Property `onCreate` hooks are normally executed during `flush` operation.
534
541
  * With this option, they will be processed early inside `em.create()` method.
@@ -62,6 +62,7 @@ const DEFAULTS = {
62
62
  upsertManaged: true,
63
63
  forceEntityConstructor: false,
64
64
  forceUndefined: false,
65
+ initNullableProperties: false,
65
66
  forceUtcTimezone: true,
66
67
  processOnCreateHooksEarly: true,
67
68
  ensureDatabase: true,
@@ -1,5 +1,5 @@
1
1
  import type { Constructor, Primary, Ref } from '../typings.js';
2
- import { Collection, type InitCollectionOptions } from '../entity/Collection.js';
2
+ import { Collection, type InitCollectionOptions, type LoadCountOptions } from '../entity/Collection.js';
3
3
  import { type EntityManager } from '../EntityManager.js';
4
4
  import { type LoadReferenceOptions } from '../entity/Reference.js';
5
5
  type BatchLoadFn<K, V> = (keys: readonly K[]) => PromiseLike<ArrayLike<V | Error>>;
@@ -44,6 +44,15 @@ export declare class DataloaderUtils {
44
44
  * makes one query per entity and maps each input collection to the corresponding result.
45
45
  */
46
46
  static getManyToManyColBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
47
+ /**
48
+ * Returns the count dataloader batchLoadFn, which aggregates `Collection.loadCount()` calls
49
+ * by entity and relation, issues a single grouped count query per entity+options combination
50
+ * via `em.countBy()`, and maps each input collection to the corresponding count.
51
+ *
52
+ * For 1:M relations, groups by the FK property on the target entity.
53
+ * For M:N relations, groups by the owner FK on the pivot entity.
54
+ */
55
+ static getCountBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<LoadCountOptions<any>, 'dataloader' | 'refresh'>?], number>;
47
56
  static getDataLoader(): Promise<Constructor<{
48
57
  load: (...args: unknown[]) => Promise<unknown>;
49
58
  }>>;