@mikro-orm/core 7.2.6-dev.0 → 7.2.6-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.
package/EntityManager.js CHANGED
@@ -789,7 +789,9 @@ export class EntityManager {
789
789
  })) => {
790
790
  for (const prop of helper(e).__meta.props) {
791
791
  if (prop.kind === ReferenceKind.SCALAR && !prop.ref) {
792
- data[prop.name] = e[prop.name];
792
+ if (helper(e).__initialized || prop.primary) {
793
+ data[prop.name] = e[prop.name];
794
+ }
793
795
  continue;
794
796
  }
795
797
  if (prop.serializedName && prop.serializedName in data) {
@@ -81,6 +81,8 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
81
81
  */
82
82
  private mapCursorOffset;
83
83
  protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T>, fromJson?: boolean): FilterQuery<T>;
84
+ /** @internal Polymorphic embeddable variants can declare same-named properties with different field names. */
85
+ protected variantProps(prop: EntityProperty, data?: Dictionary): Dictionary<EntityProperty>;
84
86
  /** @internal */
85
87
  mapDataToFieldNames(data: Dictionary, stringifyJsonArrays: boolean, properties?: Record<string, EntityProperty>, convertCustomTypes?: boolean, object?: boolean): Dictionary;
86
88
  protected inlineEmbeddables<T extends object>(meta: EntityMetadata<T>, data: T, where?: boolean): void;
@@ -403,6 +403,19 @@ export class DatabaseDriver {
403
403
  }
404
404
  return { ...atOrPast, ...past };
405
405
  }
406
+ /** @internal Polymorphic embeddable variants can declare same-named properties with different field names. */
407
+ variantProps(prop, data) {
408
+ const meta = prop.targetMeta;
409
+ // eslint-disable-next-line eqeqeq
410
+ const variant = meta?.polymorphs?.find(m => m.discriminatorValue == data?.[meta.discriminatorColumn]);
411
+ const props = variant ? { ...prop.embeddedProps } : prop.embeddedProps;
412
+ for (const p of variant ? Object.values(props) : []) {
413
+ if (variant.properties[p.embedded[1]] && Utils.isSameKind(variant.properties[p.embedded[1]], p)) {
414
+ props[p.embedded[1]] = p;
415
+ }
416
+ }
417
+ return props;
418
+ }
406
419
  /** @internal */
407
420
  mapDataToFieldNames(data, stringifyJsonArrays, properties, convertCustomTypes, object) {
408
421
  if (!properties || data == null) {
@@ -417,17 +430,17 @@ export class DatabaseDriver {
417
430
  if (prop.embeddedProps && !prop.object && !object) {
418
431
  const copy = data[k];
419
432
  delete data[k];
420
- Object.assign(data, this.mapDataToFieldNames(copy, stringifyJsonArrays, prop.embeddedProps, convertCustomTypes));
433
+ Object.assign(data, this.mapDataToFieldNames(copy, stringifyJsonArrays, this.variantProps(prop, copy), convertCustomTypes));
421
434
  return;
422
435
  }
423
436
  if (prop.embeddedProps && (object || prop.object)) {
424
437
  const copy = data[k];
425
438
  delete data[k];
426
439
  if (prop.array) {
427
- data[prop.fieldNames[0]] = copy?.map((item) => this.mapDataToFieldNames(item, stringifyJsonArrays, prop.embeddedProps, convertCustomTypes, true));
440
+ data[prop.fieldNames[0]] = copy?.map((item) => this.mapDataToFieldNames(item, stringifyJsonArrays, this.variantProps(prop, item), convertCustomTypes, true));
428
441
  }
429
442
  else {
430
- data[prop.fieldNames[0]] = this.mapDataToFieldNames(copy, stringifyJsonArrays, prop.embeddedProps, convertCustomTypes, true);
443
+ data[prop.fieldNames[0]] = this.mapDataToFieldNames(copy, stringifyJsonArrays, this.variantProps(prop, copy), convertCustomTypes, true);
431
444
  }
432
445
  if (stringifyJsonArrays && prop.array && !object) {
433
446
  data[prop.fieldNames[0]] = this.platform.convertJsonToDatabaseValue(data[prop.fieldNames[0]]);
@@ -234,14 +234,26 @@ export class EntityAssigner {
234
234
  entity[propName].push(...Object.values(tmp));
235
235
  });
236
236
  }
237
+ const polymorphs = prop.targetMeta?.polymorphs;
238
+ // eslint-disable-next-line eqeqeq
239
+ const variant = polymorphs?.find(m => m.discriminatorValue == value[prop.targetMeta.discriminatorColumn]);
237
240
  const create = () => EntityAssigner.validateEM(em) &&
238
- em.getEntityFactory().createEmbeddable(prop.targetMeta.class, value, {
241
+ em.getEntityFactory().createEmbeddable((variant ?? prop.targetMeta).class, value, {
239
242
  convertCustomTypes: options.convertCustomTypes,
240
243
  newEntity: options.mergeEmbeddedProperties ? !('propName' in entity) : true,
241
244
  });
242
- entity[propName] = (options.mergeEmbeddedProperties ? entity[propName] || create() : create());
245
+ // a different polymorphic variant cannot be merged into the current one
246
+ const merge = options.mergeEmbeddedProperties && (!variant || entity[propName]?.constructor === variant.class);
247
+ entity[propName] = (merge ? entity[propName] || create() : create());
248
+ const props = polymorphs ? { ...prop.embeddedProps } : prop.embeddedProps;
249
+ // polymorphic variants can declare same-named properties of different kinds or relation targets
250
+ for (const p of polymorphs ? entity[propName].__meta.props : []) {
251
+ if (props[p.name] && props[p.name].targetMeta?.class !== p.targetMeta?.class) {
252
+ props[p.name] = { ...p, nullable: prop.nullable || p.nullable };
253
+ }
254
+ }
243
255
  Object.keys(value).forEach(key => {
244
- EntityAssigner.assignProperty(entity[propName], key, prop.embeddedProps, value, options);
256
+ EntityAssigner.assignProperty(entity[propName], key, props, value, options);
245
257
  });
246
258
  }
247
259
  static createCollectionItem(item, em, prop, invalid, options) {
@@ -24,7 +24,10 @@ export function validateProperty(prop, givenValue, entity) {
24
24
  }
25
25
  function getValue(o, prop) {
26
26
  if (prop.embedded && prop.embedded[0] in o) {
27
- return o[prop.embedded[0]]?.[prop.embedded[1]];
27
+ const embeddable = o[prop.embedded[0]];
28
+ const variantProp = embeddable?.__meta?.properties[prop.embedded[1]];
29
+ // a same-named property of a different kind in another polymorphic variant
30
+ return variantProp && !Utils.isSameKind(variantProp, prop) ? undefined : embeddable?.[prop.embedded[1]];
28
31
  }
29
32
  /* v8 ignore next */
30
33
  if (prop.ref) {
@@ -313,6 +313,10 @@ export class ObjectHydrator extends Hydrator {
313
313
  ? dataKey + this.wrap(childProp.embedded[1])
314
314
  : this.wrap(childProp.name);
315
315
  const prop2 = childMeta.properties[childProp.embedded[1]];
316
+ // a same-named property of a different kind in another variant
317
+ if (prop2 && !Utils.isSameKind(prop2, childProp)) {
318
+ return;
319
+ }
316
320
  const prop3 = {
317
321
  ...prop2,
318
322
  name: childProp.name,
@@ -463,7 +463,8 @@ export class MetadataDiscovery {
463
463
  }
464
464
  }
465
465
  initManyToOneFieldName(prop, name) {
466
- const meta2 = prop.targetMeta;
466
+ // STI property renaming runs before `initRelation` populates `targetMeta`
467
+ const meta2 = prop.targetMeta ?? this.#metadata.getByClassName(prop.type);
467
468
  const ret = [];
468
469
  // with `targetKey` on a composite PK target, derive the FK field name from that property
469
470
  // instead of the PKs (simple PK targets keep the PK based naming for backwards compatibility)
@@ -1280,16 +1281,37 @@ export class MetadataDiscovery {
1280
1281
  // the prefix of the parent cannot be a boolean; it already passed here
1281
1282
  const prefix = this.getPrefix(embeddedProp, parentProperty);
1282
1283
  const glue = object ? '~' : '_';
1283
- for (const prop of Object.values(embeddable.properties)) {
1284
- const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
1284
+ const props = Object.values(embeddable.properties);
1285
+ // variants can declare a same-named property of a different kind, the virtual entity lacks it when the variants are shared with another union
1286
+ for (const prop of embeddable.polymorphs?.flatMap(m => Object.values(m.properties)) ?? []) {
1287
+ const same = props.find(p => p.name === prop.name && Utils.isSameKind(p, prop));
1288
+ if (!same && !prop.embedded) {
1289
+ props.push({ ...prop }); // a copy, the variant's metadata is shared with its other owners
1290
+ }
1291
+ else if (same && same.kind !== ReferenceKind.SCALAR && same.type !== prop.type) {
1292
+ // a column shared by relations to different targets cannot have a FK constraint
1293
+ same.createForeignKeyConstraint = false;
1294
+ }
1295
+ }
1296
+ for (const prop of props) {
1297
+ const sameName = props.filter(p => p.name === prop.name);
1298
+ // same-kind variants of a conflicting property share one owner property, as they share the column
1299
+ const first = sameName.find(p => Utils.isSameKind(p, prop));
1300
+ const conflict = first !== sameName[0];
1301
+ const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) +
1302
+ prop.name +
1303
+ (conflict ? `_${props.indexOf(first)}` : '');
1285
1304
  meta.properties[name] = Utils.copy(prop);
1286
1305
  meta.properties[name].name = name;
1306
+ // STI column tracking between the variants does not apply to the owner's columns
1307
+ delete meta.properties[name].stiFieldNames;
1287
1308
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
1288
1309
  meta.propertyOrder.set(name, (order += 0.01));
1289
- embeddedProp.embeddedProps[prop.name] = meta.properties[name];
1310
+ embeddedProp.embeddedProps[conflict ? name : prop.name] = meta.properties[name];
1290
1311
  meta.properties[name].persist ??= embeddedProp.persist;
1291
1312
  const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
1292
- if (embeddedProp.nullable || refInArray) {
1313
+ // each variant fills only its own column of a conflicting property
1314
+ if (embeddedProp.nullable || refInArray || sameName.some(p => !Utils.isSameKind(p, prop))) {
1293
1315
  meta.properties[name].nullable = true;
1294
1316
  }
1295
1317
  // polymorphic relations derive their column names from the discriminator, so prefix it too
@@ -1462,6 +1484,13 @@ export class MetadataDiscovery {
1462
1484
  rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
1463
1485
  // subtypes that only differ in type share the column
1464
1486
  rootProp.stiFieldNames = Utils.unique([...rootProp.stiFieldNames, ...prop.fieldNames]);
1487
+ // a column shared by relations to different targets cannot have a FK constraint
1488
+ for (const p of Object.values(meta.root.properties)) {
1489
+ const shared = p.type !== newProp.type && compareArrays(p.fieldNames ?? [], newProp.fieldNames);
1490
+ if (shared && p.kind !== ReferenceKind.SCALAR && Utils.isSameKind(p, newProp)) {
1491
+ p.createForeignKeyConstraint = newProp.createForeignKeyConstraint = false;
1492
+ }
1493
+ }
1465
1494
  newProp.nullable = true;
1466
1495
  newProp.name = name;
1467
1496
  newProp.hydrate = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.2.6-dev.0",
3
+ "version": "7.2.6-dev.10",
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",
@@ -165,7 +165,8 @@ export class ChangeSetComputer {
165
165
  const targets = Utils.unwrapProperty(changeSet.entity, changeSet.meta, prop);
166
166
  targets.forEach(([rawTarget, idx]) => {
167
167
  const target = Reference.unwrapReference(rawTarget);
168
- const needsProcessing = target != null && (prop.targetKey != null || !target.__helper.hasPrimaryKey());
168
+ // a same-named scalar of another polymorphic embeddable variant is not a reference
169
+ const needsProcessing = Utils.isEntity(target) && (prop.targetKey != null || !target.__helper.hasPrimaryKey());
169
170
  if (needsProcessing) {
170
171
  // a reference known only by its `targetKey` has no PK, and is never inserted to generate one
171
172
  if (!prop.targetKey && !target.__helper.__initialized) {
@@ -531,11 +531,22 @@ export class EntityComparator {
531
531
  }
532
532
  return true;
533
533
  }
534
+ // a same-named property of a different kind in another polymorphic variant applies only to its own variants
535
+ const guard = (childProp, code) => {
536
+ const variants = prop.targetMeta?.polymorphs?.filter(m => m.properties[childProp.embedded[1]]) ?? [];
537
+ const owners = variants.filter(m => Utils.isSameKind(m.properties[childProp.embedded[1]], childProp));
538
+ if (owners.length === variants.length) {
539
+ return code;
540
+ }
541
+ const discriminator = `entity${[...path, prop.targetMeta.discriminatorColumn].map(k => this.wrap(k)).join('')}`;
542
+ const conds = owners.map(m => `${discriminator} == ${JSON.stringify(m.discriminatorValue)}`);
543
+ return `${padding} if (${conds.join(' || ')}) {\n${code}\n${padding} }`;
544
+ };
545
+ const childProps = meta.props.filter(p => p.embedded?.[0] === prop.name &&
546
+ // object for JSON embeddable
547
+ (p.object || p.persist !== false));
534
548
  ret +=
535
- meta.props
536
- .filter(p => p.embedded?.[0] === prop.name &&
537
- // object for JSON embeddable
538
- (p.object || p.persist !== false))
549
+ childProps
539
550
  .map(childProp => {
540
551
  const childDataKey = meta.embeddable || prop.object ? dataKey + this.wrap(childProp.embedded[1]) : this.wrap(childProp.name);
541
552
  const childEntityKey = [...path, childProp.embedded[1]].map(k => this.wrap(k)).join('');
@@ -558,6 +569,7 @@ export class EntityComparator {
558
569
  }
559
570
  return `${padding} if (${childCond}) ret${childDataKey} = clone(entity${childEntityKey});`;
560
571
  })
572
+ .map((code, i) => guard(childProps[i], code))
561
573
  .join('\n') + `\n`;
562
574
  if (this.shouldSerialize(prop, dataKey)) {
563
575
  return `${ret + padding} ret${dataKey} = cloneEmbeddable(ret${dataKey});\n${padding}}`;
package/utils/Utils.d.ts CHANGED
@@ -175,6 +175,8 @@ export declare class Utils {
175
175
  static getORMVersion(): string;
176
176
  static createFunction(context: Map<string, any>, code: string, compiledFunctions?: CompiledFunctions, key?: string): any;
177
177
  static callCompiledFunction<T extends unknown[], R>(fn: (...args: T) => R, ...args: T): R;
178
+ /** @internal Same-named properties of polymorphic embeddable variants conflict when their kinds differ, to-one kinds share the column. */
179
+ static isSameKind(a: EntityProperty, b: EntityProperty): boolean;
178
180
  static unwrapProperty<T>(entity: T, meta: EntityMetadata<T>, prop: EntityProperty<T>, payload?: boolean): [unknown, number[]][];
179
181
  static setPayloadProperty<T>(entity: EntityDictionary<T>, meta: EntityMetadata<T>, prop: EntityProperty<T>, value: unknown, idx: number[]): void;
180
182
  static tryImport<T extends Dictionary = any>({ module, warning, }: {
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.2.6-dev.0';
156
+ static #ORM_VERSION = '7.2.6-dev.10';
157
157
  /** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
158
158
  static getRlsSettingName(filterName, argName) {
159
159
  return `mikro.${filterName}.${argName}`;
@@ -775,6 +775,11 @@ export class Utils {
775
775
  throw e;
776
776
  }
777
777
  }
778
+ /** @internal Same-named properties of polymorphic embeddable variants conflict when their kinds differ, to-one kinds share the column. */
779
+ static isSameKind(a, b) {
780
+ const toOne = (p) => p.kind === ReferenceKind.MANY_TO_ONE || p.kind === ReferenceKind.ONE_TO_ONE;
781
+ return a.kind === b.kind || (toOne(a) && toOne(b));
782
+ }
778
783
  static unwrapProperty(entity, meta, prop, payload = false) {
779
784
  let p = prop;
780
785
  const path = [];