@mikro-orm/core 7.0.0-dev.2 → 7.0.0-dev.20

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 (58) hide show
  1. package/EntityManager.d.ts +13 -5
  2. package/EntityManager.js +60 -40
  3. package/MikroORM.js +2 -2
  4. package/decorators/Check.d.ts +2 -2
  5. package/decorators/CreateRequestContext.js +4 -1
  6. package/decorators/Embeddable.d.ts +5 -5
  7. package/decorators/Embeddable.js +1 -1
  8. package/decorators/Embedded.d.ts +6 -12
  9. package/decorators/Entity.d.ts +5 -5
  10. package/decorators/Entity.js +0 -1
  11. package/decorators/Enum.d.ts +1 -1
  12. package/decorators/Formula.d.ts +1 -2
  13. package/decorators/Indexed.d.ts +9 -7
  14. package/decorators/Indexed.js +1 -1
  15. package/decorators/ManyToMany.d.ts +2 -2
  16. package/decorators/ManyToOne.d.ts +4 -2
  17. package/decorators/OneToMany.d.ts +4 -4
  18. package/decorators/OneToOne.d.ts +3 -1
  19. package/decorators/PrimaryKey.d.ts +2 -3
  20. package/decorators/Property.d.ts +1 -1
  21. package/drivers/IDatabaseDriver.d.ts +4 -1
  22. package/entity/ArrayCollection.d.ts +1 -1
  23. package/entity/ArrayCollection.js +11 -4
  24. package/entity/Collection.d.ts +1 -2
  25. package/entity/Collection.js +14 -9
  26. package/entity/EntityFactory.js +4 -1
  27. package/entity/EntityHelper.js +3 -0
  28. package/entity/EntityLoader.d.ts +3 -2
  29. package/entity/EntityLoader.js +20 -6
  30. package/entity/Reference.d.ts +3 -7
  31. package/enums.d.ts +1 -1
  32. package/events/EventSubscriber.d.ts +3 -1
  33. package/exports.d.ts +24 -0
  34. package/exports.js +23 -0
  35. package/hydration/ObjectHydrator.js +1 -0
  36. package/index.d.ts +1 -1
  37. package/metadata/EntitySchema.d.ts +6 -4
  38. package/metadata/EntitySchema.js +23 -17
  39. package/metadata/MetadataDiscovery.js +23 -10
  40. package/metadata/MetadataValidator.js +4 -3
  41. package/package.json +5 -4
  42. package/types/BigIntType.d.ts +1 -0
  43. package/types/BigIntType.js +3 -0
  44. package/typings.d.ts +17 -10
  45. package/typings.js +3 -0
  46. package/unit-of-work/ChangeSetPersister.js +7 -1
  47. package/unit-of-work/UnitOfWork.js +25 -9
  48. package/utils/AbstractSchemaGenerator.js +5 -2
  49. package/utils/Configuration.d.ts +1 -1
  50. package/utils/Configuration.js +1 -1
  51. package/utils/Cursor.d.ts +3 -3
  52. package/utils/DataloaderUtils.d.ts +1 -1
  53. package/utils/DataloaderUtils.js +19 -5
  54. package/utils/EntityComparator.js +65 -49
  55. package/utils/QueryHelper.js +1 -1
  56. package/utils/RawQueryFragment.js +6 -1
  57. package/utils/Utils.d.ts +6 -0
  58. package/utils/Utils.js +23 -3
package/typings.d.ts CHANGED
@@ -28,6 +28,7 @@ export type EntityKey<T = unknown, B extends boolean = false> = string & {
28
28
  [K in keyof T]-?: CleanKeys<T, K, B> extends never ? never : K;
29
29
  }[keyof T];
30
30
  export type EntityValue<T> = T[EntityKey<T>];
31
+ export type EntityDataValue<T> = EntityData<T>[EntityKey<T>];
31
32
  export type FilterKey<T> = keyof FilterQuery<T>;
32
33
  export type AsyncFunction<R = any, T = Dictionary> = (args: T) => Promise<T>;
33
34
  export type Compute<T> = {
@@ -176,7 +177,7 @@ export interface IWrappedEntityInternal<Entity extends object> extends IWrappedE
176
177
  __touched: boolean;
177
178
  __originalEntityData?: EntityData<Entity>;
178
179
  __loadedProperties: Set<string>;
179
- __identifier?: EntityIdentifier;
180
+ __identifier?: EntityIdentifier | EntityIdentifier[];
180
181
  __managed: boolean;
181
182
  __processing: boolean;
182
183
  __schema?: string;
@@ -205,7 +206,7 @@ export type EntityName<T> = string | EntityClass<T> | EntitySchema<T, any> | {
205
206
  };
206
207
  export type GetRepository<Entity extends {
207
208
  [k: PropertyKey]: any;
208
- }, Fallback> = Entity[typeof EntityRepositoryType] extends EntityRepository<Entity> | undefined ? NonNullable<Entity[typeof EntityRepositoryType]> : Fallback;
209
+ }, Fallback> = Entity[typeof EntityRepositoryType] extends EntityRepository<any> | undefined ? NonNullable<Entity[typeof EntityRepositoryType]> : Fallback;
209
210
  export type EntityDataPropValue<T> = T | Primary<T>;
210
211
  type ExpandEntityProp<T, C extends boolean = false> = T extends Record<string, any> ? {
211
212
  [K in keyof T as CleanKeys<T, K>]?: EntityDataProp<ExpandProperty<T[K]>, C> | EntityDataPropValue<ExpandProperty<T[K]>> | null;
@@ -216,14 +217,17 @@ type ExpandRequiredEntityPropObject<T, I = never, C extends boolean = false> = {
216
217
  } & {
217
218
  [K in keyof T as OptionalKeys<T, K, I>]?: RequiredEntityDataProp<ExpandProperty<T[K]>, T, C> | EntityDataPropValue<ExpandProperty<T[K]>> | null | undefined;
218
219
  };
220
+ type NonArrayObject = object & {
221
+ [Symbol.iterator]?: never;
222
+ };
219
223
  export type EntityDataProp<T, C extends boolean> = T extends Date ? string | Date : T extends Scalar ? T : T extends {
220
224
  __runtime?: infer Runtime;
221
225
  __raw?: infer Raw;
222
- } ? (C extends true ? Raw : Runtime) : T extends Reference<infer U> ? EntityDataNested<U, C> : T extends ScalarReference<infer U> ? EntityDataProp<U, C> : T extends Collection<infer U, any> ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : T extends readonly (infer U)[] ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : EntityDataNested<T, C>;
226
+ } ? (C extends true ? Raw : Runtime) : T extends Reference<infer U> ? EntityDataNested<U, C> : T extends ScalarReference<infer U> ? EntityDataProp<U, C> : T extends Collection<infer U, any> ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : U[] | EntityDataNested<U, C>[] : EntityDataNested<T, C>;
223
227
  export type RequiredEntityDataProp<T, O, C extends boolean> = T extends Date ? string | Date : T extends Scalar ? T : T extends {
224
228
  __runtime?: infer Runtime;
225
229
  __raw?: infer Raw;
226
- } ? (C extends true ? Raw : Runtime) : T extends Reference<infer U> ? RequiredEntityDataNested<U, O, C> : T extends ScalarReference<infer U> ? RequiredEntityDataProp<U, O, C> : T extends Collection<infer U, any> ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : T extends readonly (infer U)[] ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : RequiredEntityDataNested<T, O, C>;
230
+ } ? (C extends true ? Raw : Runtime) : T extends Reference<infer U> ? RequiredEntityDataNested<U, O, C> : T extends ScalarReference<infer U> ? RequiredEntityDataProp<U, O, C> : T extends Collection<infer U, any> ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : U[] | RequiredEntityDataNested<U, O, C>[] : RequiredEntityDataNested<T, O, C>;
227
231
  export type EntityDataNested<T, C extends boolean = false> = T extends undefined ? never : T extends any[] ? Readonly<T> : EntityData<T, C> | ExpandEntityProp<T, C>;
228
232
  type EntityDataItem<T, C extends boolean> = C extends false ? T | EntityDataProp<T, C> | null : EntityDataProp<T, C> | null;
229
233
  export type RequiredEntityDataNested<T, O, C extends boolean> = T extends any[] ? Readonly<T> : RequiredEntityData<T, O> | ExpandRequiredEntityProp<T, O, C>;
@@ -240,7 +244,7 @@ type IsOptional<T, K extends keyof T, I> = T[K] extends Collection<any, any> ? t
240
244
  type RequiredKeys<T, K extends keyof T, I> = IsOptional<T, K, I> extends false ? CleanKeys<T, K> : never;
241
245
  type OptionalKeys<T, K extends keyof T, I> = IsOptional<T, K, I> extends false ? never : CleanKeys<T, K>;
242
246
  export type EntityData<T, C extends boolean = false> = {
243
- [K in EntityKey<T>]?: EntityDataItem<T[K], C>;
247
+ [K in EntityKey<T>]?: EntityDataItem<T[K] & {}, C>;
244
248
  };
245
249
  export type RequiredEntityData<T, I = never, C extends boolean = false> = {
246
250
  [K in keyof T as RequiredKeys<T, K, I>]: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]>;
@@ -282,7 +286,7 @@ type PrimaryOrObject<T, U, C extends TypeConfig> = PreferExplicitConfig<C, Extra
282
286
  } : Primary<U>;
283
287
  export type EntityDTOProp<E, T, C extends TypeConfig = never> = T extends Scalar ? T : T extends {
284
288
  __serialized?: infer U;
285
- } ? U : T extends LoadedReference<infer U> ? EntityDTO<U, C> : T extends Reference<infer U> ? PrimaryOrObject<E, U, C> : T extends ScalarReference<infer U> ? U : T extends LoadedCollection<infer U> ? EntityDTO<U, C>[] : T extends Collection<infer U> ? PrimaryOrObject<E, U, C>[] : T extends readonly (infer U)[] ? (T extends readonly any[] ? T : U[]) : T extends Relation<T> ? EntityDTO<T, C> : T;
289
+ } ? (IsUnknown<U> extends false ? U : T) : T extends LoadedReference<infer U> ? EntityDTO<U, C> : T extends Reference<infer U> ? PrimaryOrObject<E, U, C> : T extends ScalarReference<infer U> ? U : T extends LoadedCollection<infer U> ? EntityDTO<U, C>[] : T extends Collection<infer U> ? PrimaryOrObject<E, U, C>[] : T extends readonly (infer U)[] ? (T extends readonly any[] ? T : U[]) : T extends Relation<T> ? EntityDTO<T, C> : T;
286
290
  type DTOProbablyOptionalProps<T> = NonNullable<NullableKeys<T, undefined>>;
287
291
  type DTOIsOptional<T, K extends keyof T> = T[K] extends LoadedCollection<any> ? false : K extends PrimaryProperty<T> ? false : K extends DTOProbablyOptionalProps<T> ? true : false;
288
292
  type DTORequiredKeys<T, K extends keyof T> = DTOIsOptional<T, K> extends false ? ExcludeHidden<T, K> & CleanKeys<T, K> : never;
@@ -292,7 +296,9 @@ export type EntityDTO<T, C extends TypeConfig = never> = {
292
296
  } & {
293
297
  [K in keyof T as DTOOptionalKeys<T, K>]?: EntityDTOProp<T, T[K], C> | AddOptional<T[K]>;
294
298
  };
295
- export type CheckCallback<T> = (columns: Record<keyof T, string>) => string;
299
+ type TargetKeys<T> = T extends EntityClass<infer P> ? keyof P : keyof T;
300
+ type CheckKey<T> = IsUnknown<T> extends false ? TargetKeys<T> : string;
301
+ export type CheckCallback<T> = (columns: Record<CheckKey<T>, string>) => string;
296
302
  export type GeneratedColumnCallback<T> = (columns: Record<keyof T, string>) => string;
297
303
  export interface CheckConstraint<T = any> {
298
304
  name?: string;
@@ -388,6 +394,7 @@ export interface EntityProperty<Owner = any, Target = any> {
388
394
  optional?: boolean;
389
395
  ignoreSchemaChanges?: ('type' | 'extra' | 'default')[];
390
396
  deferMode?: DeferMode;
397
+ foreignKeyName?: string;
391
398
  }
392
399
  export declare class EntityMetadata<T = any> {
393
400
  private static counter;
@@ -416,7 +423,7 @@ export interface EntityMetadata<T = any> {
416
423
  schema?: string;
417
424
  pivotTable?: boolean;
418
425
  virtual?: boolean;
419
- expression?: string | ((em: any, where: FilterQuery<T>, options: FindOptions<T, any, any, any>) => MaybePromise<RawQueryFragment | object | string>);
426
+ expression?: string | ((em: any, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>) => MaybePromise<RawQueryFragment | object | string>);
420
427
  discriminatorColumn?: EntityKey<T> | AnyString;
421
428
  discriminatorValue?: number | string;
422
429
  discriminatorMap?: Dictionary<string>;
@@ -681,7 +688,7 @@ export interface MigrationObject {
681
688
  }
682
689
  export type FilterDef = {
683
690
  name: string;
684
- cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>) => Dictionary | Promise<Dictionary>);
691
+ cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>, entityName?: EntityName<any>) => Dictionary | Promise<Dictionary>);
685
692
  default?: boolean;
686
693
  entity?: string[];
687
694
  args?: boolean;
@@ -726,7 +733,7 @@ export type MergeSelected<T, U, F extends string> = T extends Loaded<infer TT, i
726
733
  type MergeFields<F1 extends string, F2 extends string, P1, P2> = P1 | P2 extends '*' ? '*' : F1 | F2;
727
734
  type MergeExcludes<F extends string, E extends string> = F extends E ? never : Exclude<E, F>;
728
735
  export type MergeLoaded<T, U, P extends string, F extends string, E extends string, R extends boolean = false> = T extends Loaded<U, infer PP, infer FF, infer EE> ? string extends FF ? Loaded<T, P, F, AnyStringToNever<EE> | E> : string extends P ? Loaded<U, never, F | (FF & string), MergeExcludes<F | (FF & string), EE | E>> : Loaded<U, P | AnyStringToNever<PP>, MergeFields<F, AnyStringToNever<FF>, P, PP>, MergeExcludes<MergeFields<F, AnyStringToNever<FF>, P, PP>, (R extends true ? never : EE) | E>> : Loaded<T, P, F>;
729
- type AddOptional<T> = undefined | null extends T ? null | undefined : null extends T ? null : undefined extends T ? undefined : never;
736
+ export type AddOptional<T> = undefined | null extends T ? null | undefined : null extends T ? null : undefined extends T ? undefined : never;
730
737
  type LoadedProp<T, L extends string = never, F extends string = '*', E extends string = never> = LoadedLoadable<T, Loaded<ExtractType<T>, L, F, E>>;
731
738
  export type AddEager<T> = ExtractEagerProps<T> & string;
732
739
  export type ExpandHint<T, L extends string> = L | AddEager<T>;
package/typings.js CHANGED
@@ -123,6 +123,9 @@ export class EntityMetadata {
123
123
  const hydrator = wrapped.hydrator;
124
124
  const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
125
125
  const old = Reference.unwrapReference(wrapped.__data[prop.name]);
126
+ if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
127
+ old[prop.inversedBy].removeWithoutPropagation(this);
128
+ }
126
129
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
127
130
  // when propagation from inside hydration, we set the FK to the entity data immediately
128
131
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
@@ -210,7 +210,9 @@ export class ChangeSetPersister {
210
210
  // of using the raw value from db, we convert it back to the db value explicitly
211
211
  value = prop.customType ? prop.customType.convertToDatabaseValue(insertId, this.platform, { mode: 'serialization' }) : value;
212
212
  changeSet.payload[wrapped.__meta.primaryKeys[0]] = value;
213
- wrapped.__identifier?.setValue(value);
213
+ if (wrapped.__identifier && !Array.isArray(wrapped.__identifier)) {
214
+ wrapped.__identifier.setValue(value);
215
+ }
214
216
  }
215
217
  /**
216
218
  * Sets populate flag to new entities so they are serialized like if they were loaded from the db
@@ -338,6 +340,10 @@ export class ChangeSetPersister {
338
340
  changeSet.payload[prop.name] = value.getValue();
339
341
  return;
340
342
  }
343
+ if (Array.isArray(value) && value.every(item => item instanceof EntityIdentifier)) {
344
+ changeSet.payload[prop.name] = value.map(item => item.getValue());
345
+ return;
346
+ }
341
347
  if (prop.kind === ReferenceKind.MANY_TO_MANY && Array.isArray(value)) {
342
348
  changeSet.payload[prop.name] = value.map(val => val instanceof EntityIdentifier ? val.getValue() : val);
343
349
  return;
@@ -542,13 +542,22 @@ export class UnitOfWork {
542
542
  if (!wrapped || wrapped.__identifier || wrapped.hasPrimaryKey()) {
543
543
  return;
544
544
  }
545
- const pk = wrapped.__meta.getPrimaryProps()[0];
546
- if (pk.kind === ReferenceKind.SCALAR) {
547
- wrapped.__identifier = new EntityIdentifier();
545
+ const pks = wrapped.__meta.getPrimaryProps();
546
+ const idents = [];
547
+ for (const pk of pks) {
548
+ if (pk.kind === ReferenceKind.SCALAR) {
549
+ idents.push(new EntityIdentifier(entity[pk.name]));
550
+ }
551
+ else if (entity[pk.name]) {
552
+ this.initIdentifier(entity[pk.name]);
553
+ idents.push(helper(entity[pk.name])?.__identifier);
554
+ }
555
+ }
556
+ if (pks.length === 1) {
557
+ wrapped.__identifier = idents[0];
548
558
  }
549
- else if (entity[pk.name]) {
550
- this.initIdentifier(entity[pk.name]);
551
- wrapped.__identifier = helper(entity[pk.name])?.__identifier;
559
+ else {
560
+ wrapped.__identifier = idents;
552
561
  }
553
562
  }
554
563
  processReference(parent, prop, kind, visited, processed, idx) {
@@ -600,8 +609,15 @@ export class UnitOfWork {
600
609
  const diff = this.comparator.diffEntities(changeSet.name, copy, current);
601
610
  Object.assign(changeSet.payload, diff);
602
611
  const wrapped = helper(changeSet.entity);
603
- if (wrapped.__identifier && diff[wrapped.__meta.primaryKeys[0]]) {
604
- wrapped.__identifier.setValue(diff[wrapped.__meta.primaryKeys[0]]);
612
+ if (wrapped.__identifier) {
613
+ const idents = Utils.asArray(wrapped.__identifier);
614
+ let i = 0;
615
+ for (const pk of wrapped.__meta.primaryKeys) {
616
+ if (diff[pk]) {
617
+ idents[i].setValue(diff[pk]);
618
+ }
619
+ i++;
620
+ }
605
621
  }
606
622
  }
607
623
  postCommitCleanup() {
@@ -884,7 +900,7 @@ export class UnitOfWork {
884
900
  collectionUpdates.push(coll);
885
901
  }
886
902
  }
887
- await this.em.getDriver().syncCollections(collectionUpdates, { ctx });
903
+ await this.em.getDriver().syncCollections(collectionUpdates, { ctx, schema: this.em.schema });
888
904
  for (const coll of this.collectionUpdates) {
889
905
  coll.takeSnapshot();
890
906
  }
@@ -32,7 +32,7 @@ export class AbstractSchemaGenerator {
32
32
  }
33
33
  else {
34
34
  await this.ensureDatabase();
35
- await this.dropSchema();
35
+ await this.dropSchema(options);
36
36
  }
37
37
  if (options?.createSchema !== false) {
38
38
  await this.createSchema(options);
@@ -103,7 +103,10 @@ export class AbstractSchemaGenerator {
103
103
  }
104
104
  return calc.sort()
105
105
  .map(cls => this.metadata.find(cls))
106
- .filter(meta => schema ? [schema, '*'].includes(meta.schema) : meta.schema !== '*');
106
+ .filter(meta => {
107
+ const targetSchema = meta.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
108
+ return schema ? [schema, '*'].includes(targetSchema) : meta.schema !== '*';
109
+ });
107
110
  }
108
111
  notImplemented() {
109
112
  throw new Error(`This method is not supported by ${this.driver.constructor.name} driver`);
@@ -105,7 +105,7 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
105
105
  ignoreSchema: never[];
106
106
  };
107
107
  embeddables: {
108
- prefixMode: "absolute";
108
+ prefixMode: "relative";
109
109
  };
110
110
  entityGenerator: {
111
111
  forceUndefined: true;
@@ -95,7 +95,7 @@ export class Configuration {
95
95
  ignoreSchema: [],
96
96
  },
97
97
  embeddables: {
98
- prefixMode: 'absolute',
98
+ prefixMode: 'relative',
99
99
  },
100
100
  entityGenerator: {
101
101
  forceUndefined: true,
package/utils/Cursor.d.ts CHANGED
@@ -49,13 +49,13 @@ import { type QueryOrder } from '../enums.js';
49
49
  * }
50
50
  * ```
51
51
  */
52
- export declare class Cursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never> {
52
+ export declare class Cursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true> {
53
53
  readonly items: Loaded<Entity, Hint, Fields, Excludes>[];
54
- readonly totalCount: number;
54
+ readonly totalCount: IncludeCount extends true ? number : undefined;
55
55
  readonly hasPrevPage: boolean;
56
56
  readonly hasNextPage: boolean;
57
57
  private readonly definition;
58
- constructor(items: Loaded<Entity, Hint, Fields, Excludes>[], totalCount: number, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>, meta: EntityMetadata<Entity>);
58
+ constructor(items: Loaded<Entity, Hint, Fields, Excludes>[], totalCount: IncludeCount extends true ? number : undefined, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, meta: EntityMetadata<Entity>);
59
59
  get startCursor(): string | null;
60
60
  get endCursor(): string | null;
61
61
  /**
@@ -1,7 +1,7 @@
1
+ import type DataLoader from 'dataloader';
1
2
  import type { Primary, Ref } from '../typings.js';
2
3
  import { Collection, type InitCollectionOptions } from '../entity/Collection.js';
3
4
  import { type EntityManager } from '../EntityManager.js';
4
- import type DataLoader from 'dataloader';
5
5
  import { type LoadReferenceOptions } from '../entity/Reference.js';
6
6
  export declare class DataloaderUtils {
7
7
  /**
@@ -2,6 +2,7 @@ import { Collection } from '../entity/Collection.js';
2
2
  import { helper } from '../entity/wrap.js';
3
3
  import { ReferenceKind } from '../enums.js';
4
4
  import { Reference } from '../entity/Reference.js';
5
+ import { Utils } from './Utils.js';
5
6
  export class DataloaderUtils {
6
7
  /**
7
8
  * Groups identified references by entity and returns a Map with the
@@ -118,7 +119,6 @@ export class DataloaderUtils {
118
119
  // We need to populate the inverse side of the relationship in order to be able to later retrieve the PK(s) from its item(s)
119
120
  populate: [
120
121
  ...(opts.populate === false ? [] : opts.populate ?? []),
121
- ...(opts.ref ? [':ref'] : []),
122
122
  ...Array.from(filterMap.keys()).filter(
123
123
  // We need to do so only if the inverse side is a collection, because we can already retrieve the PK from a reference without having to load it
124
124
  prop => em.getMetadata(className).properties[prop]?.ref !== true),
@@ -149,10 +149,6 @@ export class DataloaderUtils {
149
149
  else if (target) {
150
150
  return target === collection.owner;
151
151
  }
152
- // FIXME https://github.com/mikro-orm/mikro-orm/issues/6031
153
- if (!target && collection.property.kind === ReferenceKind.MANY_TO_MANY) {
154
- throw new Error(`Inverse side is required for M:N relations with dataloader: ${collection.owner.constructor.name}.${collection.property.name}`);
155
- }
156
152
  return false;
157
153
  };
158
154
  }
@@ -162,6 +158,24 @@ export class DataloaderUtils {
162
158
  */
163
159
  static getColBatchLoadFn(em) {
164
160
  return async (collsWithOpts) => {
161
+ const prop = collsWithOpts[0][0].property;
162
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
163
+ const options = {};
164
+ const wrap = (cond) => ({ [prop.name]: cond });
165
+ const orderBy = Utils.asArray(collsWithOpts[0][1]?.orderBy).map(o => wrap(o));
166
+ const populate = wrap(collsWithOpts[0][1]?.populate);
167
+ const owners = collsWithOpts.map(c => c[0].owner);
168
+ const $or = [];
169
+ // a bit of a hack, but we need to prefix the key, since we have only a column name, not a property name
170
+ const alias = em.config.getNamingStrategy().aliasName(prop.pivotEntity, 0);
171
+ const fk = `${alias}.${Utils.getPrimaryKeyHash(prop.joinColumns)}`;
172
+ for (const c of collsWithOpts) {
173
+ $or.push({ $and: [c[1]?.where ?? {}, { [fk]: c[0].owner }] });
174
+ options.refresh ??= c[1]?.refresh;
175
+ }
176
+ options.where = wrap({ $or });
177
+ return em.getEntityLoader().findChildrenFromPivotTable(owners, prop, options, orderBy, populate, collsWithOpts[0][1]?.ref);
178
+ }
165
179
  const entitiesAndOptsMap = DataloaderUtils.groupInversedOrMappedKeysByEntityAndOpts(collsWithOpts);
166
180
  const promises = DataloaderUtils.entitiesAndOptsMapToQueries(entitiesAndOptsMap, em);
167
181
  const resultsMap = new Map(await Promise.all(promises));
@@ -74,7 +74,7 @@ export class EntityComparator {
74
74
  lines.push(` if (entity${this.wrap(pk)} != null && (entity${this.wrap(pk)}.__entity || entity${this.wrap(pk)}.__reference)) {`);
75
75
  lines.push(` const pk = entity${this.wrap(pk)}.__helper.getPrimaryKey();`);
76
76
  if (meta.properties[pk].targetMeta.compositePK) {
77
- lines.push(` if (typeof pk === 'object') {`);
77
+ lines.push(` if (typeof pk === 'object' && pk != null) {`);
78
78
  lines.push(` return [`);
79
79
  for (const childPK of meta.properties[pk].targetMeta.primaryKeys) {
80
80
  lines.push(` pk${this.wrap(childPK)},`);
@@ -286,56 +286,72 @@ export class EntityComparator {
286
286
  lines.push(`${padding} }`);
287
287
  };
288
288
  lines.push(` const mapped = {};`);
289
- meta.props.forEach(prop => {
290
- if (!prop.fieldNames) {
291
- return;
292
- }
293
- if (prop.targetMeta && prop.fieldNames.length > 1) {
294
- lines.push(` if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
295
- lines.push(` } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
296
- lines.push(` ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
297
- lines.push(...prop.fieldNames.map(field => ` ${this.propName(field, 'mapped')} = true;`));
298
- lines.push(` } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
299
- lines.push(...prop.fieldNames.map(field => ` ${this.propName(field, 'mapped')} = true;`), ' }');
300
- return;
301
- }
302
- if (prop.embedded && (meta.embeddable || meta.properties[prop.embedded[0]].object)) {
303
- return;
304
- }
305
- if (prop.runtimeType === 'boolean') {
306
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
307
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : !!${this.propName(prop.fieldNames[0])};`);
308
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
309
- lines.push(` }`);
310
- }
311
- else if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
312
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
313
- context.set('parseDate', (value) => this.platform.parseDate(value));
314
- parseDate('ret' + this.wrap(prop.name), this.propName(prop.fieldNames[0]));
315
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
316
- lines.push(` }`);
317
- }
318
- else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
319
- const idx = this.tmpIndex++;
320
- context.set(`mapEmbeddedResult_${idx}`, (data) => {
321
- const item = parseJsonSafe(data);
322
- if (Array.isArray(item)) {
323
- return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
324
- }
325
- return item == null ? item : this.getResultMapper(prop.type)(item);
326
- });
327
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
328
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
329
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
330
- lines.push(` }`);
289
+ const mapEntityProperties = (meta, padding = '') => {
290
+ for (const prop of meta.props) {
291
+ if (!prop.fieldNames) {
292
+ continue;
293
+ }
294
+ if (prop.targetMeta && prop.fieldNames.length > 1) {
295
+ lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
296
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
297
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
298
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
299
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
300
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
301
+ continue;
302
+ }
303
+ if (prop.embedded && (meta.embeddable || meta.properties[prop.embedded[0]].object)) {
304
+ continue;
305
+ }
306
+ if (prop.runtimeType === 'boolean') {
307
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
308
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : !!${this.propName(prop.fieldNames[0])};`);
309
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
310
+ lines.push(`${padding} }`);
311
+ }
312
+ else if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
313
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
314
+ context.set('parseDate', (value) => this.platform.parseDate(value));
315
+ parseDate('ret' + this.wrap(prop.name), this.propName(prop.fieldNames[0]), padding);
316
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
317
+ lines.push(`${padding} }`);
318
+ }
319
+ else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
320
+ const idx = this.tmpIndex++;
321
+ context.set(`mapEmbeddedResult_${idx}`, (data) => {
322
+ const item = parseJsonSafe(data);
323
+ if (Array.isArray(item)) {
324
+ return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
325
+ }
326
+ return item == null ? item : this.getResultMapper(prop.type)(item);
327
+ });
328
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
329
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
330
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
331
+ lines.push(`${padding} }`);
332
+ }
333
+ else if (prop.kind !== ReferenceKind.EMBEDDED) {
334
+ lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
335
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])};`);
336
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
337
+ lines.push(`${padding} }`);
338
+ }
331
339
  }
332
- else if (prop.kind !== ReferenceKind.EMBEDDED) {
333
- lines.push(` if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
334
- lines.push(` ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])};`);
335
- lines.push(` ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
340
+ };
341
+ if (meta.polymorphs && meta.discriminatorColumn) {
342
+ for (const polymorph of meta.polymorphs) {
343
+ const first = polymorph === meta.polymorphs[0];
344
+ lines.push(` ${first ? '' : 'else '}if (${this.propName(meta.discriminatorColumn)} == '${polymorph.discriminatorValue}') {`);
345
+ mapEntityProperties(polymorph, ' ');
336
346
  lines.push(` }`);
337
347
  }
338
- });
348
+ lines.push(` else {`);
349
+ mapEntityProperties(meta, ' ');
350
+ lines.push(` }`);
351
+ }
352
+ else {
353
+ mapEntityProperties(meta);
354
+ }
339
355
  lines.push(` for (let k in result) { if (Object.hasOwn(result, k) && !mapped[k]) ret[k] = result[k]; }`);
340
356
  const code = `// compiled mapper for entity ${meta.className}\n`
341
357
  + `return function(result) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
@@ -591,7 +607,7 @@ export class EntityComparator {
591
607
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
592
608
  */
593
609
  static isComparable(prop, root) {
594
- const virtual = prop.persist === false || prop.generated;
610
+ const virtual = prop.persist === false || (prop.generated && !prop.primary);
595
611
  const inverse = prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
596
612
  const discriminator = prop.name === root.discriminatorColumn;
597
613
  const collection = prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY;
@@ -45,7 +45,7 @@ export class QueryHelper {
45
45
  return false;
46
46
  }
47
47
  if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
48
- return !!key && !GroupOperator[key] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
48
+ return !!key && !GroupOperator[key] && key !== '$not' && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
49
49
  if (Utils.isOperator(v, false)) {
50
50
  return false;
51
51
  }
@@ -17,7 +17,12 @@ export class RawQueryFragment {
17
17
  this.#key = `[raw]: ${this.sql} (#${RawQueryFragment.#index++})`;
18
18
  }
19
19
  as(alias) {
20
- return new RawQueryFragment(`${this.sql} as ${alias}`, this.params);
20
+ // TODO: to be removed in v7
21
+ /* istanbul ignore next */
22
+ if (alias.startsWith('`') || alias.startsWith('"')) {
23
+ return new RawQueryFragment(`${this.sql} as ${alias}`, this.params);
24
+ }
25
+ return new RawQueryFragment(`${this.sql} as ??`, [...this.params, alias]);
21
26
  }
22
27
  valueOf() {
23
28
  throw new Error(`Trying to modify raw SQL fragment: '${this.sql}'`);
package/utils/Utils.d.ts CHANGED
@@ -230,6 +230,12 @@ export declare class Utils {
230
230
  * @param [from] Location to start the node resolution
231
231
  */
232
232
  static requireFrom<T extends Dictionary>(id: string, from?: string): T;
233
+ /**
234
+ * Resolve path to a module.
235
+ * @param id The module to require
236
+ * @param [from] Location to start the node resolution
237
+ */
238
+ static resolveModulePath(id: string, from?: string): string;
233
239
  static dynamicImport<T = any>(id: string): Promise<T>;
234
240
  static setDynamicImportProvider(provider: (id: string) => Promise<unknown>): void;
235
241
  static ensureDir(path: string): void;
package/utils/Utils.js CHANGED
@@ -625,8 +625,12 @@ export class Utils {
625
625
  || !!process.env.TS_JEST // check if ts-jest is used (works only with v27.0.4+)
626
626
  || !!process.env.VITEST // check if vitest is used
627
627
  || !!process.versions.bun // check if bun is used
628
- || process.argv.slice(1).some(arg => arg.includes('ts-node')) // registering ts-node runner
629
- || process.execArgv.some(arg => arg === 'ts-node/esm'); // check for ts-node/esm module loader
628
+ || process.argv.slice(1).some(arg => arg.match(/\.([mc]?ts|tsx)$/)) // executing `.ts` file
629
+ || process.execArgv.some(arg => {
630
+ return arg.includes('ts-node') // check for ts-node loader
631
+ || arg.includes('@swc-node/register') // check for swc-node/register loader
632
+ || arg.includes('node_modules/tsx/'); // check for tsx loader
633
+ });
630
634
  }
631
635
  /**
632
636
  * Uses some dark magic to get source path to caller where decorator is used.
@@ -644,7 +648,8 @@ export class Utils {
644
648
  // but those are also present in node, so we need to check this only if they weren't found.
645
649
  if (line === -1) {
646
650
  // here we handle bun which stack is different from nodejs so we search for reflect-metadata
647
- const reflectLine = stack.findIndex(line => Utils.normalizePath(line).includes('node_modules/reflect-metadata/Reflect.js'));
651
+ // Different bun versions might have different stack traces. The "last index" works for both 1.2.6 and 1.2.7.
652
+ const reflectLine = stack.findLastIndex(line => Utils.normalizePath(line).includes('node_modules/reflect-metadata/Reflect.js'));
648
653
  if (reflectLine === -1 || reflectLine + 2 >= stack.length || !stack[reflectLine + 1].includes('bun:wrap')) {
649
654
  return name;
650
655
  }
@@ -863,6 +868,21 @@ export class Utils {
863
868
  }
864
869
  return createRequire(resolve(from))(id);
865
870
  }
871
+ /**
872
+ * Resolve path to a module.
873
+ * @param id The module to require
874
+ * @param [from] Location to start the node resolution
875
+ */
876
+ static resolveModulePath(id, from = process.cwd()) {
877
+ if (!extname(from)) {
878
+ from = join(from, '__fake.js');
879
+ }
880
+ const path = Utils.normalizePath(createRequire(resolve(from)).resolve(id));
881
+ const parts = path.split('/');
882
+ const idx = parts.lastIndexOf(id) + 1;
883
+ parts.splice(idx, parts.length - idx);
884
+ return parts.join('/');
885
+ }
866
886
  static async dynamicImport(id) {
867
887
  /* v8 ignore next 7 */
868
888
  if (platform() === 'win32') {