@mikro-orm/core 7.2.6-dev.4 → 7.2.6-dev.40

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) {
@@ -815,7 +815,7 @@ export class EntityLoader {
815
815
  if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
816
816
  return entity[f].getItems(false).every((item) => this.isPropertyLoaded(item, r.join('.')));
817
817
  }
818
- return this.isPropertyLoaded(entity[f], r.join('.'));
818
+ return this.isPropertyLoaded(Reference.unwrapReference(entity[f]), r.join('.'));
819
819
  }
820
820
  filterReferences(entities, field, options, ref, populate) {
821
821
  if (ref) {
@@ -630,6 +630,21 @@ type AllKeys<TProperties, TBase> = keyof TProperties | (IsNever<TBase> extends t
630
630
  type PartialWhere<TProperties, TBase> = string | FilterQuery<{
631
631
  [K in AllKeys<TProperties, TBase> & string]?: unknown;
632
632
  }>;
633
+ type DefineEntityFilterMap = Dictionary<{
634
+ name: string;
635
+ cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>, entityName?: string) => MaybePromise<FilterQuery<any>>);
636
+ default?: boolean;
637
+ entity?: EntityName<any> | EntityName<any>[];
638
+ args?: boolean;
639
+ strict?: boolean;
640
+ rls?: boolean | {
641
+ setting?: string;
642
+ };
643
+ }>;
644
+ type DefineEntityFilterMapLoose = Dictionary<{
645
+ name: string;
646
+ cond: Dictionary;
647
+ }>;
633
648
  /** Metadata descriptor for `defineEntity()`, combining entity options with property definitions. */
634
649
  export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false, TDiscriminatorColumn extends string | undefined = undefined, TDiscriminatorValue extends string | number | undefined = undefined, TBaseDiscriminatorColumn extends string | undefined = undefined, TEmbeddable extends boolean = false> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminator' | 'discriminatorColumn' | 'discriminatorValue' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
635
650
  name: TName;
@@ -644,17 +659,7 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
644
659
  primaryKeys?: TPK & NoInfer<InferPrimaryKeyConstraint<TProperties>>[];
645
660
  hooks?: DefineEntityHooks;
646
661
  repository?: () => TRepository;
647
- filters?: Dictionary<{
648
- name: string;
649
- cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>, entityName?: string) => MaybePromise<FilterQuery<any>>);
650
- default?: boolean;
651
- entity?: EntityName<any> | EntityName<any>[];
652
- args?: boolean;
653
- strict?: boolean;
654
- rls?: boolean | {
655
- setting?: string;
656
- };
657
- }>;
662
+ filters?: DefineEntityFilterMap | IsNever<TRepository, never, DefineEntityFilterMapLoose>;
658
663
  forceObject?: TForceObject;
659
664
  embeddable?: TEmbeddable;
660
665
  inheritance?: 'tpt';
@@ -34,6 +34,11 @@ export declare class MetadataDiscovery {
34
34
  private initManyToOneFields;
35
35
  private initOneToManyFields;
36
36
  private processEntity;
37
+ /**
38
+ * Groups the relations of all entities by target class name in one pass, so `findReferencingProperties()`
39
+ * does not go over the relations of every entity for each entity. Lists keep the order of `metadata` and `relations`.
40
+ */
41
+ private indexReferencingProperties;
37
42
  private findReferencingProperties;
38
43
  private initFactoryField;
39
44
  private ensureCorrectFKOrderInPivotEntity;
@@ -183,10 +183,11 @@ export class MetadataDiscovery {
183
183
  }
184
184
  discovered.forEach(meta => meta.sync(true));
185
185
  this.#metadataProvider.combineCache();
186
+ const referencingProperties = this.indexReferencingProperties(filtered);
186
187
  return discovered.map(meta => {
187
188
  meta = this.#metadata.get(meta.class);
188
189
  meta.sync(true);
189
- this.findReferencingProperties(meta, filtered);
190
+ this.findReferencingProperties(meta, referencingProperties);
190
191
  if (meta.inheritanceType === 'tpt') {
191
192
  this.computeTPTOwnProps(meta);
192
193
  }
@@ -217,18 +218,20 @@ export class MetadataDiscovery {
217
218
  .replace(/Array<(.*)>/, '$1') // unwrap array
218
219
  .replace(/\[]$/, '') // remove array suffix
219
220
  .replace(/\((.*)\)/, '$1'); // unwrap union types
221
+ const discoveredNames = new Set(this.#discovered.map(m => m.className));
220
222
  // Names can be ambiguous when a minifier mangles two classes to the same name,
221
223
  // so class references also need to be checked by identity.
224
+ const discoveredClasses = new Set(this.#discovered.map(m => m.class));
222
225
  const discoveredByIdentity = (target) => {
223
226
  const cls = EntitySchema.is(target) ? target.meta.class : target;
224
- return typeof cls !== 'function' || this.#discovered.some(m => m.class === cls);
227
+ return typeof cls !== 'function' || discoveredClasses.has(cls);
225
228
  };
226
229
  const missing = [];
227
230
  this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
228
231
  const indirect = (prop.kind === ReferenceKind.MANY_TO_MANY ? prop.pivotEntity : prop.through);
229
232
  if (indirect) {
230
233
  const target = typeof indirect === 'function' && !indirect.prototype ? indirect() : indirect;
231
- if (!this.#discovered.find(m => m.className === Utils.className(target)) || !discoveredByIdentity(target)) {
234
+ if (!discoveredNames.has(Utils.className(target)) || !discoveredByIdentity(target)) {
232
235
  missing.push(target);
233
236
  }
234
237
  }
@@ -236,7 +239,7 @@ export class MetadataDiscovery {
236
239
  const target = typeof prop.entity === 'function' && !prop.entity.prototype ? prop.entity() : prop.type;
237
240
  if (!unwrap(prop.type)
238
241
  .split(/ ?\| ?/)
239
- .every(type => this.#discovered.find(m => m.className === type)) ||
242
+ .every(type => discoveredNames.has(type)) ||
240
243
  !Utils.asArray(target).every(discoveredByIdentity)) {
241
244
  missing.push(...Utils.asArray(target));
242
245
  }
@@ -302,7 +305,8 @@ export class MetadataDiscovery {
302
305
  if (validate) {
303
306
  this.#validator.validateDiscovered(this.#discovered, this.#config.get('discovery'));
304
307
  }
305
- return this.#discovered.filter(meta => found.find(m => m.name === meta.className));
308
+ const foundNames = new Set(found.map(m => m.name));
309
+ return this.#discovered.filter(meta => foundNames.has(meta.className));
306
310
  }
307
311
  reset(entityName) {
308
312
  const exists = this.#discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
@@ -668,20 +672,47 @@ export class MetadataDiscovery {
668
672
  }
669
673
  return [];
670
674
  }
671
- findReferencingProperties(meta, metadata) {
675
+ /**
676
+ * Groups the relations of all entities by target class name in one pass, so `findReferencingProperties()`
677
+ * does not go over the relations of every entity for each entity. Lists keep the order of `metadata` and `relations`.
678
+ */
679
+ indexReferencingProperties(metadata) {
680
+ const index = { byType: new Map(), withTargetKey: new Map() };
681
+ const add = (map, key, value) => {
682
+ const list = map.get(key);
683
+ if (list) {
684
+ list.push(value);
685
+ }
686
+ else {
687
+ map.set(key, [value]);
688
+ }
689
+ };
672
690
  for (const meta2 of metadata) {
673
691
  for (const prop2 of meta2.relations) {
674
- if (prop2.kind !== ReferenceKind.SCALAR && prop2.type === meta.className) {
675
- meta.referencingProperties.push({ meta: meta2, prop: prop2 });
692
+ if (prop2.kind !== ReferenceKind.SCALAR) {
693
+ add(index.byType, prop2.type, [meta2, prop2]);
676
694
  }
677
- if (prop2.targetKey &&
678
- (prop2.type === meta.className || prop2.polymorphTargets?.some(t => t.className === meta.className)) &&
679
- !(meta.primaryKeys.length === 1 && meta.primaryKeys[0] === prop2.targetKey) &&
680
- !meta.root.targetKeys?.includes(prop2.targetKey)) {
681
- (meta.root.targetKeys ??= []).push(prop2.targetKey);
695
+ if (prop2.targetKey) {
696
+ const targets = new Set([prop2.type, ...(prop2.polymorphTargets?.map(t => t.className) ?? [])]);
697
+ for (const target of targets) {
698
+ add(index.withTargetKey, target, prop2);
699
+ }
682
700
  }
683
701
  }
684
702
  }
703
+ return index;
704
+ }
705
+ findReferencingProperties(meta, index) {
706
+ for (const [meta2, prop2] of index.byType.get(meta.className) ?? []) {
707
+ meta.referencingProperties.push({ meta: meta2, prop: prop2 });
708
+ }
709
+ for (const prop2 of index.withTargetKey.get(meta.className) ?? []) {
710
+ const targetKey = prop2.targetKey;
711
+ if (!(meta.primaryKeys.length === 1 && meta.primaryKeys[0] === targetKey) &&
712
+ !meta.root.targetKeys?.includes(targetKey)) {
713
+ (meta.root.targetKeys ??= []).push(targetKey);
714
+ }
715
+ }
685
716
  }
686
717
  initFactoryField(meta, prop) {
687
718
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
@@ -1906,7 +1937,7 @@ export class MetadataDiscovery {
1906
1937
  if (prop.customType && (Array.isArray(prop.default) || prop.customType.compareAsType() !== 'any')) {
1907
1938
  val = prop.customType.convertToDatabaseValue(prop.default, this.#platform);
1908
1939
  }
1909
- prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
1940
+ prop.defaultRaw = typeof val === 'string' ? `'${val.replaceAll("'", "''")}'` : '' + val;
1910
1941
  }
1911
1942
  inferTypeFromDefault(prop) {
1912
1943
  if ((prop.defaultRaw == null && prop.default == null) || prop.type !== 'any') {
@@ -9,6 +9,7 @@ export declare class MetadataStorage {
9
9
  /** Returns the global metadata dictionary, or a specific entry by entity name and path (keyed by the class reference when `target` is provided). */
10
10
  static getMetadata(): Dictionary<EntityMetadata>;
11
11
  static getMetadata<T = any>(entity: string, path: string, target?: EntityCtor): EntityMetadata<T>;
12
+ private static hashPath;
12
13
  /** Checks whether an entity with the given class name exists in the global metadata. */
13
14
  static isKnownEntity(name: string): boolean;
14
15
  /** Clears all entries from the global metadata registry. */
@@ -13,6 +13,10 @@ export class MetadataStorage {
13
13
  static PATH_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.PATH_SYMBOL');
14
14
  static META_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.META_SYMBOL');
15
15
  static #metadata = getGlobalStorage('metadata');
16
+ /** Lives in the global storage next to the registry, so it stays in sync when several copies of this module share it (e.g. after `vi.resetModules()`). */
17
+ static #classNames = getGlobalStorage('metadata-class-names');
18
+ /** Hashes of entity file paths, decorators call `getMetadata()` with the same path for every decorated member. */
19
+ static #pathHashes = new Map();
16
20
  #metadataMap = new Map();
17
21
  #idMap;
18
22
  #classNameMap;
@@ -31,7 +35,7 @@ export class MetadataStorage {
31
35
  }
32
36
  }
33
37
  static getMetadata(entity, path, target) {
34
- const key = entity && path ? entity + '-' + Utils.hash(path) : null;
38
+ const key = entity && path ? entity + '-' + MetadataStorage.hashPath(path) : null;
35
39
  // Key the registry by the class reference when available, so two classes minified
36
40
  // to the same mangled name don't collide on the `className-path` key.
37
41
  if (key && target) {
@@ -43,23 +47,36 @@ export class MetadataStorage {
43
47
  }
44
48
  // Keep the name-keyed entry in sync, the class-keyed metadata survives `MetadataStorage.clear()`.
45
49
  MetadataStorage.#metadata[key] = target[MetadataStorage.META_SYMBOL];
50
+ MetadataStorage.#classNames.names?.add(target[MetadataStorage.META_SYMBOL].className);
46
51
  return target[MetadataStorage.META_SYMBOL];
47
52
  }
48
53
  if (key && !MetadataStorage.#metadata[key]) {
49
- MetadataStorage.#metadata[key] = new EntityMetadata({ className: entity, path });
54
+ const meta = new EntityMetadata({ className: entity, path });
55
+ MetadataStorage.#metadata[key] = meta;
56
+ MetadataStorage.#classNames.names?.add(meta.className);
50
57
  }
51
58
  if (key) {
52
59
  return MetadataStorage.#metadata[key];
53
60
  }
54
61
  return MetadataStorage.#metadata;
55
62
  }
63
+ static hashPath(path) {
64
+ let hash = MetadataStorage.#pathHashes.get(path);
65
+ if (hash === undefined) {
66
+ hash = Utils.hash(path);
67
+ MetadataStorage.#pathHashes.set(path, hash);
68
+ }
69
+ return hash;
70
+ }
56
71
  /** Checks whether an entity with the given class name exists in the global metadata. */
57
72
  static isKnownEntity(name) {
58
- return !!Object.values(this.#metadata).find(meta => meta.className === name);
73
+ this.#classNames.names ??= new Set(Object.values(this.#metadata).map(meta => meta.className));
74
+ return this.#classNames.names.has(name);
59
75
  }
60
76
  /** Clears all entries from the global metadata registry. */
61
77
  static clear() {
62
78
  Object.keys(this.#metadata).forEach(k => delete this.#metadata[k]);
79
+ delete this.#classNames.names;
63
80
  }
64
81
  /** Returns the map of all registered entity metadata. */
65
82
  getAll() {
@@ -118,12 +118,13 @@ export class MetadataValidator {
118
118
  return Utils.className(p);
119
119
  };
120
120
  const pivotProps = new Map();
121
+ const discoveredNames = new Set(discovered.map(m => m.className));
121
122
  // check for not discovered entities
122
123
  discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
123
124
  if (prop.kind !== ReferenceKind.SCALAR &&
124
125
  !unwrap(prop.type)
125
126
  .split(/ ?\| ?/)
126
- .every(type => discovered.find(m => m.className === type))) {
127
+ .every(type => discoveredNames.has(type))) {
127
128
  throw MetadataError.fromUnknownEntity(prop.type, `${meta.className}.${prop.name}`);
128
129
  }
129
130
  if (prop.pivotEntity) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.2.6-dev.4",
3
+ "version": "7.2.6-dev.40",
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/typings.js CHANGED
@@ -304,8 +304,11 @@ export class EntityMetadata {
304
304
  }, { __gettersDefined: { value: true, enumerable: false } });
305
305
  }
306
306
  initIndexes(prop) {
307
- const simpleIndex = this.indexes.find(index => index.properties === prop.name && !index.options && !index.type && !index.expression && index.where == null);
308
- const simpleUnique = this.uniques.find(index => index.properties === prop.name && !index.options && !index.expression && index.where == null);
307
+ // `prop.index`/`prop.unique` keep only the name, so an index with any other option must stay in `meta.indexes`
308
+ const isSimple = (index) => index.properties === prop.name &&
309
+ Object.entries(index).every(([key, value]) => key === 'name' || key === 'properties' || value == null);
310
+ const simpleIndex = this.indexes.find(isSimple);
311
+ const simpleUnique = this.uniques.find(isSimple);
309
312
  const owner = prop.kind === ReferenceKind.MANY_TO_ONE;
310
313
  if (!prop.index && simpleIndex) {
311
314
  Utils.defaultValue(simpleIndex, 'name', true);
@@ -1,6 +1,7 @@
1
1
  import { clone } from './clone.js';
2
2
  import { ReferenceKind } from '../enums.js';
3
3
  import { compareArrays, compareBooleans, compareBuffers, compareObjects, equals, parseJsonSafe, Utils, } from './Utils.js';
4
+ import { DateType } from '../types/DateType.js';
4
5
  import { JsonType } from '../types/JsonType.js';
5
6
  import { Raw } from './RawQueryFragment.js';
6
7
  import { EntityIdentifier } from '../entity/EntityIdentifier.js';
@@ -395,6 +396,14 @@ export class EntityComparator {
395
396
  lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
396
397
  lines.push(`${padding} }`);
397
398
  }
399
+ else if (prop.customType instanceof DateType) {
400
+ // tedious returns DATE columns as `Date`, map them to the string the entity holds, so the snapshot matches it
401
+ context.set('convertDateToJSValue', (value) => this.#platform.convertDateToJSValue(value));
402
+ lines.push(`${padding} if (${defined}) {`);
403
+ lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} instanceof Date ? convertDateToJSValue(${this.propName(prop.fieldNames[0])}) : ${this.propName(prop.fieldNames[0])};`);
404
+ lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
405
+ lines.push(`${padding} }`);
406
+ }
398
407
  else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
399
408
  const idx = this.#tmpIndex++;
400
409
  context.set(`mapEmbeddedResult_${idx}`, (data) => {
@@ -308,6 +308,13 @@ export class QueryHelper {
308
308
  o[key] = { [op]: value };
309
309
  return o;
310
310
  }
311
+ // `$all` items are matched like separate `$some` payloads, so they need the same processing
312
+ if (key === '$all' && Array.isArray(value)) {
313
+ o[key] = value.map(item => Utils.isPlainObject(item)
314
+ ? QueryHelper.processWhere({ ...options, where: item, entityName, root: false })
315
+ : item);
316
+ return o;
317
+ }
311
318
  if (Utils.isPlainObject(value)) {
312
319
  o[key] = QueryHelper.processWhere({
313
320
  ...options,
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.4';
156
+ static #ORM_VERSION = '7.2.6-dev.40';
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}`;
@@ -651,14 +651,19 @@ export class Utils {
651
651
  static isCollection(item) {
652
652
  return Collection.isCollection(item);
653
653
  }
654
- // FNV-1a 64-bit
654
+ // FNV-1a 64-bit, computed on two uint32 halves instead of a BigInt. The prime is 2^40 + 0x1b3, so
655
+ // `h * prime mod 2^64` is `h * 0x1b3` plus the low half shifted up by 40 bits. Every partial product
656
+ // stays below 2^41, so the double arithmetic is exact.
655
657
  static hash(data, length) {
656
- let h1 = 0xcbf29ce484222325n;
658
+ let hi = 0xcbf29ce4;
659
+ let lo = 0x84222325;
657
660
  for (let i = 0; i < data.length; i++) {
658
- h1 ^= BigInt(data.charCodeAt(i));
659
- h1 = (h1 * 0x100000001b3n) & 0xffffffffffffffffn;
661
+ lo = (lo ^ data.charCodeAt(i)) >>> 0;
662
+ const loProduct = lo * 0x1b3;
663
+ hi = (((hi * 0x1b3) >>> 0) + Math.floor(loProduct / 0x100000000) + ((lo << 8) >>> 0)) >>> 0;
664
+ lo = loProduct >>> 0;
660
665
  }
661
- const hash = h1.toString(16).padStart(16, '0');
666
+ const hash = hi.toString(16).padStart(8, '0') + lo.toString(16).padStart(8, '0');
662
667
  if (length) {
663
668
  return hash.substring(0, length);
664
669
  }
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);