@mikro-orm/core 7.3.0-dev.2 → 7.3.0-dev.20

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.
Files changed (49) hide show
  1. package/EntityManager.js +131 -32
  2. package/drivers/DatabaseDriver.d.ts +2 -0
  3. package/drivers/DatabaseDriver.js +16 -3
  4. package/entity/EntityAssigner.js +15 -3
  5. package/entity/EntityFactory.d.ts +8 -0
  6. package/entity/EntityFactory.js +69 -3
  7. package/entity/EntityLoader.js +31 -15
  8. package/entity/WrappedEntity.js +6 -1
  9. package/entity/defineEntity.d.ts +32 -19
  10. package/entity/defineEntity.js +9 -0
  11. package/entity/validators.js +4 -1
  12. package/errors.d.ts +7 -0
  13. package/errors.js +15 -0
  14. package/hydration/ObjectHydrator.js +6 -2
  15. package/metadata/MetadataDiscovery.d.ts +10 -0
  16. package/metadata/MetadataDiscovery.js +178 -30
  17. package/metadata/MetadataStorage.d.ts +8 -0
  18. package/metadata/MetadataStorage.js +47 -3
  19. package/metadata/MetadataValidator.js +2 -1
  20. package/metadata/types.d.ts +12 -0
  21. package/package.json +1 -1
  22. package/platforms/Platform.d.ts +6 -0
  23. package/platforms/Platform.js +26 -0
  24. package/serialization/EntitySerializer.js +52 -12
  25. package/types/DecimalType.js +34 -1
  26. package/types/DoubleType.d.ts +7 -3
  27. package/types/DoubleType.js +8 -3
  28. package/typings.d.ts +26 -6
  29. package/typings.js +5 -2
  30. package/unit-of-work/ChangeSetComputer.js +22 -7
  31. package/unit-of-work/ChangeSetPersister.js +24 -5
  32. package/unit-of-work/CommitOrderCalculator.js +2 -1
  33. package/unit-of-work/IdentityMap.d.ts +3 -2
  34. package/unit-of-work/IdentityMap.js +30 -7
  35. package/unit-of-work/UnitOfWork.js +45 -7
  36. package/utils/Configuration.d.ts +12 -1
  37. package/utils/EntityComparator.d.ts +5 -0
  38. package/utils/EntityComparator.js +87 -19
  39. package/utils/QueryHelper.d.ts +14 -0
  40. package/utils/QueryHelper.js +90 -13
  41. package/utils/RawQueryFragment.d.ts +5 -1
  42. package/utils/RawQueryFragment.js +27 -19
  43. package/utils/TransactionManager.js +4 -0
  44. package/utils/Utils.d.ts +2 -0
  45. package/utils/Utils.js +24 -8
  46. package/utils/clone.js +5 -7
  47. package/utils/env-vars.js +2 -0
  48. package/utils/fs-utils.js +19 -15
  49. package/utils/upsert-utils.js +2 -1
@@ -1,11 +1,6 @@
1
1
  import { Utils } from './Utils.js';
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';
2
+ // Prototype brand of raw fragments; `Symbol.for` so package copies agree.
3
+ const RAW_FRAGMENT_BRAND = Symbol.for('mikro-orm.raw-fragment');
9
4
  // Back-references from a fragment's symbol key to the fragment itself, shared
10
5
  // across CJS/ESM module copies via globalThis: when one copy creates a fragment
11
6
  // via `raw('…')` and stores its symbol in a where-clause object key, the other
@@ -13,6 +8,20 @@ const RAW_FRAGMENT_BRAND = '__mikroOrmRawFragment';
13
8
  const REGISTRY_KEY = Symbol.for('@mikro-orm/core/RawQueryFragment.references');
14
9
  const rawQueryReferences = (globalThis[REGISTRY_KEY] ??=
15
10
  new WeakMap());
11
+ /** Prototype brand of query builders usable as sub-queries; a symbol so JSON payloads cannot carry it, `Symbol.for` so package copies agree. */
12
+ export const SUBQUERY_BRAND = Symbol.for('mikro-orm.subquery');
13
+ function hasPrototypeBrand(value, brand) {
14
+ // Walk the prototype chain starting from the *prototype* (not the value) so
15
+ // own-property spoofing from JSON payloads cannot forge the brand. Stop at
16
+ // `Object.prototype`, which is never branded — that bails plain objects,
17
+ // JSON payloads, and built-ins like `Date`/`Array`/`Map` at depth 1.
18
+ for (let p = Object.getPrototypeOf(value); p != null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
19
+ if (Object.hasOwn(p, brand)) {
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
16
25
  /** Checks whether the given value is a `RawQueryFragment` instance. */
17
26
  export function isRaw(value) {
18
27
  if (value == null || typeof value !== 'object') {
@@ -23,16 +32,11 @@ export function isRaw(value) {
23
32
  if (value instanceof RawQueryFragment) {
24
33
  return true;
25
34
  }
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;
35
+ return hasPrototypeBrand(value, RAW_FRAGMENT_BRAND);
36
+ }
37
+ /** Checks whether the given value is a query builder usable as a sub-query (its prototype carries the `SUBQUERY_BRAND`). */
38
+ export function isSubquery(value) {
39
+ return value != null && typeof value === 'object' && hasPrototypeBrand(value, SUBQUERY_BRAND);
36
40
  }
37
41
  /** Represents a raw SQL fragment with optional parameters, usable as both a value and an object key via Symbol coercion. */
38
42
  export class RawQueryFragment {
@@ -121,6 +125,10 @@ export { RawQueryFragment as Raw };
121
125
  export const ALIAS_REPLACEMENT = '[::alias::]';
122
126
  /** @internal */
123
127
  export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
128
+ // a query builder param is embedded as a sub-query instead of being bound as a value
129
+ function toRawParam(param) {
130
+ return isSubquery(param) ? param.toRaw() : param;
131
+ }
124
132
  /**
125
133
  * Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
126
134
  * by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
@@ -204,12 +212,12 @@ export function raw(sql, params) {
204
212
  if (!Object.hasOwn(dict, key)) {
205
213
  return match;
206
214
  }
207
- objectParams.push(dict[key]);
215
+ objectParams.push(toRawParam(dict[key]));
208
216
  return identifier ? '??' : '?';
209
217
  });
210
218
  return new RawQueryFragment(sql, objectParams);
211
219
  }
212
- return new RawQueryFragment(sql, params);
220
+ return new RawQueryFragment(sql, params?.map(toRawParam));
213
221
  }
214
222
  /**
215
223
  * Alternative to the `raw()` helper allowing to use it as a tagged template function for the simple cases.
@@ -189,6 +189,10 @@ export class TransactionManager {
189
189
  if (wrapped.__originalEntityData) {
190
190
  parentWrapped.__originalEntityData = originalEntityData;
191
191
  }
192
+ // index a merged `targetKey` value, `getByKey` ignores the old one as stale
193
+ if (meta.root.targetKeys) {
194
+ parentUoW.getIdentityMap().store(parentEntity);
195
+ }
192
196
  }
193
197
  else {
194
198
  parentUoW.merge(entity, new Set([entity]));
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.3.0-dev.2';
156
+ static #ORM_VERSION = '7.3.0-dev.20';
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}`;
@@ -244,7 +244,7 @@ export class Utils {
244
244
  /**
245
245
  * Merges all sources into the target recursively.
246
246
  */
247
- static _merge(target, sources, ignoreUndefined) {
247
+ static _merge(target, sources, ignoreUndefined, copies) {
248
248
  if (!sources.length) {
249
249
  return target;
250
250
  }
@@ -259,7 +259,13 @@ export class Utils {
259
259
  }
260
260
  if (Utils.isPlainObject(value)) {
261
261
  if (!Utils.isObject(target[key])) {
262
- target[key] = Utils.copy(value);
262
+ // copy plain objects but keep class instances by reference, tracking copies so cycles terminate
263
+ const seen = copies ?? new WeakMap();
264
+ if (!seen.has(value)) {
265
+ seen.set(value, {});
266
+ Utils._merge(seen.get(value), [value], ignoreUndefined, seen);
267
+ }
268
+ target[key] = seen.get(value);
263
269
  continue;
264
270
  }
265
271
  /* v8 ignore next */
@@ -651,14 +657,19 @@ export class Utils {
651
657
  static isCollection(item) {
652
658
  return Collection.isCollection(item);
653
659
  }
654
- // FNV-1a 64-bit
660
+ // FNV-1a 64-bit, computed on two uint32 halves instead of a BigInt. The prime is 2^40 + 0x1b3, so
661
+ // `h * prime mod 2^64` is `h * 0x1b3` plus the low half shifted up by 40 bits. Every partial product
662
+ // stays below 2^41, so the double arithmetic is exact.
655
663
  static hash(data, length) {
656
- let h1 = 0xcbf29ce484222325n;
664
+ let hi = 0xcbf29ce4;
665
+ let lo = 0x84222325;
657
666
  for (let i = 0; i < data.length; i++) {
658
- h1 ^= BigInt(data.charCodeAt(i));
659
- h1 = (h1 * 0x100000001b3n) & 0xffffffffffffffffn;
667
+ lo = (lo ^ data.charCodeAt(i)) >>> 0;
668
+ const loProduct = lo * 0x1b3;
669
+ hi = (((hi * 0x1b3) >>> 0) + Math.floor(loProduct / 0x100000000) + ((lo << 8) >>> 0)) >>> 0;
670
+ lo = loProduct >>> 0;
660
671
  }
661
- const hash = h1.toString(16).padStart(16, '0');
672
+ const hash = hi.toString(16).padStart(8, '0') + lo.toString(16).padStart(8, '0');
662
673
  if (length) {
663
674
  return hash.substring(0, length);
664
675
  }
@@ -775,6 +786,11 @@ export class Utils {
775
786
  throw e;
776
787
  }
777
788
  }
789
+ /** @internal Same-named properties of polymorphic embeddable variants conflict when their kinds differ, to-one kinds share the column. */
790
+ static isSameKind(a, b) {
791
+ const toOne = (p) => p.kind === ReferenceKind.MANY_TO_ONE || p.kind === ReferenceKind.ONE_TO_ONE;
792
+ return a.kind === b.kind || (toOne(a) && toOne(b));
793
+ }
778
794
  static unwrapProperty(entity, meta, prop, payload = false) {
779
795
  let p = prop;
780
796
  const path = [];
package/utils/clone.js CHANGED
@@ -22,8 +22,8 @@ function getPropertyDescriptor(obj, prop) {
22
22
  }
23
23
  const TypedArray = Object.getPrototypeOf(Uint8Array);
24
24
  export function clone(parent, respectCustomCloneMethod = true) {
25
- const allParents = [];
26
- const allChildren = [];
25
+ // already cloned objects, so shared references and cycles map to a single clone
26
+ const clones = new Map();
27
27
  function _clone(parent) {
28
28
  // cloning null always returns null
29
29
  if (parent === null) {
@@ -92,12 +92,10 @@ export function clone(parent, respectCustomCloneMethod = true) {
92
92
  proto = Object.getPrototypeOf(parent);
93
93
  child = Object.create(proto);
94
94
  }
95
- const index = allParents.indexOf(parent);
96
- if (index !== -1) {
97
- return allChildren[index];
95
+ if (clones.has(parent)) {
96
+ return clones.get(parent);
98
97
  }
99
- allParents.push(parent);
100
- allChildren.push(child);
98
+ clones.set(parent, child);
101
99
  if (parent instanceof Map) {
102
100
  parent.forEach((value, key) => {
103
101
  const keyChild = _clone(key);
package/utils/env-vars.js CHANGED
@@ -74,6 +74,8 @@ export function loadEnvironmentVars() {
74
74
  read2('transactional', bool);
75
75
  read2('disableForeignKeys', bool);
76
76
  read2('allOrNothing', bool);
77
+ read2('lock', bool);
78
+ read2('lockTimeout', num);
77
79
  read2('dropTables', bool);
78
80
  read2('safe', bool);
79
81
  read2('silent', bool);
package/utils/fs-utils.js CHANGED
@@ -5,7 +5,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import { Utils } from './Utils.js';
6
6
  import { colors } from '../logging/colors.js';
7
7
  let globSync = (patterns, options) => {
8
- const files = nodeGlobSync(patterns, { ...options, withFileTypes: true });
8
+ const cwd = options?.dir ? join(options.cwd ?? '', options.dir) : options?.cwd;
9
+ const files = nodeGlobSync(patterns, { cwd, withFileTypes: true });
9
10
  return files.filter(f => f.isFile()).map(f => join(f.parentPath, f.name));
10
11
  };
11
12
  export const fs = {
@@ -13,7 +14,8 @@ export const fs = {
13
14
  const tinyGlobby = await import('tinyglobby').catch(() => null);
14
15
  if (tinyGlobby) {
15
16
  globSync = (patterns, options) => {
16
- patterns = Utils.asArray(patterns).map(p => p.replace(/\\/g, '/'));
17
+ const dir = options?.dir ? tinyGlobby.escapePath(options.dir.replace(/\\/g, '/')) + '/' : '';
18
+ patterns = Utils.asArray(patterns).map(p => dir + p.replace(/\\/g, '/'));
17
19
  // never forward `cwd: undefined` — tinyglobby >= 0.2.16 calls `path.resolve(undefined)` and throws
18
20
  return tinyGlobby.globSync(patterns, {
19
21
  expandDirectories: false,
@@ -23,10 +25,10 @@ export const fs = {
23
25
  }
24
26
  },
25
27
  pathExists(path) {
26
- if (/[*?[\]]/.test(path)) {
27
- return globSync(path).length > 0;
28
+ if (existsSync(path)) {
29
+ return true;
28
30
  }
29
- return existsSync(path);
31
+ return /[*?[\]]/.test(path) && this.resolveGlob(path).length > 0;
30
32
  },
31
33
  ensureDir(path) {
32
34
  if (!existsSync(path)) {
@@ -62,18 +64,20 @@ export const fs = {
62
64
  if (Array.isArray(input)) {
63
65
  return input.flatMap(paths => this.resolveGlob(paths, cwd));
64
66
  }
65
- const hasGlobChars = /[*?[\]]/.test(input);
66
- if (!hasGlobChars) {
67
- try {
68
- const s = statSync(cwd ? this.normalizePath(cwd, input) : input);
69
- if (s.isDirectory()) {
70
- return globSync(join(input, '**'), { cwd });
71
- }
72
- }
73
- catch {
74
- // ignore
67
+ // an absolute path repeats the cwd, which may contain glob characters (e.g. `~/Dropbox (Team)/app`)
68
+ if (isAbsolute(input)) {
69
+ cwd ??= process.cwd();
70
+ input = relative(cwd, input).replace(/\\/g, '/') || '.';
71
+ }
72
+ try {
73
+ const s = statSync(cwd ? this.normalizePath(cwd, input) : input);
74
+ if (s.isDirectory()) {
75
+ return globSync('**', { cwd, dir: input });
75
76
  }
76
77
  }
78
+ catch {
79
+ // ignore
80
+ }
77
81
  return globSync(input, { cwd });
78
82
  },
79
83
  getPackageConfig(basePath = process.cwd()) {
@@ -103,7 +103,8 @@ export function getOnConflictReturningFields(meta, data, uniqueFields, options,
103
103
  if (p.lazy || p.embeddable) {
104
104
  return false;
105
105
  }
106
- if (p.autoincrement) {
106
+ // a PK missing in the data is generated by the database
107
+ if (p.autoincrement || (p.primary && data[p.name] == null)) {
107
108
  return true;
108
109
  }
109
110
  // for a raw `onConflictFields` fragment we can't introspect which columns form the conflict