@mikro-orm/core 7.1.14 → 7.1.15-dev.0

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
@@ -260,7 +260,7 @@ export class EntityManager {
260
260
  addFilter(options) {
261
261
  options = { ...options };
262
262
  if (options.entity) {
263
- options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
263
+ options.entity = Utils.asArray(options.entity).map(n => Utils.classOrName(n));
264
264
  }
265
265
  options.default ??= true;
266
266
  this.getContext(false).#filters[options.name] = options;
@@ -2116,7 +2116,11 @@ export class EntityManager {
2116
2116
  ]) {
2117
2117
  delete opts[k];
2118
2118
  }
2119
- return [Utils.className(entityName), method, opts, where];
2119
+ // the table name (plus discriminator value for STI) is stable across builds and processes,
2120
+ // unlike class names, which minifiers can mangle to the same short name for two entities
2121
+ const meta = this.metadata.find(entityName);
2122
+ const key = meta?.tableName ? [meta.schema, meta.tableName, meta.discriminatorValue] : Utils.className(entityName);
2123
+ return [key, method, opts, where];
2120
2124
  }
2121
2125
  /**
2122
2126
  * @internal
@@ -223,7 +223,11 @@ export class EntityRepository {
223
223
  }
224
224
  const entityName = entities[0].constructor.name;
225
225
  const repoType = Utils.className(this.entityName);
226
- if (entityName && repoType !== entityName) {
226
+ // compare class identity where possible, as minifiers can mangle two classes to the same name
227
+ const entityMeta = entities[0].__meta;
228
+ const repoMeta = this.getEntityManager().getMetadata?.().find(this.entityName);
229
+ const mismatch = entityMeta && repoMeta ? entityMeta.class !== repoMeta.class : entityName && repoType !== entityName;
230
+ if (mismatch) {
227
231
  throw ValidationError.fromWrongRepositoryType(entityName, repoType, method);
228
232
  }
229
233
  }
package/errors.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
60
60
  static duplicateFieldName(entityName: EntityName, names: [string, string][]): MetadataError;
61
61
  static multipleDecorators(entityName: string, propertyName: string): MetadataError;
62
62
  static missingMetadata(entity: string): MetadataError;
63
+ static ambiguousEntityName(className: string): MetadataError;
63
64
  static invalidPrimaryKey(meta: EntityMetadata, prop: EntityProperty, requiredName: string): MetadataError;
64
65
  static invalidManyToManyWithPivotEntity(meta1: EntityMetadata, prop1: EntityProperty, meta2: EntityMetadata, prop2: EntityProperty): MetadataError;
65
66
  static targetIsAbstract(meta: EntityMetadata, prop: EntityProperty): MetadataError;
package/errors.js CHANGED
@@ -195,6 +195,9 @@ export class MetadataError extends ValidationError {
195
195
  static missingMetadata(entity) {
196
196
  return new MetadataError(`Metadata for entity ${entity} not found`);
197
197
  }
198
+ static ambiguousEntityName(className) {
199
+ return new MetadataError(`Entity name '${className}' is ambiguous, multiple discovered entity classes share it (possibly due to a minifier mangling class names). Use a class reference instead of a string name.`);
200
+ }
198
201
  static invalidPrimaryKey(meta, prop, requiredName) {
199
202
  return this.fromMessage(meta, prop, `has wrong field name, '${requiredName}' is required in current driver`);
200
203
  }
@@ -43,7 +43,10 @@ export class EventManager {
43
43
  }));
44
44
  for (const listener of this.#listeners[event] ?? new Set()) {
45
45
  const entities = this.#entities.get(listener);
46
- if (entities.size === 0 || !entity || entities.has(entity.constructor.name)) {
46
+ if (entities.size === 0 ||
47
+ !entity ||
48
+ entities.has(entity.constructor) ||
49
+ entities.has(entity.constructor.name)) {
47
50
  listeners.push(listener[event].bind(listener));
48
51
  }
49
52
  }
@@ -68,7 +71,7 @@ export class EventManager {
68
71
  }
69
72
  for (const listener of this.#listeners[event] ?? new Set()) {
70
73
  const entities = this.#entities.get(listener);
71
- if (entities.size === 0 || entities.has(meta.className)) {
74
+ if (entities.size === 0 || entities.has(meta.class) || entities.has(meta.className)) {
72
75
  this.#cache.set(cacheKey, true);
73
76
  return true;
74
77
  }
@@ -84,6 +87,6 @@ export class EventManager {
84
87
  if (!listener.getSubscribedEntities) {
85
88
  return new Set();
86
89
  }
87
- return new Set(listener.getSubscribedEntities().map(name => Utils.className(name)));
90
+ return new Set(listener.getSubscribedEntities().map(name => Utils.classOrName(name)));
88
91
  }
89
92
  }
@@ -2138,7 +2138,7 @@ export class MetadataDiscovery {
2138
2138
  shouldForceConstructorUsage(meta) {
2139
2139
  const forceConstructor = this.#config.get('forceEntityConstructor');
2140
2140
  if (Array.isArray(forceConstructor)) {
2141
- return forceConstructor.some(cls => Utils.className(cls) === meta.className);
2141
+ return forceConstructor.some(cls => Utils.matchesEntity(cls, meta));
2142
2142
  }
2143
2143
  return forceConstructor;
2144
2144
  }
@@ -17,6 +17,7 @@ export class MetadataStorage {
17
17
  #idMap;
18
18
  #classNameMap;
19
19
  #uniqueNameMap;
20
+ #ambiguousNames = new Set();
20
21
  constructor(metadata = {}) {
21
22
  this.#idMap = {};
22
23
  this.#uniqueNameMap = {};
@@ -64,6 +65,10 @@ export class MetadataStorage {
64
65
  }
65
66
  /** Returns metadata for the given entity, optionally initializing it if not found. */
66
67
  get(entityName, init = false) {
68
+ // string lookups cannot be resolved when several classes were minified to the same name
69
+ if (typeof entityName === 'string' && this.#ambiguousNames.has(entityName)) {
70
+ throw MetadataError.ambiguousEntityName(entityName);
71
+ }
67
72
  const exists = this.find(entityName);
68
73
  if (exists) {
69
74
  return exists;
@@ -99,7 +104,13 @@ export class MetadataStorage {
99
104
  this.#metadataMap.set(entityName, meta);
100
105
  this.#idMap[meta._id] = meta;
101
106
  this.#uniqueNameMap[meta.uniqueName] = meta;
102
- this.#classNameMap[Utils.className(entityName)] = meta;
107
+ const className = Utils.className(entityName);
108
+ const existing = this.#classNameMap[className];
109
+ // track name collisions caused by minifiers mangling two classes to the same name
110
+ if (existing && existing !== meta && existing.class !== meta.class) {
111
+ this.#ambiguousNames.add(className);
112
+ }
113
+ this.#classNameMap[className] = meta;
103
114
  return meta;
104
115
  }
105
116
  /** Removes metadata for the given entity from all internal maps. */
@@ -110,6 +121,11 @@ export class MetadataStorage {
110
121
  delete this.#idMap[meta._id];
111
122
  delete this.#uniqueNameMap[meta.uniqueName];
112
123
  delete this.#classNameMap[meta.className];
124
+ // the name may still be ambiguous among the remaining metas
125
+ const remaining = new Set([...this.#metadataMap.values()].filter(m => m.className === meta.className).map(m => m.class));
126
+ if (remaining.size <= 1) {
127
+ this.#ambiguousNames.delete(meta.className);
128
+ }
113
129
  }
114
130
  }
115
131
  /** Decorates all entity prototypes with helper methods (e.g. init, toJSON). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.14",
3
+ "version": "7.1.15-dev.0",
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",
package/types/Type.js CHANGED
@@ -55,11 +55,11 @@ export class Type {
55
55
  return prop.columnTypes?.[0] ?? platform.getTextTypeDeclarationSQL(prop);
56
56
  }
57
57
  static getType(cls) {
58
- const key = cls.name;
59
- if (!Type.types.has(key)) {
60
- Type.types.set(key, new cls());
58
+ // keyed by class reference, as minifiers can mangle two classes to the same name
59
+ if (!Type.types.has(cls)) {
60
+ Type.types.set(cls, new cls());
61
61
  }
62
- return Type.types.get(key);
62
+ return Type.types.get(cls);
63
63
  }
64
64
  /**
65
65
  * Checks whether the argument is instance of `Type`.
@@ -305,7 +305,7 @@ export class QueryHelper {
305
305
  return Utils.mergeConfig({}, propFilters, options);
306
306
  }
307
307
  static isFilterActive(meta, filterName, filter, options) {
308
- if (filter.entity && !filter.entity.includes(meta.className)) {
308
+ if (filter.entity && !Utils.asArray(filter.entity).some(e => Utils.matchesEntity(e, meta))) {
309
309
  return false;
310
310
  }
311
311
  if (options[filterName] === false) {
package/utils/Utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CompiledFunctions, Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
1
+ import type { CompiledFunctions, Dictionary, EntityCtor, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
3
  import { ScalarReference } from '../entity/Reference.js';
4
4
  import { Collection } from '../entity/Collection.js';
@@ -129,6 +129,16 @@ export declare class Utils {
129
129
  * Gets string name of given class.
130
130
  */
131
131
  static className<T>(classOrName: string | EntityName<T>): string;
132
+ /**
133
+ * Normalizes an entity reference for identity-safe matching: keeps class references
134
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
135
+ */
136
+ static classOrName<T>(classOrName: string | EntityName<T>): EntityCtor<T> | string;
137
+ /**
138
+ * Checks whether the given entity reference points at the given metadata,
139
+ * comparing classes by identity and strings by class name.
140
+ */
141
+ static matchesEntity<T>(classOrName: string | EntityName<T>, meta: EntityMetadata<any>): boolean;
132
142
  static extractChildElements(items: readonly string[], prefix: string, allSymbol?: string): string[];
133
143
  /**
134
144
  * Tries to detect TypeScript support.
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.14';
156
+ static #ORM_VERSION = '7.1.15-dev.0';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */
@@ -565,6 +565,21 @@ export class Utils {
565
565
  }
566
566
  return classOrName.name;
567
567
  }
568
+ /**
569
+ * Normalizes an entity reference for identity-safe matching: keeps class references
570
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
571
+ */
572
+ static classOrName(classOrName) {
573
+ return typeof classOrName === 'function' ? classOrName : Utils.className(classOrName);
574
+ }
575
+ /**
576
+ * Checks whether the given entity reference points at the given metadata,
577
+ * comparing classes by identity and strings by class name.
578
+ */
579
+ static matchesEntity(classOrName, meta) {
580
+ const ref = Utils.classOrName(classOrName);
581
+ return typeof ref === 'function' ? ref === meta.class : ref === meta.className;
582
+ }
568
583
  static extractChildElements(items, prefix, allSymbol) {
569
584
  return items
570
585
  .filter(field => field === allSymbol || field.startsWith(`${prefix}.`))