@mikro-orm/core 7.1.9-dev.2 → 7.1.9-dev.21

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.
@@ -364,6 +364,9 @@ export class ObjectHydrator extends Hydrator {
364
364
  ret.push(` data${dataKey}.forEach((_, idx_${idx}) => {`);
365
365
  ret.push(...hydrateEmbedded(prop, [...path, `[idx_${idx}]`], `${dataKey}[idx_${idx}]`).map(l => ' ' + l));
366
366
  ret.push(` });`);
367
+ ret.push(` } else if (data${dataKey} === null) {`);
368
+ /* v8 ignore next */
369
+ ret.push(` entity${entityKey} = ${this.config.get('forceUndefined') ? 'undefined' : 'null'};`);
367
370
  ret.push(` }`);
368
371
  return ret;
369
372
  };
@@ -10,7 +10,8 @@ export class AbstractNamingStrategy {
10
10
  classToMigrationName(timestamp, customMigrationName) {
11
11
  let migrationName = `Migration${timestamp}`;
12
12
  if (customMigrationName) {
13
- migrationName += `_${customMigrationName}`;
13
+ // the name becomes part of a class identifier
14
+ migrationName += `_${customMigrationName.replace(/[^$\p{ID_Continue}]+/gu, '_')}`;
14
15
  }
15
16
  return migrationName;
16
17
  }
@@ -9,7 +9,8 @@ export interface NamingStrategy {
9
9
  */
10
10
  classToTableName(entityName: string, tableName?: string): string;
11
11
  /**
12
- * Return a migration name. This name should allow ordering.
12
+ * Return a migration name. This name should allow ordering, and has to be a valid class identifier,
13
+ * as it is used as the class name in the generated migration file.
13
14
  */
14
15
  classToMigrationName(timestamp: string, customMigrationName?: string): string;
15
16
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.9-dev.2",
3
+ "version": "7.1.9-dev.21",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -29,15 +29,18 @@ export class ChangeSet {
29
29
  else {
30
30
  this.primaryKey = this.originalEntity[this.meta.primaryKeys[0]];
31
31
  }
32
- if (!this.meta.compositePK &&
33
- this.meta.getPrimaryProp().targetMeta?.compositePK &&
34
- typeof this.primaryKey === 'object' &&
35
- this.primaryKey !== null) {
36
- this.primaryKey = this.meta.getPrimaryProp().targetMeta.primaryKeys.map(childPK => {
37
- return this.primaryKey[childPK];
38
- });
32
+ const primaryProp = this.meta.getPrimaryProp();
33
+ const relationPK = !this.meta.compositePK && !!primaryProp.targetMeta?.compositePK;
34
+ // arrays are already the ordered tuple of the target's primary keys
35
+ if (relationPK && Utils.isPlainObject(this.primaryKey)) {
36
+ const pk = this.primaryKey;
37
+ this.primaryKey = primaryProp.targetMeta.primaryKeys.map(childPK => pk[childPK]);
39
38
  }
40
39
  if (object && this.primaryKey != null) {
40
+ // the whole tuple belongs to the single relation PK, it must not be spread over the (single) PK prop
41
+ if (relationPK) {
42
+ return { [primaryProp.name]: this.primaryKey };
43
+ }
41
44
  return Utils.primaryKeyToObject(this.meta, this.primaryKey);
42
45
  }
43
46
  return this.primaryKey ?? null;
@@ -3,7 +3,7 @@ import { PolymorphicRef } from '../entity/PolymorphicRef.js';
3
3
  import { helper } from '../entity/wrap.js';
4
4
  import { ChangeSetType } from './ChangeSet.js';
5
5
  import { isRaw } from '../utils/RawQueryFragment.js';
6
- import { Utils } from '../utils/Utils.js';
6
+ import { equals, Utils } from '../utils/Utils.js';
7
7
  import { OptimisticLockError, ValidationError } from '../errors.js';
8
8
  import { ReferenceKind } from '../enums.js';
9
9
  /** @internal Executes change sets against the database, handling inserts, updates, and deletes. */
@@ -230,11 +230,15 @@ export class ChangeSetPersister {
230
230
  }
231
231
  const res = await this.#driver.nativeUpdateMany(meta.class, cond, payload, options);
232
232
  const map = new Map();
233
- res.rows?.forEach(item => map.set(Utils.getCompositeKeyHash(item, meta, true, this.#platform, true), item));
233
+ // returning rows are not mapped yet, so they are keyed by field names - we need to build the hash
234
+ // from those to be able to match them with `getSerializedPrimaryKey()` of the entity
235
+ const pkFields = meta.getPrimaryProps().flatMap(prop => prop.fieldNames);
236
+ res.rows?.forEach(item => map.set(Utils.getPrimaryKeyHash(pkFields.map(field => item[field])), item));
234
237
  for (const changeSet of changeSets) {
235
238
  if (res.rows) {
236
239
  const row = map.get(helper(changeSet.entity).getSerializedPrimaryKey());
237
- this.mapReturnedValues(changeSet.entity, changeSet.payload, row, meta);
240
+ // STI batches can mix child types, so map through the change set's own metadata
241
+ this.mapReturnedValues(changeSet.entity, changeSet.payload, row, changeSet.meta);
238
242
  }
239
243
  changeSet.persisted = true;
240
244
  }
@@ -334,7 +338,8 @@ export class ChangeSetPersister {
334
338
  });
335
339
  const res = await this.#driver.find(meta.root.class, { $or }, options);
336
340
  if (res.length !== changeSets.length) {
337
- const compare = (a, b, keys) => keys.every(k => a[k] === b[k]);
341
+ // a FK pointing to a composite PK is an array, so the values need to be compared deeply
342
+ const compare = (a, b, keys) => keys.every(k => equals(a[k], b[k]));
338
343
  const entity = changeSets.find(cs => {
339
344
  return !res.some(row => compare(Utils.getPrimaryKeyCond(cs.entity, primaryKeys), row, primaryKeys));
340
345
  }).entity;
@@ -375,7 +380,8 @@ export class ChangeSetPersister {
375
380
  changeSets.forEach(cs => {
376
381
  Utils.keys(cs.payload).forEach(k => {
377
382
  if (isRaw(cs.payload[k]) && isRaw(cs.entity[k])) {
378
- returning.add(meta.properties[k]);
383
+ // STI batches can mix child types, so the property might not exist on `meta`
384
+ returning.add(cs.meta.properties[k]);
379
385
  }
380
386
  });
381
387
  });
@@ -400,12 +406,14 @@ export class ChangeSetPersister {
400
406
  options = this.prepareOptions(meta, options, {
401
407
  fields: Utils.unique(reloadProps.map(prop => prop.name)),
402
408
  });
403
- const data = await this.#driver.find(meta.class, { [pk]: { $in: pks } }, options);
409
+ // a mixed STI batch shares one table but the child discriminator would filter out the siblings
410
+ const target = changeSets.some(cs => cs.meta !== meta) && meta.root.discriminatorColumn ? meta.root : meta;
411
+ const data = await this.#driver.find(target.class, { [pk]: { $in: pks } }, options);
404
412
  const map = new Map();
405
413
  data.forEach(item => map.set(Utils.getCompositeKeyHash(item, meta, false, this.#platform, true), item));
406
414
  for (const changeSet of changeSets) {
407
415
  const data = map.get(helper(changeSet.entity).getSerializedPrimaryKey());
408
- this.#hydrator.hydrate(changeSet.entity, meta, data, this.#factory, 'full', false, true);
416
+ this.#hydrator.hydrate(changeSet.entity, changeSet.meta, data, this.#factory, 'full', false, true);
409
417
  Object.assign(changeSet.payload, data); // merge to the changeset payload, so it gets saved to the entity snapshot
410
418
  }
411
419
  }
@@ -85,7 +85,8 @@ const DEFAULTS = {
85
85
  safe: false,
86
86
  snapshot: true,
87
87
  emit: 'ts',
88
- fileName: (timestamp, name) => `Migration${timestamp}${name ? '_' + name : ''}`,
88
+ // mirrors `NamingStrategy.classToMigrationName`, so the file name matches the class it declares
89
+ fileName: (timestamp, name) => `Migration${timestamp}${name ? '_' + name.replace(/[^$\p{ID_Continue}]+/gu, '_') : ''}`,
89
90
  },
90
91
  schemaGenerator: {
91
92
  createForeignKeyConstraints: true,
@@ -447,6 +447,7 @@ export class EntityComparator {
447
447
  const ret = [];
448
448
  const padding = ' '.repeat(level * 2);
449
449
  const idx = this.#tmpIndex++;
450
+ ret.push(`${padding}if (entity${entityKey} === null) ret${dataKey} = null;`);
450
451
  ret.push(`${padding}if (Array.isArray(entity${entityKey})) {`);
451
452
  ret.push(`${padding} ret${dataKey} = [];`);
452
453
  ret.push(`${padding} entity${entityKey}.forEach((_, idx_${idx}) => {`);
package/utils/Utils.js CHANGED
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.1.9-dev.2';
156
+ static #ORM_VERSION = '7.1.9-dev.21';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */
@@ -462,7 +462,9 @@ export class Utils {
462
462
  }
463
463
  static getPrimaryKeyCond(entity, primaryKeys) {
464
464
  const cond = primaryKeys.reduce((o, pk) => {
465
- o[pk] = Utils.extractPK(entity[pk]);
465
+ const value = entity[pk];
466
+ // FKs pointing to a composite PK are arrays, which `extractPK` rejects
467
+ o[pk] = Utils.isPrimaryKey(value, true) ? value : Utils.extractPK(value);
466
468
  return o;
467
469
  }, {});
468
470
  if (Object.values(cond).some(v => v === null)) {