@mikro-orm/core 7.2.0-dev.0 → 7.2.0-dev.1

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
@@ -1432,7 +1432,7 @@ export class EntityManager {
1432
1432
  options.validate ??= true;
1433
1433
  options.cascade ??= true;
1434
1434
  validatePrimaryKey(data, em.metadata.get(entityName));
1435
- let entity = em.#unitOfWork.tryGetById(entityName, data, options.schema, false);
1435
+ let entity = em.#unitOfWork.tryGetById(entityName, data, options.schema, false, options.convertCustomTypes);
1436
1436
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1437
1437
  return entity;
1438
1438
  }
@@ -1,5 +1,5 @@
1
1
  import { Collection } from './Collection.js';
2
- import { Utils } from '../utils/Utils.js';
2
+ import { DANGEROUS_PROPERTY_NAMES, Utils } from '../utils/Utils.js';
3
3
  import { Reference } from './Reference.js';
4
4
  import { ReferenceKind, SCALAR_TYPES } from '../enums.js';
5
5
  import { validateProperty } from './validators.js';
@@ -35,6 +35,10 @@ export class EntityAssigner {
35
35
  return entity;
36
36
  }
37
37
  static assignProperty(entity, propName, props, data, options) {
38
+ // needs to happen before the `props` lookup, as those keys resolve to inherited accessors
39
+ if (DANGEROUS_PROPERTY_NAMES.includes(propName)) {
40
+ return;
41
+ }
38
42
  let value = data[propName];
39
43
  const onlyProperties = options.onlyProperties && !(propName in props);
40
44
  const ignoreUndefined = options.ignoreUndefined === true && value === undefined;
@@ -183,7 +187,9 @@ export class EntityAssigner {
183
187
  if (options.updateNestedEntities && options.updateByPrimaryKey && Utils.isPlainObject(item)) {
184
188
  const pk = Utils.extractPK(item, prop.targetMeta);
185
189
  if (pk && EntityAssigner.validateEM(em)) {
186
- const ref = em.getUnitOfWork().getById(prop.targetMeta.class, pk, options.schema);
190
+ const ref = em
191
+ .getUnitOfWork()
192
+ .getById(prop.targetMeta.class, pk, options.schema, options.convertCustomTypes);
187
193
  if (ref) {
188
194
  return EntityAssigner.assign(ref, item, options);
189
195
  }
@@ -421,7 +421,7 @@ export class EntityFactory {
421
421
  const value = data[k];
422
422
  if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
423
423
  const pk = Reference.unwrapReference(value);
424
- const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
424
+ const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, options.convertCustomTypes);
425
425
  if (entity) {
426
426
  return entity;
427
427
  }
@@ -291,7 +291,8 @@ export class EntityLoader {
291
291
  }
292
292
  for (const child of children) {
293
293
  const fk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
294
- if (fk) {
294
+ // check for `null`/`undefined` explicitly, the FK can be a falsy raw PK value like `0` (e.g. with `mapToPk`)
295
+ if (fk != null) {
295
296
  let key;
296
297
  if (targetKey) {
297
298
  // `fk` is the owner reference (resolve its targetKey) unless the relation maps to the raw PK value
@@ -1,19 +1,7 @@
1
- import { Utils } from '../utils/Utils.js';
1
+ import { DANGEROUS_PROPERTY_NAMES, Utils } from '../utils/Utils.js';
2
2
  import { normalizePartitionNameForComparison, splitCommaSeparatedIdentifiers } from '../utils/partition-utils.js';
3
3
  import { MetadataError } from '../errors.js';
4
4
  import { ReferenceKind } from '../enums.js';
5
- /**
6
- * List of property names that could lead to prototype pollution vulnerabilities.
7
- * These names should never be used as entity property names because they could
8
- * allow malicious code to modify object prototypes when property values are assigned.
9
- *
10
- * - `__proto__`: Could modify the prototype chain
11
- * - `constructor`: Could modify the constructor property
12
- * - `prototype`: Could modify the prototype object
13
- *
14
- * @internal
15
- */
16
- const DANGEROUS_PROPERTY_NAMES = ['__proto__', 'constructor', 'prototype'];
17
5
  /**
18
6
  * @internal
19
7
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.2.0-dev.0",
3
+ "version": "7.2.0-dev.1",
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",
@@ -138,7 +138,8 @@ export class ChangeSetComputer {
138
138
  return data;
139
139
  }
140
140
  processProperty(changeSet, prop, target) {
141
- if (!target) {
141
+ // check for `null`/`undefined` explicitly, the target can be a falsy raw PK value like `0` (e.g. with `mapToPk`)
142
+ if (target == null) {
142
143
  const targets = Utils.unwrapProperty(changeSet.entity, changeSet.meta, prop);
143
144
  targets.forEach(([t]) => this.processProperty(changeSet, prop, t));
144
145
  return;
@@ -12,6 +12,11 @@ export declare class IdentityMap {
12
12
  storeByKey<T>(item: T, key: string, value: string, schema?: string): void;
13
13
  /** Removes an entity and its alternate key entries from the identity map. */
14
14
  delete<T>(item: T): void;
15
+ /**
16
+ * Retrieves the entity occupying the same slot as the given one, if any. Hashes the entity the same way `store()`
17
+ * does, so it matches regardless of the primary key shape — unlike hashing a primary key value obtained elsewhere.
18
+ */
19
+ getByEntity<T>(item: T): T | undefined;
15
20
  /** Retrieves an entity by its hash key from the identity map. */
16
21
  getByHash<T>(meta: EntityMetadata<T>, hash: string): T | undefined;
17
22
  /** Returns (or creates) the per-entity-class store within the identity map. */
@@ -40,6 +40,13 @@ export class IdentityMap {
40
40
  this.#alternateKeys.delete(item);
41
41
  }
42
42
  }
43
+ /**
44
+ * Retrieves the entity occupying the same slot as the given one, if any. Hashes the entity the same way `store()`
45
+ * does, so it matches regardless of the primary key shape — unlike hashing a primary key value obtained elsewhere.
46
+ */
47
+ getByEntity(item) {
48
+ return this.getStore(item.__meta.root).get(this.getPkHash(item));
49
+ }
43
50
  /** Retrieves an entity by its hash key from the identity map. */
44
51
  getByHash(meta, hash) {
45
52
  const store = this.getStore(meta);
@@ -48,7 +48,7 @@ export declare class UnitOfWork {
48
48
  */
49
49
  storeByKey<T extends object>(entity: T, key: string, value: unknown, schema?: string, convertCustomTypes?: boolean): void;
50
50
  /** Attempts to extract a primary key from the where condition and look up the entity in the identity map. */
51
- tryGetById<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, schema?: string, strict?: boolean): T | null;
51
+ tryGetById<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, schema?: string, strict?: boolean, convertCustomTypes?: boolean): T | null;
52
52
  /**
53
53
  * Returns map of all managed entities.
54
54
  */
@@ -227,12 +227,12 @@ export class UnitOfWork {
227
227
  this.#identityMap.storeByKey(entity, key, '' + value, schema);
228
228
  }
229
229
  /** Attempts to extract a primary key from the where condition and look up the entity in the identity map. */
230
- tryGetById(entityName, where, schema, strict = true) {
230
+ tryGetById(entityName, where, schema, strict = true, convertCustomTypes) {
231
231
  const pk = Utils.extractPK(where, this.#metadata.find(entityName), strict);
232
232
  if (!pk) {
233
233
  return null;
234
234
  }
235
- return this.getById(entityName, pk, schema);
235
+ return this.getById(entityName, pk, schema, convertCustomTypes);
236
236
  }
237
237
  /**
238
238
  * Returns map of all managed entities.
@@ -95,6 +95,8 @@ export declare abstract class AbstractMigrator<D extends IDatabaseDriver> implem
95
95
  */
96
96
  unlogMigration(name: string): Promise<void>;
97
97
  protected init(): Promise<void>;
98
+ /** Resolves the migrations path (including source folder auto-detection) without touching the filesystem. */
99
+ protected resolvePaths(): Promise<void>;
98
100
  protected initPaths(): Promise<void>;
99
101
  protected initServices(): void;
100
102
  protected resolve(params: {
@@ -9,6 +9,7 @@ export class AbstractMigrator {
9
9
  options;
10
10
  absolutePath;
11
11
  initialized = false;
12
+ #pathsEnsured = false;
12
13
  #listeners = new Map();
13
14
  constructor(em) {
14
15
  this.em = em;
@@ -303,7 +304,8 @@ export class AbstractMigrator {
303
304
  this.initialized = true;
304
305
  await this.initPaths();
305
306
  }
306
- async initPaths() {
307
+ /** Resolves the migrations path (including source folder auto-detection) without touching the filesystem. */
308
+ async resolvePaths() {
307
309
  if (this.absolutePath || this.options.migrationsList) {
308
310
  return;
309
311
  }
@@ -312,6 +314,14 @@ export class AbstractMigrator {
312
314
  /* v8 ignore next */
313
315
  const key = this.config.get('preferTs', Utils.detectTypeScriptSupport()) && this.options.pathTs ? 'pathTs' : 'path';
314
316
  this.absolutePath = fs.absolutePath(this.options[key], this.config.get('baseDir'));
317
+ }
318
+ async initPaths() {
319
+ await this.resolvePaths();
320
+ if (this.#pathsEnsured || this.options.migrationsList) {
321
+ return;
322
+ }
323
+ this.#pathsEnsured = true;
324
+ const { fs } = await import('@mikro-orm/core/fs-utils');
315
325
  try {
316
326
  fs.ensureDir(this.absolutePath);
317
327
  }
@@ -172,6 +172,10 @@ export class EntityComparator {
172
172
  if (meta.properties[pk].kind !== ReferenceKind.SCALAR) {
173
173
  lines.push(` (entity${this.wrap(pk)} != null && isEntityOrRef(entity${this.wrap(pk)})) ? entity${this.wrap(pk)}.__helper.getSerializedPrimaryKey() : entity${this.wrap(pk)},`);
174
174
  }
175
+ else if (meta.properties[pk].customType) {
176
+ const convertorKey = this.registerCustomType(meta.properties[pk], context);
177
+ lines.push(` convertToDatabaseValue_${convertorKey}(entity${this.wrap(pk)}),`);
178
+ }
175
179
  else {
176
180
  lines.push(` entity${this.wrap(pk)},`);
177
181
  }
@@ -162,7 +162,7 @@ export class TransactionManager {
162
162
  for (const entity of fork.getUnitOfWork(false).getIdentityMap()) {
163
163
  const wrapped = helper(entity);
164
164
  const meta = wrapped.__meta;
165
- const parentEntity = parentUoW.getById(meta.class, wrapped.getPrimaryKey(), parent.schema, true);
165
+ const parentEntity = parentUoW.getIdentityMap().getByEntity(entity);
166
166
  if (parentEntity && parentEntity !== entity) {
167
167
  const parentWrapped = helper(parentEntity);
168
168
  // Don't overwrite a fully-loaded entity with an uninitialized reference
package/utils/Utils.d.ts CHANGED
@@ -3,6 +3,18 @@ import type { Platform } from '../platforms/Platform.js';
3
3
  import { ScalarReference } from '../entity/Reference.js';
4
4
  import { Collection } from '../entity/Collection.js';
5
5
  import { type RawQueryFragmentSymbol } from './RawQueryFragment.js';
6
+ /**
7
+ * List of property names that could lead to prototype pollution vulnerabilities.
8
+ * These names should never be used as entity property names because they could
9
+ * allow malicious code to modify object prototypes when property values are assigned.
10
+ *
11
+ * - `__proto__`: Could modify the prototype chain
12
+ * - `constructor`: Could modify the constructor property
13
+ * - `prototype`: Could modify the prototype object
14
+ *
15
+ * @internal
16
+ */
17
+ export declare const DANGEROUS_PROPERTY_NAMES: readonly string[];
6
18
  /** Deeply compares two objects for equality, handling dates, regexes, and raw fragments. */
7
19
  export declare function compareObjects(a: any, b: any): boolean;
8
20
  /** Compares two arrays element-by-element for deep equality. */
package/utils/Utils.js CHANGED
@@ -5,6 +5,18 @@ import { Reference, ScalarReference } from '../entity/Reference.js';
5
5
  import { Collection } from '../entity/Collection.js';
6
6
  import { EntityHelper } from '../entity/EntityHelper.js';
7
7
  import { Raw, isRaw } from './RawQueryFragment.js';
8
+ /**
9
+ * List of property names that could lead to prototype pollution vulnerabilities.
10
+ * These names should never be used as entity property names because they could
11
+ * allow malicious code to modify object prototypes when property values are assigned.
12
+ *
13
+ * - `__proto__`: Could modify the prototype chain
14
+ * - `constructor`: Could modify the constructor property
15
+ * - `prototype`: Could modify the prototype object
16
+ *
17
+ * @internal
18
+ */
19
+ export const DANGEROUS_PROPERTY_NAMES = ['__proto__', 'constructor', 'prototype'];
8
20
  function compareConstructors(a, b) {
9
21
  if (a.constructor === b.constructor) {
10
22
  return true;
@@ -141,7 +153,7 @@ export function parseJsonSafe(value) {
141
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
142
154
  export class Utils {
143
155
  static PK_SEPARATOR = '~~~';
144
- static #ORM_VERSION = '7.2.0-dev.0';
156
+ static #ORM_VERSION = '7.2.0-dev.1';
145
157
  /**
146
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
147
159
  */
@@ -232,7 +244,7 @@ export class Utils {
232
244
  const source = sources.shift();
233
245
  if (Utils.isObject(target) && Utils.isPlainObject(source)) {
234
246
  for (const [key, value] of Object.entries(source)) {
235
- if (['__proto__', 'constructor', 'prototype'].includes(key)) {
247
+ if (DANGEROUS_PROPERTY_NAMES.includes(key)) {
236
248
  continue;
237
249
  }
238
250
  if (ignoreUndefined && typeof value === 'undefined') {