@mikro-orm/core 7.0.0-dev.8 → 7.0.0-dev.9

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.
@@ -171,6 +171,10 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
171
171
  * });
172
172
  * ```
173
173
  *
174
+ * The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
175
+ * returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
176
+ * of pages.
177
+ *
174
178
  * The `Cursor` object provides the following interface:
175
179
  *
176
180
  * ```ts
@@ -180,7 +184,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
180
184
  * User { ... },
181
185
  * User { ... },
182
186
  * ],
183
- * totalCount: 50,
187
+ * totalCount: 50, // not included if `includeCount: false`
184
188
  * startCursor: 'WzRd',
185
189
  * endCursor: 'WzZd',
186
190
  * hasPrevPage: true,
@@ -188,7 +192,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
188
192
  * }
189
193
  * ```
190
194
  */
191
- findByCursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>): Promise<Cursor<Entity, Hint, Fields, Excludes>>;
195
+ findByCursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
192
196
  /**
193
197
  * Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been
194
198
  * persisted. Returns the same entity instance (same object reference), but re-hydrated. If the entity is no longer
@@ -419,7 +423,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
419
423
  /**
420
424
  * Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
421
425
  */
422
- populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entities: Entity, populate: AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
426
+ populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entities: Entity, populate: readonly AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
423
427
  /**
424
428
  * Returns new EntityManager instance with its own identity map
425
429
  */
package/EntityManager.js CHANGED
@@ -433,6 +433,10 @@ export class EntityManager {
433
433
  * });
434
434
  * ```
435
435
  *
436
+ * The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
437
+ * returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
438
+ * of pages.
439
+ *
436
440
  * The `Cursor` object provides the following interface:
437
441
  *
438
442
  * ```ts
@@ -442,7 +446,7 @@ export class EntityManager {
442
446
  * User { ... },
443
447
  * User { ... },
444
448
  * ],
445
- * totalCount: 50,
449
+ * totalCount: 50, // not included if `includeCount: false`
446
450
  * startCursor: 'WzRd',
447
451
  * endCursor: 'WzZd',
448
452
  * hasPrevPage: true,
@@ -457,7 +461,9 @@ export class EntityManager {
457
461
  if (Utils.isEmpty(options.orderBy)) {
458
462
  throw new Error('Explicit `orderBy` option required');
459
463
  }
460
- const [entities, count] = await em.findAndCount(entityName, where, options);
464
+ const [entities, count] = options.includeCount !== false
465
+ ? await em.findAndCount(entityName, where, options)
466
+ : [await em.find(entityName, where, options)];
461
467
  return new Cursor(entities, count, options, this.metadata.get(entityName));
462
468
  }
463
469
  /**
@@ -149,7 +149,8 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
149
149
  /** @internal used to apply filters to the auto-joined relations */
150
150
  em?: EntityManager;
151
151
  }
152
- export interface FindByCursorOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never> extends Omit<FindOptions<T, P, F, E>, 'limit' | 'offset'> {
152
+ export interface FindByCursorOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never, I extends boolean = true> extends Omit<FindOptions<T, P, F, E>, 'limit' | 'offset'> {
153
+ includeCount?: I;
153
154
  }
154
155
  export interface FindOneOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never> extends Omit<FindOptions<T, P, F, E>, 'limit' | 'lockMode'> {
155
156
  lockMode?: LockMode;
@@ -45,6 +45,7 @@ export class EntityFactory {
45
45
  let wrapped = exists && helper(exists);
46
46
  if (wrapped && !options.refresh) {
47
47
  wrapped.__processing = true;
48
+ Utils.dropUndefinedProperties(data);
48
49
  this.mergeData(meta2, exists, data, options);
49
50
  wrapped.__processing = false;
50
51
  if (wrapped.isInitialized()) {
@@ -146,6 +146,9 @@ export class EntityHelper {
146
146
  set(val) {
147
147
  const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
148
148
  const old = Reference.unwrapReference(wrapped.__data[prop.name]);
149
+ if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
150
+ old[prop.inversedBy].removeWithoutPropagation(this);
151
+ }
149
152
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
150
153
  // when propagation from inside hydration, we set the FK to the entity data immediately
151
154
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
@@ -314,9 +314,16 @@ export class EntityLoader {
314
314
  const innerOrderBy = Utils.asArray(options.orderBy)
315
315
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
316
316
  .map(orderBy => orderBy[prop.name]);
317
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
317
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
318
318
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
319
- const filtered = Utils.unique(children.filter(e => !options.visited.has(e)));
319
+ const visited = options.visited;
320
+ for (const entity of entities) {
321
+ visited.delete(entity);
322
+ }
323
+ const filtered = Utils.unique(children.filter(e => !visited.has(e)));
324
+ for (const entity of entities) {
325
+ visited.add(entity);
326
+ }
320
327
  await this.populate(prop.type, filtered, populate.children ?? populate.all, {
321
328
  where: await this.extractChildCondition(options, prop, false),
322
329
  orderBy: innerOrderBy,
@@ -329,6 +336,7 @@ export class EntityLoader {
329
336
  populateWhere,
330
337
  connectionType,
331
338
  logging,
339
+ schema,
332
340
  // @ts-ignore not a public option, will be propagated to the populate call
333
341
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
334
342
  // @ts-ignore not a public option, will be propagated to the populate call
@@ -518,7 +526,7 @@ export class EntityLoader {
518
526
  if (refresh) {
519
527
  return entities;
520
528
  }
521
- return entities.filter(e => !e[field]?.__helper?.__initialized);
529
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
522
530
  }
523
531
  lookupAllRelationships(entityName) {
524
532
  const ret = [];
@@ -1,5 +1,5 @@
1
1
  import { inspect } from 'node:util';
2
- import type { AddEager, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
2
+ import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
3
3
  import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
4
4
  export declare class Reference<T extends object> {
5
5
  private entity;
@@ -72,15 +72,11 @@ export interface LoadReferenceOrFailOptions<T extends object, P extends string =
72
72
  /**
73
73
  * shortcut for `wrap(entity).toReference()`
74
74
  */
75
- export declare function ref<T>(entity: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
75
+ export declare function ref<I extends unknown | Ref<unknown> | undefined | null, T extends I & {}>(entity: I): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>> | AddOptional<typeof entity>;
76
76
  /**
77
77
  * shortcut for `Reference.createFromPK(entityType, pk)`
78
78
  */
79
- export declare function ref<T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk?: T | PKV): Ref<T>;
80
- /**
81
- * shortcut for `wrap(entity).toReference()`
82
- */
83
- export declare function ref<T>(value: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
79
+ export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk: I): Ref<T> | AddOptional<typeof pk>;
84
80
  /**
85
81
  * shortcut for `Reference.createNakedFromPK(entityType, pk)`
86
82
  */
@@ -256,6 +256,7 @@ export class ObjectHydrator extends Hydrator {
256
256
  ...prop2,
257
257
  name: childProp.name,
258
258
  embedded: childProp.embedded,
259
+ embeddedProps: childProp.embeddedProps,
259
260
  };
260
261
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
261
262
  ret.push(...hydrateProperty(prop3, childProp.object, [...path, childProp.embedded[1]], childDataKey).map(l => ' ' + l));
@@ -7,7 +7,7 @@ import { MetadataStorage } from './MetadataStorage.js';
7
7
  import { EntitySchema } from './EntitySchema.js';
8
8
  import { Cascade, ReferenceKind } from '../enums.js';
9
9
  import { MetadataError } from '../errors.js';
10
- import { ArrayType, BigIntType, BlobType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
10
+ import { ArrayType, BigIntType, BlobType, DateType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
11
11
  import { colors } from '../logging/colors.js';
12
12
  import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
13
13
  export class MetadataDiscovery {
@@ -765,6 +765,7 @@ export class MetadataDiscovery {
765
765
  delete prop.default;
766
766
  if (properties[prop.name] && properties[prop.name].type !== prop.type) {
767
767
  properties[prop.name].type = `${properties[prop.name].type} | ${prop.type}`;
768
+ properties[prop.name].runtimeType = 'any';
768
769
  return properties[prop.name];
769
770
  }
770
771
  return properties[prop.name] = prop;
@@ -924,7 +925,7 @@ export class MetadataDiscovery {
924
925
  }
925
926
  let i = 1;
926
927
  Object.values(meta.properties).forEach(prop => {
927
- const newProp = Utils.copy(prop, false);
928
+ const newProp = { ...prop };
928
929
  if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
929
930
  const name = newProp.name;
930
931
  this.initFieldName(newProp, newProp.object);
@@ -1134,7 +1135,7 @@ export class MetadataDiscovery {
1134
1135
  }
1135
1136
  const mappedType = this.getMappedType(prop);
1136
1137
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1137
- [BigIntType, DoubleType, DecimalType, IntervalType]
1138
+ [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1138
1139
  .filter(type => mappedType instanceof type)
1139
1140
  .forEach(type => prop.customType = new type());
1140
1141
  }
@@ -100,14 +100,15 @@ export class MetadataValidator {
100
100
  if (!prop.type) {
101
101
  throw MetadataError.fromWrongTypeDefinition(meta, prop);
102
102
  }
103
+ const targetMeta = metadata.find(prop.type);
103
104
  // references do have type of known entity
104
- if (!metadata.find(prop.type)) {
105
+ if (!targetMeta) {
105
106
  throw MetadataError.fromWrongTypeDefinition(meta, prop);
106
107
  }
107
- if (metadata.find(prop.type).abstract && !metadata.find(prop.type).discriminatorColumn) {
108
+ if (targetMeta.abstract && !targetMeta.discriminatorColumn && !targetMeta.embeddable) {
108
109
  throw MetadataError.targetIsAbstract(meta, prop);
109
110
  }
110
- if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.persist === false && metadata.find(prop.type).compositePK && options.checkNonPersistentCompositeProps) {
111
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.persist === false && targetMeta.compositePK && options.checkNonPersistentCompositeProps) {
111
112
  throw MetadataError.nonPersistentCompositeProp(meta, prop);
112
113
  }
113
114
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
3
  "type": "module",
4
- "version": "7.0.0-dev.8",
4
+ "version": "7.0.0-dev.9",
5
5
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
6
6
  "exports": {
7
7
  "./package.json": "./package.json",
@@ -52,10 +52,10 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "dataloader": "2.2.3",
55
- "dotenv": "16.4.7",
55
+ "dotenv": "16.5.0",
56
56
  "esprima": "4.0.1",
57
57
  "globby": "11.1.0",
58
- "mikro-orm": "7.0.0-dev.8",
58
+ "mikro-orm": "7.0.0-dev.9",
59
59
  "reflect-metadata": "0.2.2"
60
60
  }
61
61
  }
package/typings.d.ts CHANGED
@@ -177,7 +177,7 @@ export interface IWrappedEntityInternal<Entity extends object> extends IWrappedE
177
177
  __touched: boolean;
178
178
  __originalEntityData?: EntityData<Entity>;
179
179
  __loadedProperties: Set<string>;
180
- __identifier?: EntityIdentifier;
180
+ __identifier?: EntityIdentifier | EntityIdentifier[];
181
181
  __managed: boolean;
182
182
  __processing: boolean;
183
183
  __schema?: string;
@@ -206,7 +206,7 @@ export type EntityName<T> = string | EntityClass<T> | EntitySchema<T, any> | {
206
206
  };
207
207
  export type GetRepository<Entity extends {
208
208
  [k: PropertyKey]: any;
209
- }, 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;
210
210
  export type EntityDataPropValue<T> = T | Primary<T>;
211
211
  type ExpandEntityProp<T, C extends boolean = false> = T extends Record<string, any> ? {
212
212
  [K in keyof T as CleanKeys<T, K>]?: EntityDataProp<ExpandProperty<T[K]>, C> | EntityDataPropValue<ExpandProperty<T[K]>> | null;
@@ -283,7 +283,7 @@ type PrimaryOrObject<T, U, C extends TypeConfig> = PreferExplicitConfig<C, Extra
283
283
  } : Primary<U>;
284
284
  export type EntityDTOProp<E, T, C extends TypeConfig = never> = T extends Scalar ? T : T extends {
285
285
  __serialized?: infer U;
286
- } ? 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;
286
+ } ? (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;
287
287
  type DTOProbablyOptionalProps<T> = NonNullable<NullableKeys<T, undefined>>;
288
288
  type DTOIsOptional<T, K extends keyof T> = T[K] extends LoadedCollection<any> ? false : K extends PrimaryProperty<T> ? false : K extends DTOProbablyOptionalProps<T> ? true : false;
289
289
  type DTORequiredKeys<T, K extends keyof T> = DTOIsOptional<T, K> extends false ? ExcludeHidden<T, K> & CleanKeys<T, K> : never;
@@ -729,7 +729,7 @@ export type MergeSelected<T, U, F extends string> = T extends Loaded<infer TT, i
729
729
  type MergeFields<F1 extends string, F2 extends string, P1, P2> = P1 | P2 extends '*' ? '*' : F1 | F2;
730
730
  type MergeExcludes<F extends string, E extends string> = F extends E ? never : Exclude<E, F>;
731
731
  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>;
732
- type AddOptional<T> = undefined | null extends T ? null | undefined : null extends T ? null : undefined extends T ? undefined : never;
732
+ export type AddOptional<T> = undefined | null extends T ? null | undefined : null extends T ? null : undefined extends T ? undefined : never;
733
733
  type LoadedProp<T, L extends string = never, F extends string = '*', E extends string = never> = LoadedLoadable<T, Loaded<ExtractType<T>, L, F, E>>;
734
734
  export type AddEager<T> = ExtractEagerProps<T> & string;
735
735
  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() {
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
  /**
@@ -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}`;
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
  }