@mikro-orm/core 7.0.10-dev.1 → 7.0.10-dev.11

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.
@@ -7,7 +7,12 @@ import { ReferenceKind } from '../enums.js';
7
7
  import { helper } from './wrap.js';
8
8
  import { inspect } from '../logging/inspect.js';
9
9
  import { getEnv } from '../utils/env-vars.js';
10
- const entitySymbol = Symbol('Entity');
10
+ // Globally registered so the marker survives the CJS/ESM dual-package hazard
11
+ // (#7515) — `tsx` registers both an ESM and a CJS hook for the CLI and ends up
12
+ // loading `@mikro-orm/core` twice. JSON payloads still cannot forge the marker
13
+ // because Symbol-keyed properties have no JSON representation, which is the
14
+ // threat model the symbol-vs-string switch in f7e59a5ce was guarding against.
15
+ const entitySymbol = Symbol.for('@mikro-orm/core/EntityHelper.entity');
11
16
  /**
12
17
  * @internal
13
18
  */
@@ -39,11 +44,6 @@ export class EntityHelper {
39
44
  // toJSON can be overridden
40
45
  Object.defineProperty(prototype, 'toJSON', {
41
46
  value: function (...args) {
42
- // Guard against being called on the prototype itself (e.g. by serializers
43
- // walking the object graph and calling toJSON on prototype objects)
44
- if (this === prototype) {
45
- return {};
46
- }
47
47
  return EntityTransformer.toObject(this, ...args);
48
48
  },
49
49
  writable: true,
@@ -51,6 +51,22 @@ export class EntityHelper {
51
51
  enumerable: false,
52
52
  });
53
53
  }
54
+ // Walkers / serializers reaching the prototype directly invoke its methods and
55
+ // accessors with `this === prototype`. Wrap each so that case is a no-op rather
56
+ // than throwing (when a user `@Property({ persist: false })` getter dereferences
57
+ // unhydrated instance state) or installing state on the prototype itself (#7151).
58
+ for (const name of Object.getOwnPropertyNames(prototype)) {
59
+ const desc = Object.getOwnPropertyDescriptor(prototype, name);
60
+ const fn = desc.get ?? desc.value;
61
+ if (name === 'constructor' || typeof fn !== 'function' || fn.__guarded) {
62
+ continue;
63
+ }
64
+ const guarded = function (...args) {
65
+ return this === prototype ? undefined : fn.apply(this, args);
66
+ };
67
+ guarded.__guarded = true;
68
+ Object.defineProperty(prototype, name, desc.get ? { ...desc, get: guarded } : { ...desc, value: guarded });
69
+ }
54
70
  }
55
71
  /**
56
72
  * As a performance optimization, we create entity state methods lazily. We first add
@@ -393,7 +393,7 @@ export class MetadataDiscovery {
393
393
  }
394
394
  if (prop.joinColumns.length > 1) {
395
395
  prop.ownColumns = prop.joinColumns.filter(col => {
396
- return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
396
+ return !meta.props.find(p => p.name !== prop.name && p.fieldNames?.includes(col));
397
397
  });
398
398
  }
399
399
  if (!prop.ownColumns || prop.ownColumns.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.0.10-dev.1",
3
+ "version": "7.0.10-dev.11",
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",
@@ -1188,20 +1188,21 @@ export class UnitOfWork {
1188
1188
  }
1189
1189
  getCommitOrder() {
1190
1190
  const calc = new CommitOrderCalculator();
1191
- const set = new Set();
1191
+ // keyed by `_id` so we return the SAME instances as the change set groups (GH #7511)
1192
+ const metaById = new Map();
1192
1193
  this.#changeSets.forEach(cs => {
1193
1194
  if (cs.meta.inheritanceType === 'tpt') {
1194
- set.add(cs.meta);
1195
+ metaById.set(cs.meta._id, cs.meta);
1195
1196
  for (const parentCs of cs.tptChangeSets ?? []) {
1196
- set.add(parentCs.meta);
1197
+ metaById.set(parentCs.meta._id, parentCs.meta);
1197
1198
  }
1198
1199
  }
1199
1200
  else {
1200
- set.add(cs.rootMeta);
1201
+ metaById.set(cs.rootMeta._id, cs.rootMeta);
1201
1202
  }
1202
1203
  });
1203
- set.forEach(meta => calc.addNode(meta._id));
1204
- for (const meta of set) {
1204
+ metaById.forEach(meta => calc.addNode(meta._id));
1205
+ for (const meta of metaById.values()) {
1205
1206
  for (const prop of meta.relations) {
1206
1207
  if (prop.polymorphTargets) {
1207
1208
  for (const targetMeta of prop.polymorphTargets) {
@@ -1213,11 +1214,11 @@ export class UnitOfWork {
1213
1214
  }
1214
1215
  }
1215
1216
  // For TPT, parent table must be inserted BEFORE child tables
1216
- if (meta.inheritanceType === 'tpt' && meta.tptParent && set.has(meta.tptParent)) {
1217
+ if (meta.inheritanceType === 'tpt' && meta.tptParent && metaById.has(meta.tptParent._id)) {
1217
1218
  calc.addDependency(meta.tptParent._id, meta._id, 1);
1218
1219
  }
1219
1220
  }
1220
- return calc.sort().map(id => this.#metadata.getById(id));
1221
+ return calc.sort().map(id => metaById.get(id));
1221
1222
  }
1222
1223
  resetTransaction(oldTx) {
1223
1224
  if (oldTx) {
@@ -106,7 +106,10 @@ export class QueryHelper {
106
106
  Object.keys(where).every(k => !Utils.isPlainObject(where[k]) ||
107
107
  Object.keys(where[k]).every(v => {
108
108
  if (Utils.isOperator(v, false)) {
109
- return true;
109
+ // multi-value operators (e.g. `$in`/`$nin`) cannot be inlined into a composite
110
+ // PK tuple — `getPrimaryKeyValues` would flatten the operator's array alongside
111
+ // the sibling PK values, producing a malformed `(col1, col2) IN ((a, b))` clause
112
+ return !meta.compositePK || !Array.isArray(where[k][v]);
110
113
  }
111
114
  if (meta.properties[k].primary &&
112
115
  [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[k].kind)) {
@@ -4,6 +4,8 @@ declare const rawFragmentSymbolBrand: unique symbol;
4
4
  export type RawQueryFragmentSymbol = symbol & {
5
5
  readonly [rawFragmentSymbolBrand]: true;
6
6
  };
7
+ /** Checks whether the given value is a `RawQueryFragment` instance. */
8
+ export declare function isRaw(value: unknown): value is RawQueryFragment;
7
9
  /** Represents a raw SQL fragment with optional parameters, usable as both a value and an object key via Symbol coercion. */
8
10
  export declare class RawQueryFragment<Alias extends string = string> {
9
11
  #private;
@@ -30,8 +32,6 @@ export declare class RawQueryFragment<Alias extends string = string> {
30
32
  static getKnownFragment(key: unknown): RawQueryFragment | undefined;
31
33
  }
32
34
  export { RawQueryFragment as Raw };
33
- /** Checks whether the given value is a `RawQueryFragment` instance. */
34
- export declare function isRaw(value: unknown): value is RawQueryFragment;
35
35
  /** @internal */
36
36
  export declare const ALIAS_REPLACEMENT = "[::alias::]";
37
37
  /** @internal */
@@ -1,21 +1,53 @@
1
1
  import { Utils } from './Utils.js';
2
- const rawSymbol = Symbol('RawQueryFragment');
2
+ // Brand lives on the prototype so JSON payloads — whose proto is
3
+ // `Object.prototype` — cannot forge it. String key, not a `Symbol.for(...)`,
4
+ // so each CJS/ESM copy of this module independently installs it on its own
5
+ // prototype without publishing a global key for the marker that controls raw
6
+ // SQL assembly. The string is namespaced so it does not collide with property
7
+ // names users might independently install on their own prototypes.
8
+ const RAW_FRAGMENT_BRAND = '__mikroOrmRawFragment';
9
+ // Back-references from a fragment's symbol key to the fragment itself, shared
10
+ // across CJS/ESM module copies via globalThis: when one copy creates a fragment
11
+ // via `raw('…')` and stores its symbol in a where-clause object key, the other
12
+ // copy still needs to recover the original fragment to assemble SQL.
13
+ const REGISTRY_KEY = Symbol.for('@mikro-orm/core/RawQueryFragment.references');
14
+ const rawQueryReferences = (globalThis[REGISTRY_KEY] ??=
15
+ new WeakMap());
16
+ /** Checks whether the given value is a `RawQueryFragment` instance. */
17
+ export function isRaw(value) {
18
+ if (value == null || typeof value !== 'object') {
19
+ return false;
20
+ }
21
+ // Fast path: intra-module instances and their subclasses.
22
+ // eslint-disable-next-line no-use-before-define
23
+ if (value instanceof RawQueryFragment) {
24
+ return true;
25
+ }
26
+ // Walk the prototype chain starting from the *prototype* (not the value) so
27
+ // own-property spoofing from JSON payloads cannot forge the brand. Stop at
28
+ // `Object.prototype`, which is never branded — that bails plain objects,
29
+ // JSON payloads, and built-ins like `Date`/`Array`/`Map` at depth 1.
30
+ for (let p = Object.getPrototypeOf(value); p != null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
31
+ if (Object.hasOwn(p, RAW_FRAGMENT_BRAND)) {
32
+ return true;
33
+ }
34
+ }
35
+ return false;
36
+ }
3
37
  /** Represents a raw SQL fragment with optional parameters, usable as both a value and an object key via Symbol coercion. */
4
38
  export class RawQueryFragment {
5
39
  sql;
6
40
  params;
7
- static #rawQueryReferences = new WeakMap();
8
41
  #key;
9
42
  constructor(sql, params = []) {
10
43
  this.sql = sql;
11
44
  this.params = params;
12
- Object.defineProperty(this, rawSymbol, { value: true, enumerable: false });
13
45
  }
14
46
  /** Returns a unique symbol key for this fragment, creating and caching it on first access. */
15
47
  get key() {
16
48
  if (!this.#key) {
17
49
  this.#key = Symbol(this.toJSON());
18
- RawQueryFragment.#rawQueryReferences.set(this.#key, this);
50
+ rawQueryReferences.set(this.#key, this);
19
51
  }
20
52
  return this.#key;
21
53
  }
@@ -42,7 +74,7 @@ export class RawQueryFragment {
42
74
  }
43
75
  /** Checks whether the given value is a symbol that maps to a known raw query fragment. */
44
76
  static isKnownFragmentSymbol(key) {
45
- return typeof key === 'symbol' && this.#rawQueryReferences.has(key);
77
+ return typeof key === 'symbol' && rawQueryReferences.has(key);
46
78
  }
47
79
  /** Checks whether an object has any symbol keys that are known raw query fragments. */
48
80
  static hasObjectFragments(object) {
@@ -51,20 +83,17 @@ export class RawQueryFragment {
51
83
  }
52
84
  /** Checks whether the given value is a RawQueryFragment instance or a known fragment symbol. */
53
85
  static isKnownFragment(key) {
54
- if (key instanceof RawQueryFragment) {
55
- return true;
56
- }
57
- return this.isKnownFragmentSymbol(key);
86
+ return isRaw(key) || this.isKnownFragmentSymbol(key);
58
87
  }
59
88
  /** Retrieves the RawQueryFragment associated with the given key (instance or symbol). */
60
89
  static getKnownFragment(key) {
61
- if (key instanceof RawQueryFragment) {
90
+ if (isRaw(key)) {
62
91
  return key;
63
92
  }
64
93
  if (typeof key !== 'symbol') {
65
94
  return;
66
95
  }
67
- return this.#rawQueryReferences.get(key);
96
+ return rawQueryReferences.get(key);
68
97
  }
69
98
  /** @ignore */
70
99
  /* v8 ignore next */
@@ -75,11 +104,19 @@ export class RawQueryFragment {
75
104
  return { sql: this.sql };
76
105
  }
77
106
  }
107
+ // Non-enumerable so the brand is skipped by JSON/Object.keys/for-in, and locked
108
+ // down (non-writable, non-configurable) so in-process code cannot delete or
109
+ // overwrite it and thereby silently disable `isRaw` recognition for every
110
+ // fragment in the process. Subclasses don't need mutability here — they inherit
111
+ // the brand via the prototype chain, and sibling-copy classes install their own
112
+ // brand on their own prototype (a different object), so lockdown only blocks
113
+ // tampering with the canonical marker.
114
+ Object.defineProperty(RawQueryFragment.prototype, RAW_FRAGMENT_BRAND, {
115
+ value: true,
116
+ writable: false,
117
+ configurable: false,
118
+ });
78
119
  export { RawQueryFragment as Raw };
79
- /** Checks whether the given value is a `RawQueryFragment` instance. */
80
- export function isRaw(value) {
81
- return typeof value === 'object' && value !== null && Object.hasOwn(value, rawSymbol);
82
- }
83
120
  /** @internal */
84
121
  export const ALIAS_REPLACEMENT = '[::alias::]';
85
122
  /** @internal */
@@ -140,7 +177,7 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
140
177
  * ```
141
178
  */
142
179
  export function raw(sql, params) {
143
- if (sql instanceof RawQueryFragment) {
180
+ if (isRaw(sql)) {
144
181
  return sql;
145
182
  }
146
183
  if (sql instanceof Function) {
package/utils/Utils.js CHANGED
@@ -22,13 +22,22 @@ export function compareObjects(a, b) {
22
22
  if (a === b || (a == null && b == null)) {
23
23
  return true;
24
24
  }
25
- if (!a || !b || typeof a !== 'object' || typeof b !== 'object' || !compareConstructors(a, b)) {
25
+ if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
26
26
  return false;
27
27
  }
28
+ // Raw fragments are compared by `sql` + `params` *before* the constructor
29
+ // check, so that two fragments carrying the same SQL but constructed by
30
+ // different CJS/ESM copies of this module (different classes, different
31
+ // prototypes) still compare as equal. Without this, the dual-package hazard
32
+ // would produce spurious change-set diffs when a raw fragment is used as a
33
+ // property value.
28
34
  if (isRaw(a) && isRaw(b)) {
29
35
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
30
36
  return a.sql === b.sql && compareArrays(a.params, b.params);
31
37
  }
38
+ if (!compareConstructors(a, b)) {
39
+ return false;
40
+ }
32
41
  if (a instanceof Date && b instanceof Date) {
33
42
  const timeA = a.getTime();
34
43
  const timeB = b.getTime();
@@ -132,7 +141,7 @@ export function parseJsonSafe(value) {
132
141
  /** Collection of general-purpose utility methods used throughout the ORM. */
133
142
  export class Utils {
134
143
  static PK_SEPARATOR = '~~~';
135
- static #ORM_VERSION = '7.0.10-dev.1';
144
+ static #ORM_VERSION = '7.0.10-dev.11';
136
145
  /**
137
146
  * Checks if the argument is instance of `Object`. Returns false for arrays.
138
147
  */
@@ -647,8 +656,10 @@ export class Utils {
647
656
  * Extracts all possible values of a TS enum. Works with both string and numeric enums.
648
657
  */
649
658
  static extractEnumValues(target) {
650
- const keys = Object.keys(target);
651
- const values = Object.values(target);
659
+ // skip namespace-merged functions (GH #7500)
660
+ const entries = Object.entries(target).filter(([, v]) => typeof v !== 'function');
661
+ const keys = entries.map(([k]) => k);
662
+ const values = entries.map(([, v]) => v);
652
663
  const numeric = !!values.find(v => typeof v === 'number');
653
664
  const constEnum = values.length % 2 === 0 && // const enum will have even number of items
654
665
  values.slice(0, values.length / 2).every(v => typeof v === 'string') && // first half are strings
package/utils/fs-utils.js CHANGED
@@ -14,10 +14,11 @@ export const fs = {
14
14
  if (tinyGlobby) {
15
15
  globSync = (patterns, options) => {
16
16
  patterns = Utils.asArray(patterns).map(p => p.replace(/\\/g, '/'));
17
- if (options?.cwd) {
18
- options = { ...options, cwd: options.cwd.replace(/\\/g, '/') };
19
- }
20
- return tinyGlobby.globSync(patterns, { ...options, expandDirectories: false });
17
+ // never forward `cwd: undefined` — tinyglobby >= 0.2.16 calls `path.resolve(undefined)` and throws
18
+ return tinyGlobby.globSync(patterns, {
19
+ expandDirectories: false,
20
+ ...(options?.cwd && { cwd: options.cwd.replace(/\\/g, '/') }),
21
+ });
21
22
  };
22
23
  }
23
24
  },