@mikro-orm/core 7.0.0-dev.113 → 7.0.0-dev.115

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 (61) hide show
  1. package/EntityManager.d.ts +8 -8
  2. package/EntityManager.js +47 -69
  3. package/MikroORM.d.ts +1 -1
  4. package/MikroORM.js +2 -3
  5. package/drivers/DatabaseDriver.d.ts +11 -11
  6. package/drivers/DatabaseDriver.js +8 -9
  7. package/drivers/IDatabaseDriver.d.ts +12 -12
  8. package/entity/Collection.js +7 -6
  9. package/entity/EntityAssigner.js +9 -9
  10. package/entity/EntityFactory.js +14 -17
  11. package/entity/EntityHelper.d.ts +2 -2
  12. package/entity/EntityHelper.js +2 -2
  13. package/entity/EntityLoader.d.ts +3 -3
  14. package/entity/EntityLoader.js +22 -35
  15. package/entity/WrappedEntity.js +1 -1
  16. package/entity/defineEntity.d.ts +11 -11
  17. package/entity/validators.js +2 -2
  18. package/errors.d.ts +8 -8
  19. package/errors.js +14 -13
  20. package/hydration/ObjectHydrator.js +25 -18
  21. package/metadata/EntitySchema.d.ts +5 -5
  22. package/metadata/EntitySchema.js +23 -21
  23. package/metadata/MetadataDiscovery.d.ts +2 -3
  24. package/metadata/MetadataDiscovery.js +119 -92
  25. package/metadata/MetadataProvider.js +2 -0
  26. package/metadata/MetadataStorage.d.ts +13 -6
  27. package/metadata/MetadataStorage.js +64 -19
  28. package/metadata/MetadataValidator.d.ts +2 -2
  29. package/metadata/MetadataValidator.js +22 -28
  30. package/metadata/types.d.ts +3 -3
  31. package/package.json +1 -1
  32. package/platforms/Platform.js +2 -2
  33. package/serialization/EntitySerializer.js +2 -2
  34. package/serialization/EntityTransformer.js +6 -6
  35. package/serialization/SerializationContext.d.ts +6 -6
  36. package/typings.d.ts +19 -17
  37. package/typings.js +15 -10
  38. package/unit-of-work/ChangeSet.d.ts +2 -3
  39. package/unit-of-work/ChangeSet.js +2 -3
  40. package/unit-of-work/ChangeSetComputer.js +3 -3
  41. package/unit-of-work/ChangeSetPersister.js +14 -14
  42. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  43. package/unit-of-work/CommitOrderCalculator.js +13 -13
  44. package/unit-of-work/UnitOfWork.d.ts +3 -3
  45. package/unit-of-work/UnitOfWork.js +46 -45
  46. package/utils/AbstractSchemaGenerator.js +7 -7
  47. package/utils/Configuration.d.ts +0 -5
  48. package/utils/Cursor.js +3 -3
  49. package/utils/DataloaderUtils.js +13 -11
  50. package/utils/EntityComparator.d.ts +6 -6
  51. package/utils/EntityComparator.js +30 -27
  52. package/utils/QueryHelper.d.ts +6 -6
  53. package/utils/QueryHelper.js +18 -17
  54. package/utils/RawQueryFragment.d.ts +11 -12
  55. package/utils/RawQueryFragment.js +28 -55
  56. package/utils/TransactionManager.js +1 -1
  57. package/utils/Utils.d.ts +3 -1
  58. package/utils/Utils.js +10 -2
  59. package/utils/clone.js +7 -21
  60. package/utils/env-vars.js +0 -1
  61. package/utils/upsert-utils.d.ts +4 -4
@@ -11,7 +11,7 @@ export class DataloaderUtils {
11
11
  static groupPrimaryKeysByEntityAndOpts(refsWithOpts) {
12
12
  const map = new Map();
13
13
  for (const [ref, opts] of refsWithOpts) {
14
- /* The key is a combination of the className and a stringified version if the load options because we want
14
+ /* The key is a combination of the uniqueName (a unique table name based identifier) and a stringified version if the load options because we want
15
15
  to map each combination of entities/options into separate find queries in order to return accurate results.
16
16
  This could be further optimized finding the "lowest common denominator" among the different options
17
17
  for each Entity and firing a single query for each Entity instead of Entity+options combination.
@@ -24,7 +24,7 @@ export class DataloaderUtils {
24
24
  Thus such approach should probably be configurable, if not opt-in.
25
25
  NOTE: meta + opts multi maps (https://github.com/martian17/ds-js) might be a more elegant way
26
26
  to implement this but not necessarily faster. */
27
- const key = `${helper(ref).__meta.className}|${JSON.stringify(opts ?? {})}`;
27
+ const key = `${helper(ref).__meta.uniqueName}|${JSON.stringify(opts ?? {})}`;
28
28
  let primaryKeysSet = map.get(key);
29
29
  if (primaryKeysSet == null) {
30
30
  primaryKeysSet = new Set();
@@ -42,9 +42,10 @@ export class DataloaderUtils {
42
42
  return async (refsWithOpts) => {
43
43
  const groupedIdsMap = DataloaderUtils.groupPrimaryKeysByEntityAndOpts(refsWithOpts);
44
44
  const promises = Array.from(groupedIdsMap).map(([key, idsSet]) => {
45
- const className = key.substring(0, key.indexOf('|'));
45
+ const uniqueName = key.substring(0, key.indexOf('|'));
46
46
  const opts = JSON.parse(key.substring(key.indexOf('|') + 1));
47
- return em.find(className, Array.from(idsSet), opts);
47
+ const meta = em.getMetadata().getByUniqueName(uniqueName);
48
+ return em.find(meta.class, Array.from(idsSet), opts);
48
49
  });
49
50
  await Promise.all(promises);
50
51
  /* Instead of assigning each find result to the original reference we use a shortcut
@@ -70,7 +71,7 @@ export class DataloaderUtils {
70
71
  The value is another Map which we can use to filter the find query to get results pertaining to the collections that have been dataloaded:
71
72
  its keys are the props we are going to filter to and its values are the corresponding PKs.
72
73
  */
73
- const key = `${col.property.targetMeta.className}|${JSON.stringify(opts ?? {})}`;
74
+ const key = `${col.property.targetMeta.uniqueName}|${JSON.stringify(opts ?? {})}`;
74
75
  let filterMap = entitiesMap.get(key); // We are going to use this map to filter the entities pertaining to the collections that have been dataloaded.
75
76
  if (filterMap == null) {
76
77
  filterMap = new Map();
@@ -97,9 +98,10 @@ export class DataloaderUtils {
97
98
  */
98
99
  static entitiesAndOptsMapToQueries(entitiesAndOptsMap, em) {
99
100
  return Array.from(entitiesAndOptsMap, async ([key, filterMap]) => {
100
- const className = key.substring(0, key.indexOf('|'));
101
+ const uniqueName = key.substring(0, key.indexOf('|'));
101
102
  const opts = JSON.parse(key.substring(key.indexOf('|') + 1));
102
- const res = await em.find(className, opts?.where != null && Object.keys(opts.where).length > 0 ?
103
+ const meta = em.getMetadata().getByUniqueName(uniqueName);
104
+ const res = await em.find(meta.class, opts?.where != null && Object.keys(opts.where).length > 0 ?
103
105
  {
104
106
  $and: [
105
107
  {
@@ -121,7 +123,7 @@ export class DataloaderUtils {
121
123
  ...(opts.populate === false ? [] : opts.populate ?? []),
122
124
  ...Array.from(filterMap.keys()).filter(
123
125
  // We need to do so only if the inverse side is a collection, because we can already retrieve the PK from a reference without having to load it
124
- prop => em.getMetadata(className).properties[prop]?.ref !== true),
126
+ prop => meta.properties[prop]?.ref !== true),
125
127
  ],
126
128
  });
127
129
  return [key, res];
@@ -164,7 +166,7 @@ export class DataloaderUtils {
164
166
  // We need to filter the results in order to map each input collection
165
167
  // to a subset of each query matching the collection items.
166
168
  return collsWithOpts.map(([col, opts]) => {
167
- const key = `${col.property.targetMeta.className}|${JSON.stringify(opts ?? {})}`;
169
+ const key = `${col.property.targetMeta.uniqueName}|${JSON.stringify(opts ?? {})}`;
168
170
  const entities = resultsMap.get(key);
169
171
  if (entities == null) {
170
172
  // Should never happen
@@ -183,7 +185,7 @@ export class DataloaderUtils {
183
185
  return async (collsWithOpts) => {
184
186
  const groups = new Map();
185
187
  for (const [col, opts] of collsWithOpts) {
186
- const key = `${col.property.targetMeta.className}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
188
+ const key = `${col.property.targetMeta.uniqueName}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
187
189
  const value = groups.get(key) ?? [];
188
190
  value.push([col, opts ?? {}]);
189
191
  groups.set(key, value);
@@ -198,7 +200,7 @@ export class DataloaderUtils {
198
200
  const owners = group.map(c => c[0].owner);
199
201
  const $or = [];
200
202
  // a bit of a hack, but we need to prefix the key, since we have only a column name, not a property name
201
- const alias = em.config.getNamingStrategy().aliasName(prop.pivotEntity, 0);
203
+ const alias = em.config.getNamingStrategy().aliasName(Utils.className(prop.pivotEntity), 0);
202
204
  const fk = `${alias}.${Utils.getPrimaryKeyHash(prop.joinColumns)}`;
203
205
  for (const c of group) {
204
206
  $or.push({ $and: [c[1]?.where ?? {}, { [fk]: c[0].owner }] });
@@ -20,19 +20,19 @@ export declare class EntityComparator {
20
20
  /**
21
21
  * Computes difference between two entities.
22
22
  */
23
- diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T>, options?: {
23
+ diffEntities<T extends object>(entityName: EntityName<T>, a: EntityData<T>, b: EntityData<T>, options?: {
24
24
  includeInverseSides?: boolean;
25
25
  }): EntityData<T>;
26
- matching<T>(entityName: string, a: EntityData<T>, b: EntityData<T>): boolean;
26
+ matching<T extends object>(entityName: EntityName<T>, a: EntityData<T>, b: EntityData<T>): boolean;
27
27
  /**
28
28
  * Removes ORM specific code from entities and prepares it for serializing. Used before change set computation.
29
29
  * References will be mapped to primary keys, collections to arrays of primary keys.
30
30
  */
31
- prepareEntity<T>(entity: T): EntityData<T>;
31
+ prepareEntity<T extends object>(entity: T): EntityData<T>;
32
32
  /**
33
33
  * Maps database columns to properties.
34
34
  */
35
- mapResult<T>(entityName: string, result: EntityDictionary<T>): EntityData<T>;
35
+ mapResult<T>(meta: EntityMetadata<T>, result: EntityDictionary<T>): EntityData<T>;
36
36
  /**
37
37
  * @internal Highly performance-sensitive method.
38
38
  */
@@ -64,7 +64,7 @@ export declare class EntityComparator {
64
64
  /**
65
65
  * @internal Highly performance-sensitive method.
66
66
  */
67
- getResultMapper<T>(entityName: string): ResultMapper<T>;
67
+ getResultMapper<T>(meta: EntityMetadata<T>): ResultMapper<T>;
68
68
  private getPropertyCondition;
69
69
  private getEmbeddedArrayPropertySnapshot;
70
70
  /**
@@ -78,7 +78,7 @@ export declare class EntityComparator {
78
78
  /**
79
79
  * @internal Highly performance-sensitive method.
80
80
  */
81
- getEntityComparator<T extends object>(entityName: string): Comparator<T>;
81
+ getEntityComparator<T extends object>(entityName: EntityName<T>): Comparator<T>;
82
82
  private getGenericComparator;
83
83
  private getPropertyComparator;
84
84
  private wrap;
@@ -2,7 +2,7 @@ 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
4
  import { JsonType } from '../types/JsonType.js';
5
- import { RawQueryFragment } from './RawQueryFragment.js';
5
+ import { Raw } from './RawQueryFragment.js';
6
6
  export class EntityComparator {
7
7
  metadata;
8
8
  platform;
@@ -33,21 +33,21 @@ export class EntityComparator {
33
33
  * References will be mapped to primary keys, collections to arrays of primary keys.
34
34
  */
35
35
  prepareEntity(entity) {
36
- const generator = this.getSnapshotGenerator(entity.constructor.name);
36
+ const generator = this.getSnapshotGenerator(entity.constructor);
37
37
  return Utils.callCompiledFunction(generator, entity);
38
38
  }
39
39
  /**
40
40
  * Maps database columns to properties.
41
41
  */
42
- mapResult(entityName, result) {
43
- const mapper = this.getResultMapper(entityName);
42
+ mapResult(meta, result) {
43
+ const mapper = this.getResultMapper(meta);
44
44
  return Utils.callCompiledFunction(mapper, result);
45
45
  }
46
46
  /**
47
47
  * @internal Highly performance-sensitive method.
48
48
  */
49
49
  getPkGetter(meta) {
50
- const exists = this.pkGetters.get(meta.className);
50
+ const exists = this.pkGetters.get(meta);
51
51
  /* v8 ignore next */
52
52
  if (exists) {
53
53
  return exists;
@@ -90,14 +90,14 @@ export class EntityComparator {
90
90
  const code = `// compiled pk serializer for entity ${meta.className}\n`
91
91
  + `return function(entity) {\n${lines.join('\n')}\n}`;
92
92
  const pkSerializer = Utils.createFunction(context, code);
93
- this.pkGetters.set(meta.className, pkSerializer);
93
+ this.pkGetters.set(meta, pkSerializer);
94
94
  return pkSerializer;
95
95
  }
96
96
  /**
97
97
  * @internal Highly performance-sensitive method.
98
98
  */
99
99
  getPkGetterConverted(meta) {
100
- const exists = this.pkGettersConverted.get(meta.className);
100
+ const exists = this.pkGettersConverted.get(meta);
101
101
  /* v8 ignore next */
102
102
  if (exists) {
103
103
  return exists;
@@ -140,14 +140,14 @@ export class EntityComparator {
140
140
  const code = `// compiled pk getter (with converted custom types) for entity ${meta.className}\n`
141
141
  + `return function(entity) {\n${lines.join('\n')}\n}`;
142
142
  const pkSerializer = Utils.createFunction(context, code);
143
- this.pkGettersConverted.set(meta.className, pkSerializer);
143
+ this.pkGettersConverted.set(meta, pkSerializer);
144
144
  return pkSerializer;
145
145
  }
146
146
  /**
147
147
  * @internal Highly performance-sensitive method.
148
148
  */
149
149
  getPkSerializer(meta) {
150
- const exists = this.pkSerializers.get(meta.className);
150
+ const exists = this.pkSerializers.get(meta);
151
151
  /* v8 ignore next */
152
152
  if (exists) {
153
153
  return exists;
@@ -192,19 +192,18 @@ export class EntityComparator {
192
192
  const code = `// compiled pk serializer for entity ${meta.className}\n`
193
193
  + `return function(entity) {\n${lines.join('\n')}\n}`;
194
194
  const pkSerializer = Utils.createFunction(context, code);
195
- this.pkSerializers.set(meta.className, pkSerializer);
195
+ this.pkSerializers.set(meta, pkSerializer);
196
196
  return pkSerializer;
197
197
  }
198
198
  /**
199
199
  * @internal Highly performance-sensitive method.
200
200
  */
201
201
  getSnapshotGenerator(entityName) {
202
- entityName = Utils.className(entityName);
203
- const exists = this.snapshotGenerators.get(entityName);
202
+ const meta = this.metadata.find(entityName);
203
+ const exists = this.snapshotGenerators.get(meta);
204
204
  if (exists) {
205
205
  return exists;
206
206
  }
207
- const meta = this.metadata.find(entityName);
208
207
  const lines = [];
209
208
  const context = new Map();
210
209
  context.set('clone', clone);
@@ -222,7 +221,7 @@ export class EntityComparator {
222
221
  .forEach(prop => lines.push(this.getPropertySnapshot(meta, prop, context, this.wrap(prop.name), this.wrap(prop.name), [prop.name])));
223
222
  const code = `return function(entity) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
224
223
  const snapshotGenerator = Utils.createFunction(context, code);
225
- this.snapshotGenerators.set(entityName, snapshotGenerator);
224
+ this.snapshotGenerators.set(meta, snapshotGenerator);
226
225
  return snapshotGenerator;
227
226
  }
228
227
  /**
@@ -272,12 +271,11 @@ export class EntityComparator {
272
271
  /**
273
272
  * @internal Highly performance-sensitive method.
274
273
  */
275
- getResultMapper(entityName) {
276
- const exists = this.mappers.get(entityName);
274
+ getResultMapper(meta) {
275
+ const exists = this.mappers.get(meta);
277
276
  if (exists) {
278
277
  return exists;
279
278
  }
280
- const meta = this.metadata.get(entityName);
281
279
  const lines = [];
282
280
  const context = new Map();
283
281
  const tz = this.platform.getTimezone();
@@ -336,9 +334,9 @@ export class EntityComparator {
336
334
  context.set(`mapEmbeddedResult_${idx}`, (data) => {
337
335
  const item = parseJsonSafe(data);
338
336
  if (Array.isArray(item)) {
339
- return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
337
+ return item.map(row => row == null ? row : this.getResultMapper(prop.targetMeta)(row));
340
338
  }
341
- return item == null ? item : this.getResultMapper(prop.type)(item);
339
+ return item == null ? item : this.getResultMapper(prop.targetMeta)(item);
342
340
  });
343
341
  lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
344
342
  lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
@@ -371,7 +369,7 @@ export class EntityComparator {
371
369
  const code = `// compiled mapper for entity ${meta.className}\n`
372
370
  + `return function(result) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
373
371
  const resultMapper = Utils.createFunction(context, code);
374
- this.mappers.set(entityName, resultMapper);
372
+ this.mappers.set(meta, resultMapper);
375
373
  return resultMapper;
376
374
  }
377
375
  getPropertyCondition(path) {
@@ -482,7 +480,7 @@ export class EntityComparator {
482
480
  const convertorKey = this.safeKey(prop.name);
483
481
  context.set(`convertToDatabaseValue_${convertorKey}`, (val) => {
484
482
  /* v8 ignore next */
485
- if (RawQueryFragment.isKnownFragment(val)) {
483
+ if (Raw.isKnownFragment(val)) {
486
484
  return val;
487
485
  }
488
486
  return prop.customType.convertToDatabaseValue(val, this.platform, { mode: 'serialization' });
@@ -544,11 +542,11 @@ export class EntityComparator {
544
542
  * @internal Highly performance-sensitive method.
545
543
  */
546
544
  getEntityComparator(entityName) {
547
- const exists = this.comparators.get(entityName);
545
+ const meta = this.metadata.find(entityName);
546
+ const exists = this.comparators.get(meta);
548
547
  if (exists) {
549
548
  return exists;
550
549
  }
551
- const meta = this.metadata.find(entityName);
552
550
  const lines = [];
553
551
  const context = new Map();
554
552
  context.set('compareArrays', compareArrays);
@@ -570,7 +568,7 @@ export class EntityComparator {
570
568
  const code = `// compiled comparator for entity ${meta.className}\n`
571
569
  + `return function(last, current, options) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
572
570
  const comparator = Utils.createFunction(context, code);
573
- this.comparators.set(entityName, comparator);
571
+ this.comparators.set(meta, comparator);
574
572
  return comparator;
575
573
  }
576
574
  getGenericComparator(prop, cond) {
@@ -586,18 +584,23 @@ export class EntityComparator {
586
584
  getPropertyComparator(prop, context) {
587
585
  let type = prop.type.toLowerCase();
588
586
  if (prop.kind !== ReferenceKind.SCALAR && prop.kind !== ReferenceKind.EMBEDDED) {
589
- const meta2 = this.metadata.find(prop.type);
587
+ const meta2 = prop.targetMeta;
590
588
  if (meta2.primaryKeys.length > 1) {
591
589
  type = 'array';
592
590
  }
593
591
  else {
594
- type = meta2.properties[meta2.primaryKeys[0]].type.toLowerCase();
592
+ type = meta2.getPrimaryProp().type.toLowerCase();
595
593
  }
596
594
  }
597
595
  if (prop.customType) {
598
596
  if (prop.customType.compareValues) {
599
597
  const idx = this.tmpIndex++;
600
- context.set(`compareValues_${idx}`, (a, b) => prop.customType.compareValues(a, b));
598
+ context.set(`compareValues_${idx}`, (a, b) => {
599
+ if (Raw.isKnownFragment(a) || Raw.isKnownFragment(b)) {
600
+ return Raw.getKnownFragment(a) === Raw.getKnownFragment(b);
601
+ }
602
+ return prop.customType.compareValues(a, b);
603
+ });
601
604
  return this.getGenericComparator(this.wrap(prop.name), `!compareValues_${idx}(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
602
605
  }
603
606
  type = prop.customType.compareAsType().toLowerCase();
@@ -1,4 +1,4 @@
1
- import type { Dictionary, EntityMetadata, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
1
+ import type { Dictionary, EntityMetadata, EntityName, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
3
  import type { MetadataStorage } from '../metadata/MetadataStorage.js';
4
4
  import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
@@ -6,7 +6,7 @@ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
6
6
  export declare class QueryHelper {
7
7
  static readonly SUPPORTED_OPERATORS: string[];
8
8
  static processParams(params: unknown): any;
9
- static processObjectParams<T extends object>(params?: T): T;
9
+ static processObjectParams<T extends Dictionary>(params?: T): T;
10
10
  /**
11
11
  * converts `{ account: { $or: [ [Object], [Object] ] } }`
12
12
  * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
@@ -14,9 +14,9 @@ export declare class QueryHelper {
14
14
  static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
15
15
  static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
16
16
  static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
17
- static getActiveFilters(entityName: string, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
17
+ static getActiveFilters<T>(meta: EntityMetadata<T>, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
18
18
  static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
19
- static isFilterActive(entityName: string, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
19
+ static isFilterActive<T>(meta: EntityMetadata<T>, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
20
20
  static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
21
21
  private static isSupportedOperator;
22
22
  private static processJsonCondition;
@@ -25,11 +25,11 @@ export declare class QueryHelper {
25
25
  }
26
26
  interface ProcessWhereOptions<T> {
27
27
  where: FilterQuery<T>;
28
- entityName: string;
28
+ entityName: EntityName<T>;
29
29
  metadata: MetadataStorage;
30
30
  platform: Platform;
31
31
  aliased?: boolean;
32
- aliasMap?: Dictionary<string>;
32
+ aliasMap?: Dictionary<EntityName>;
33
33
  convertCustomTypes?: boolean;
34
34
  root?: boolean;
35
35
  type?: 'where' | 'orderBy';
@@ -3,7 +3,7 @@ import { Utils } from './Utils.js';
3
3
  import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
- import { isRaw, RawQueryFragment } from './RawQueryFragment.js';
6
+ import { isRaw, Raw } from './RawQueryFragment.js';
7
7
  /** @internal */
8
8
  export class QueryHelper {
9
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
@@ -29,7 +29,7 @@ export class QueryHelper {
29
29
  return params;
30
30
  }
31
31
  static processObjectParams(params = {}) {
32
- Utils.keys(params).forEach(k => {
32
+ Utils.getObjectQueryKeys(params).forEach(k => {
33
33
  params[k] = QueryHelper.processParams(params[k]);
34
34
  });
35
35
  return params;
@@ -98,7 +98,7 @@ export class QueryHelper {
98
98
  }));
99
99
  }
100
100
  Object.keys(where).forEach(k => {
101
- const meta2 = metadata.find(meta.properties[k]?.type) || meta;
101
+ const meta2 = metadata.find(meta.properties[k]?.targetMeta?.class) || meta;
102
102
  if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
103
103
  where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
104
104
  }
@@ -126,7 +126,7 @@ export class QueryHelper {
126
126
  where = { [Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
127
127
  }
128
128
  if (Array.isArray(where) && root) {
129
- const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : entityName;
129
+ const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : Utils.className(entityName);
130
130
  let cond = { [rootPrimaryKey]: { $in: where } };
131
131
  // @ts-ignore
132
132
  // detect tuple comparison, use `$or` in case the number of constituents don't match
@@ -138,12 +138,10 @@ export class QueryHelper {
138
138
  if (!Utils.isPlainObject(where)) {
139
139
  return where;
140
140
  }
141
- return Object.keys(where).reduce((o, key) => {
141
+ return Utils.getObjectQueryKeys(where).reduce((o, key) => {
142
142
  let value = where[key];
143
- const prop = this.findProperty(key, options);
144
- const keys = prop?.joinColumns?.length ?? 0;
145
- const composite = keys > 1;
146
- if (Array.isArray(value) && value.length === 0 && RawQueryFragment.isKnownFragment(key)) {
143
+ const customExpression = Raw.isKnownFragmentSymbol(key);
144
+ if (Array.isArray(value) && value.length === 0 && customExpression) {
147
145
  o[key] = value;
148
146
  return o;
149
147
  }
@@ -157,6 +155,9 @@ export class QueryHelper {
157
155
  o[rootPrimaryKey] = { [key]: QueryHelper.processWhere({ ...options, where: value, root: false }) };
158
156
  return o;
159
157
  }
158
+ const prop = customExpression ? null : this.findProperty(key, options);
159
+ const keys = prop?.joinColumns?.length ?? 0;
160
+ const composite = keys > 1;
160
161
  if (prop?.customType && convertCustomTypes && !isRaw(value)) {
161
162
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
162
163
  }
@@ -164,7 +165,7 @@ export class QueryHelper {
164
165
  if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
165
166
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
166
167
  }
167
- if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
168
+ if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
168
169
  // comparing single composite key - use $eq instead of $in
169
170
  const op = composite && !value.every(v => Array.isArray(v)) ? '$eq' : '$in';
170
171
  o[key] = { [op]: value };
@@ -174,7 +175,7 @@ export class QueryHelper {
174
175
  o[key] = QueryHelper.processWhere({
175
176
  ...options,
176
177
  where: value,
177
- entityName: prop?.type ?? entityName,
178
+ entityName: prop?.targetMeta?.class ?? entityName,
178
179
  root: false,
179
180
  });
180
181
  }
@@ -184,7 +185,7 @@ export class QueryHelper {
184
185
  return o;
185
186
  }, {});
186
187
  }
187
- static getActiveFilters(entityName, options, filters) {
188
+ static getActiveFilters(meta, options, filters) {
188
189
  if (options === false) {
189
190
  return [];
190
191
  }
@@ -196,7 +197,7 @@ export class QueryHelper {
196
197
  Object.keys(options).forEach(filter => opts[filter] = options[filter]);
197
198
  }
198
199
  return Object.keys(filters)
199
- .filter(f => QueryHelper.isFilterActive(entityName, f, filters[f], opts))
200
+ .filter(f => QueryHelper.isFilterActive(meta, f, filters[f], opts))
200
201
  .map(f => {
201
202
  filters[f].name = f;
202
203
  return filters[f];
@@ -220,8 +221,8 @@ export class QueryHelper {
220
221
  }
221
222
  return Utils.mergeConfig({}, propFilters, options);
222
223
  }
223
- static isFilterActive(entityName, filterName, filter, options) {
224
- if (filter.entity && !filter.entity.includes(entityName)) {
224
+ static isFilterActive(meta, filterName, filter, options) {
225
+ if (filter.entity && !filter.entity.includes(meta.className)) {
225
226
  return false;
226
227
  }
227
228
  if (options[filterName] === false) {
@@ -231,8 +232,8 @@ export class QueryHelper {
231
232
  }
232
233
  static processCustomType(prop, cond, platform, key, fromQuery) {
233
234
  if (Utils.isPlainObject(cond)) {
234
- return Utils.keys(cond).reduce((o, k) => {
235
- if (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k)) {
235
+ return Utils.getObjectQueryKeys(cond).reduce((o, k) => {
236
+ if (!Raw.isKnownFragmentSymbol(k) && (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k))) {
236
237
  o[k] = QueryHelper.processCustomType(prop, cond[k], platform, k, fromQuery);
237
238
  }
238
239
  else {
@@ -1,23 +1,22 @@
1
1
  import type { AnyString, Dictionary, EntityKey } from '../typings.js';
2
+ declare const rawFragmentSymbolBrand: unique symbol;
3
+ export type RawQueryFragmentSymbol = symbol & {
4
+ readonly [rawFragmentSymbolBrand]: true;
5
+ };
2
6
  export declare class RawQueryFragment {
3
7
  #private;
4
8
  readonly sql: string;
5
9
  readonly params: unknown[];
6
- static cloneRegistry?: Set<string>;
7
10
  constructor(sql: string, params?: unknown[]);
11
+ get key(): RawQueryFragmentSymbol;
8
12
  as(alias: string): RawQueryFragment;
9
- valueOf(): string;
13
+ [Symbol.toPrimitive](hint: 'string'): RawQueryFragmentSymbol;
14
+ get [Symbol.toStringTag](): string;
10
15
  toJSON(): string;
11
- toString(): string;
12
- clone(): RawQueryFragment;
13
- static run<T>(cb: (...args: any[]) => Promise<T>): Promise<T>;
14
- /**
15
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
16
- */
17
- static checkCacheSize(): number;
18
- static isKnownFragment(key: string | RawQueryFragment): boolean;
19
- static getKnownFragment(key: string | RawQueryFragment, cleanup?: boolean): RawQueryFragment | undefined;
20
- static remove(key: string): void;
16
+ static isKnownFragmentSymbol(key: unknown): key is RawQueryFragmentSymbol;
17
+ static hasObjectFragments(object: unknown): boolean;
18
+ static isKnownFragment(key: unknown): key is RawQueryFragment | symbol;
19
+ static getKnownFragment(key: unknown): RawQueryFragment | undefined;
21
20
  }
22
21
  export { RawQueryFragment as Raw };
23
22
  export declare function isRaw(value: unknown): value is RawQueryFragment;
@@ -1,81 +1,58 @@
1
1
  import { Utils } from './Utils.js';
2
- import { createAsyncContext } from './AsyncContext.js';
3
2
  export class RawQueryFragment {
4
3
  sql;
5
4
  params;
6
- static #rawQueryCache = new Map();
7
- static #storage = createAsyncContext();
8
- static #index = 0n;
9
- static cloneRegistry;
10
- #used = 0;
5
+ // holds a weak reference to fragments used as object keys
6
+ static #rawQueryReferences = new WeakMap();
11
7
  #key;
12
8
  constructor(sql, params = []) {
13
9
  this.sql = sql;
14
10
  this.params = params;
15
- this.#key = `[raw]: ${this.sql} (#${RawQueryFragment.#index++})`;
11
+ }
12
+ get key() {
13
+ if (!this.#key) {
14
+ this.#key = Symbol(this.toJSON());
15
+ RawQueryFragment.#rawQueryReferences.set(this.#key, this);
16
+ }
17
+ return this.#key;
16
18
  }
17
19
  as(alias) {
18
20
  return new RawQueryFragment(`${this.sql} as ??`, [...this.params, alias]);
19
21
  }
20
- valueOf() {
22
+ [Symbol.toPrimitive](hint) {
23
+ // if a fragment is converted to string (used as an object key), return a unique symbol
24
+ // and save a weak reference to map so we can retrieve it when compiling the query
25
+ if (hint === 'string') {
26
+ return this.key;
27
+ }
21
28
  throw new Error(`Trying to modify raw SQL fragment: '${this.sql}'`);
22
29
  }
23
- toJSON() {
24
- return this.#key;
25
- }
26
- toString() {
27
- RawQueryFragment.#rawQueryCache.set(this.#key, this);
28
- this.#used++;
29
- return this.#key;
30
+ get [Symbol.toStringTag]() {
31
+ return this.toJSON();
30
32
  }
31
- clone() {
32
- RawQueryFragment.cloneRegistry?.add(this.#key);
33
- return new RawQueryFragment(this.sql, this.params);
33
+ toJSON() {
34
+ return `raw('${this.sql}')`;
34
35
  }
35
- static async run(cb) {
36
- const removeStack = new Set();
37
- const res = await this.#storage.run(removeStack, cb);
38
- removeStack.forEach(key => RawQueryFragment.remove(key));
39
- removeStack.clear();
40
- return res;
36
+ static isKnownFragmentSymbol(key) {
37
+ return typeof key === 'symbol' && this.#rawQueryReferences.has(key);
41
38
  }
42
- /**
43
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
44
- */
45
- static checkCacheSize() {
46
- return this.#rawQueryCache.size;
39
+ static hasObjectFragments(object) {
40
+ return Utils.isPlainObject(object) && Object.getOwnPropertySymbols(object).some(symbol => this.isKnownFragmentSymbol(symbol));
47
41
  }
48
42
  static isKnownFragment(key) {
49
43
  if (key instanceof RawQueryFragment) {
50
44
  return true;
51
45
  }
52
- return this.#rawQueryCache.has(key);
46
+ return this.isKnownFragmentSymbol(key);
53
47
  }
54
- static getKnownFragment(key, cleanup = true) {
48
+ static getKnownFragment(key) {
55
49
  if (key instanceof RawQueryFragment) {
56
50
  return key;
57
51
  }
58
- const raw = this.#rawQueryCache.get(key);
59
- if (raw && cleanup) {
60
- this.remove(key);
61
- }
62
- return raw;
63
- }
64
- static remove(key) {
65
- const raw = this.#rawQueryCache.get(key);
66
- if (!raw) {
52
+ if (typeof key !== 'symbol') {
67
53
  return;
68
54
  }
69
- raw.#used--;
70
- if (raw.#used <= 0) {
71
- const removeStack = this.#storage.getStore();
72
- if (removeStack) {
73
- removeStack.add(key);
74
- }
75
- else {
76
- this.#rawQueryCache.delete(key);
77
- }
78
- }
55
+ return this.#rawQueryReferences.get(key);
79
56
  }
80
57
  /** @ignore */
81
58
  /* v8 ignore next */
@@ -193,11 +170,7 @@ export function raw(sql, params) {
193
170
  * ```
194
171
  */
195
172
  export function sql(sql, ...values) {
196
- return raw(sql.reduce((query, queryPart, i) => {
197
- const valueExists = i < values.length;
198
- const text = query + queryPart;
199
- return valueExists ? text + '?' : text;
200
- }, ''), values);
173
+ return raw(sql.join('?'), values);
201
174
  }
202
175
  export function createSqlFunction(func, key) {
203
176
  if (typeof key === 'string') {
@@ -161,7 +161,7 @@ export class TransactionManager {
161
161
  const wrapped = helper(entity);
162
162
  const meta = wrapped.__meta;
163
163
  // eslint-disable-next-line dot-notation
164
- const parentEntity = parentUoW.getById(meta.className, wrapped.getPrimaryKey(), parent['_schema'], true);
164
+ const parentEntity = parentUoW.getById(meta.class, wrapped.getPrimaryKey(), parent['_schema'], true);
165
165
  if (parentEntity && parentEntity !== entity) {
166
166
  const parentWrapped = helper(parentEntity);
167
167
  parentWrapped.__data = wrapped.__data;