@mikro-orm/core 7.0.0-dev.39 → 7.0.0-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.
Files changed (40) hide show
  1. package/EntityManager.d.ts +12 -9
  2. package/EntityManager.js +77 -52
  3. package/README.md +2 -0
  4. package/decorators/Property.d.ts +53 -3
  5. package/entity/Collection.js +3 -0
  6. package/entity/EntityFactory.d.ts +1 -0
  7. package/entity/EntityFactory.js +7 -3
  8. package/entity/EntityHelper.js +17 -2
  9. package/entity/EntityLoader.d.ts +3 -3
  10. package/entity/EntityLoader.js +20 -2
  11. package/entity/Reference.d.ts +1 -0
  12. package/entity/Reference.js +10 -4
  13. package/entity/defineEntity.d.ts +12 -8
  14. package/entity/defineEntity.js +9 -2
  15. package/hydration/ObjectHydrator.d.ts +4 -4
  16. package/hydration/ObjectHydrator.js +25 -22
  17. package/index.d.ts +1 -1
  18. package/metadata/EntitySchema.d.ts +2 -2
  19. package/metadata/MetadataDiscovery.d.ts +1 -0
  20. package/metadata/MetadataDiscovery.js +37 -3
  21. package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
  22. package/naming-strategy/AbstractNamingStrategy.js +7 -1
  23. package/naming-strategy/NamingStrategy.d.ts +11 -1
  24. package/package.json +2 -2
  25. package/platforms/Platform.js +1 -1
  26. package/serialization/EntitySerializer.js +1 -1
  27. package/serialization/EntityTransformer.js +1 -1
  28. package/serialization/SerializationContext.js +1 -1
  29. package/typings.d.ts +11 -4
  30. package/unit-of-work/ChangeSetPersister.js +16 -5
  31. package/unit-of-work/UnitOfWork.d.ts +6 -0
  32. package/unit-of-work/UnitOfWork.js +37 -23
  33. package/utils/Configuration.d.ts +4 -0
  34. package/utils/Configuration.js +10 -0
  35. package/utils/EntityComparator.js +11 -1
  36. package/utils/QueryHelper.d.ts +3 -1
  37. package/utils/QueryHelper.js +18 -0
  38. package/utils/RawQueryFragment.d.ts +2 -2
  39. package/utils/TransactionManager.js +0 -2
  40. package/utils/Utils.js +2 -2
@@ -290,11 +290,22 @@ export class ChangeSetPersister {
290
290
  async reloadVersionValues(meta, changeSets, options) {
291
291
  const reloadProps = meta.versionProperty && !this.usesReturningStatement ? [meta.properties[meta.versionProperty]] : [];
292
292
  if (changeSets[0].type === ChangeSetType.CREATE) {
293
- // do not reload things that already had a runtime value
294
- meta.props
295
- .filter(prop => prop.persist !== false && (prop.autoincrement || prop.generated || prop.defaultRaw))
296
- .filter(prop => (changeSets[0].entity[prop.name] == null && prop.defaultRaw !== 'null') || isRaw(changeSets[0].entity[prop.name]))
297
- .forEach(prop => reloadProps.push(prop));
293
+ for (const prop of meta.props) {
294
+ if (prop.persist === false) {
295
+ continue;
296
+ }
297
+ if (isRaw(changeSets[0].entity[prop.name])) {
298
+ reloadProps.push(prop);
299
+ continue;
300
+ }
301
+ // do not reload things that already had a runtime value
302
+ if (changeSets[0].entity[prop.name] != null || prop.defaultRaw === 'null') {
303
+ continue;
304
+ }
305
+ if (prop.autoincrement || prop.generated || prop.defaultRaw) {
306
+ reloadProps.push(prop);
307
+ }
308
+ }
298
309
  }
299
310
  if (changeSets[0].type === ChangeSetType.UPDATE) {
300
311
  const returning = new Set();
@@ -28,6 +28,12 @@ export declare class UnitOfWork {
28
28
  private working;
29
29
  constructor(em: EntityManager);
30
30
  merge<T extends object>(entity: T, visited?: Set<AnyEntity>): void;
31
+ /**
32
+ * Entity data can wary in its shape, e.g. we might get a deep relation graph with joined strategy, but for diffing,
33
+ * we need to normalize the shape, so relation values are only raw FKs. This method handles that.
34
+ * @internal
35
+ */
36
+ normalizeEntityData<T extends object>(meta: EntityMetadata<T>, data: EntityData<T>): void;
31
37
  /**
32
38
  * @internal
33
39
  */
@@ -65,6 +65,40 @@ export class UnitOfWork {
65
65
  }
66
66
  this.cascade(entity, Cascade.MERGE, visited ?? new Set());
67
67
  }
68
+ /**
69
+ * Entity data can wary in its shape, e.g. we might get a deep relation graph with joined strategy, but for diffing,
70
+ * we need to normalize the shape, so relation values are only raw FKs. This method handles that.
71
+ * @internal
72
+ */
73
+ normalizeEntityData(meta, data) {
74
+ const forceUndefined = this.em.config.get('forceUndefined');
75
+ for (const key of Utils.keys(data)) {
76
+ const prop = meta.properties[key];
77
+ if (!prop) {
78
+ continue;
79
+ }
80
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
81
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
82
+ }
83
+ else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
84
+ for (const p of prop.targetMeta.props) {
85
+ /* v8 ignore next */
86
+ const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
87
+ data[prefix + p.name] = data[prop.name][p.name];
88
+ }
89
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
90
+ }
91
+ if (prop.hydrate === false && prop.customType?.ensureComparable(meta, prop)) {
92
+ const converted = prop.customType.convertToJSValue(data[key], this.platform, { key, mode: 'hydration', force: true });
93
+ data[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { key, mode: 'hydration' });
94
+ }
95
+ if (forceUndefined) {
96
+ if (data[key] === null) {
97
+ data[key] = undefined;
98
+ }
99
+ }
100
+ }
101
+ }
68
102
  /**
69
103
  * @internal
70
104
  */
@@ -82,31 +116,11 @@ export class UnitOfWork {
82
116
  wrapped.__em ??= this.em;
83
117
  wrapped.__managed = true;
84
118
  if (data && (options?.refresh || !wrapped.__originalEntityData)) {
119
+ this.normalizeEntityData(wrapped.__meta, data);
85
120
  for (const key of Utils.keys(data)) {
86
121
  const prop = wrapped.__meta.properties[key];
87
- if (!prop) {
88
- continue;
89
- }
90
- wrapped.__loadedProperties.add(key);
91
- if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
92
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
93
- }
94
- else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
95
- for (const p of prop.targetMeta.props) {
96
- /* v8 ignore next */
97
- const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
98
- data[prefix + p.name] = data[prop.name][p.name];
99
- }
100
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
101
- }
102
- if (prop.hydrate === false && prop.customType?.ensureComparable(wrapped.__meta, prop)) {
103
- const converted = prop.customType.convertToJSValue(data[key], this.platform, { key, mode: 'hydration', force: true });
104
- data[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { key, mode: 'hydration' });
105
- }
106
- if (forceUndefined) {
107
- if (data[key] === null) {
108
- data[key] = undefined;
109
- }
122
+ if (prop) {
123
+ wrapped.__loadedProperties.add(key);
110
124
  }
111
125
  }
112
126
  wrapped.__originalEntityData = data;
@@ -62,6 +62,7 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
62
62
  onQuery: (sql: string) => string;
63
63
  autoJoinOneToOneOwner: true;
64
64
  autoJoinRefsForFilters: true;
65
+ filtersOnRelations: true;
65
66
  propagationOnPrototype: true;
66
67
  populateAfterFlush: true;
67
68
  serialization: {
@@ -118,6 +119,8 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
118
119
  identifiedReferences: false;
119
120
  scalarTypeInDecorator: false;
120
121
  scalarPropertiesForRelations: "never";
122
+ entityDefinition: "decorators";
123
+ enumMode: "ts-enum";
121
124
  fileName: (className: string) => string;
122
125
  onlyPurePivotTables: false;
123
126
  outputPurePivotTables: false;
@@ -324,6 +327,7 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver, EM
324
327
  onQuery: (sql: string, params: readonly unknown[]) => string;
325
328
  autoJoinOneToOneOwner: boolean;
326
329
  autoJoinRefsForFilters: boolean;
330
+ filtersOnRelations: boolean;
327
331
  propagationOnPrototype: boolean;
328
332
  populateAfterFlush: boolean;
329
333
  serialization: {
@@ -52,6 +52,7 @@ export class Configuration {
52
52
  onQuery: sql => sql,
53
53
  autoJoinOneToOneOwner: true,
54
54
  autoJoinRefsForFilters: true,
55
+ filtersOnRelations: true,
55
56
  propagationOnPrototype: true,
56
57
  populateAfterFlush: true,
57
58
  serialization: {
@@ -108,6 +109,8 @@ export class Configuration {
108
109
  identifiedReferences: false,
109
110
  scalarTypeInDecorator: false,
110
111
  scalarPropertiesForRelations: 'never',
112
+ entityDefinition: 'decorators',
113
+ enumMode: 'ts-enum',
111
114
  fileName: (className) => className,
112
115
  onlyPurePivotTables: false,
113
116
  outputPurePivotTables: false,
@@ -342,6 +345,9 @@ export class Configuration {
342
345
  Object.keys(this.options.filters).forEach(key => {
343
346
  this.options.filters[key].default ??= true;
344
347
  });
348
+ if (!this.options.filtersOnRelations) {
349
+ this.options.autoJoinRefsForFilters ??= false;
350
+ }
345
351
  this.options.subscribers = Utils.unique(this.options.subscribers).map(subscriber => {
346
352
  return subscriber.constructor.name === 'Function' ? new subscriber() : subscriber;
347
353
  });
@@ -352,6 +358,10 @@ export class Configuration {
352
358
  }
353
359
  sync() {
354
360
  process.env.MIKRO_ORM_COLORS = '' + this.options.colors;
361
+ // FIXME remove `entityGenerator.entitySchema` option
362
+ if (this.options.entityGenerator.entitySchema) {
363
+ this.options.entityGenerator.entityDefinition = 'entitySchema';
364
+ }
355
365
  this.logger.setDebugMode(this.options.debug);
356
366
  }
357
367
  /**
@@ -417,11 +417,21 @@ export class EntityComparator {
417
417
  }
418
418
  getEmbeddedPropertySnapshot(meta, prop, context, level, path, dataKey, object = prop.object) {
419
419
  const padding = ' '.repeat(level * 2);
420
+ const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
420
421
  let ret = `${level === 1 ? '' : '\n'}`;
421
422
  if (object) {
422
- const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
423
423
  ret += `${padding}if (${nullCond}) ret${dataKey} = null;\n`;
424
424
  }
425
+ else {
426
+ ret += `${padding}if (${nullCond}) {\n`;
427
+ ret += meta.props.filter(p => p.embedded?.[0] === prop.name
428
+ // object for JSON embeddable
429
+ && (p.object || (p.persist !== false))).map(childProp => {
430
+ const childDataKey = meta.embeddable || prop.object ? dataKey + this.wrap(childProp.embedded[1]) : this.wrap(childProp.name);
431
+ return `${padding} ret${childDataKey} = null;`;
432
+ }).join('\n') + `\n`;
433
+ ret += `${padding}}\n`;
434
+ }
425
435
  const cond = `entity${path.map(k => this.wrap(k)).join('')} != null`;
426
436
  ret += `${padding}if (${cond}) {\n`;
427
437
  if (object) {
@@ -1,6 +1,7 @@
1
1
  import type { Dictionary, EntityMetadata, 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
+ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
4
5
  /** @internal */
5
6
  export declare class QueryHelper {
6
7
  static readonly SUPPORTED_OPERATORS: string[];
@@ -13,7 +14,8 @@ export declare class QueryHelper {
13
14
  static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
14
15
  static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
15
16
  static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
16
- static getActiveFilters(entityName: string, options: Dictionary<boolean | Dictionary> | string[] | boolean, filters: Dictionary<FilterDef>): FilterDef[];
17
+ static getActiveFilters(entityName: string, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
18
+ static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
17
19
  static isFilterActive(entityName: string, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
18
20
  static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
19
21
  private static isSupportedOperator;
@@ -202,6 +202,24 @@ export class QueryHelper {
202
202
  return filters[f];
203
203
  });
204
204
  }
205
+ static mergePropertyFilters(propFilters, options) {
206
+ if (!options || !propFilters || options === true || propFilters === true) {
207
+ return options ?? propFilters;
208
+ }
209
+ if (Array.isArray(propFilters)) {
210
+ propFilters = propFilters.reduce((o, item) => {
211
+ o[item] = true;
212
+ return o;
213
+ }, {});
214
+ }
215
+ if (Array.isArray(options)) {
216
+ options = options.reduce((o, item) => {
217
+ o[item] = true;
218
+ return o;
219
+ }, {});
220
+ }
221
+ return Utils.mergeConfig({}, propFilters, options);
222
+ }
205
223
  static isFilterActive(entityName, filterName, filter, options) {
206
224
  if (filter.entity && !filter.entity.includes(entityName)) {
207
225
  return false;
@@ -91,7 +91,7 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
91
91
  * export class Author { ... }
92
92
  * ```
93
93
  */
94
- export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
94
+ export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R>;
95
95
  /**
96
96
  * Alternative to the `raw()` helper allowing to use it as a tagged template function for the simple cases.
97
97
  *
@@ -108,7 +108,7 @@ export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> |
108
108
  */
109
109
  export declare function sql(sql: readonly string[], ...values: unknown[]): any;
110
110
  export declare namespace sql {
111
- var ref: <T extends object>(...keys: string[]) => RawQueryFragment;
111
+ var ref: <T extends object>(...keys: string[]) => NoInfer<RawQueryFragment>;
112
112
  var now: (length?: number) => string;
113
113
  var lower: <T extends object>(key: string | ((alias: string) => string)) => string;
114
114
  var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
@@ -17,9 +17,7 @@ export class TransactionManager {
17
17
  */
18
18
  async handle(cb, options = {}) {
19
19
  const em = this.em.getContext(false);
20
- // Set NESTED as the default propagation type
21
20
  options.propagation ??= TransactionPropagation.NESTED;
22
- // Set the context to the current transaction context if not already set
23
21
  options.ctx ??= em.getTransactionContext();
24
22
  const hasExistingTransaction = !!em.getTransactionContext();
25
23
  return this.executeWithPropagation(options.propagation, em, cb, options, hasExistingTransaction);
package/utils/Utils.js CHANGED
@@ -700,11 +700,11 @@ export class Utils {
700
700
  return simple;
701
701
  }
702
702
  const objectType = Object.prototype.toString.call(value);
703
- const type = objectType.match(/\[object (\w+)]/)[1];
703
+ const type = objectType.match(/^\[object (.+)]$/)[1];
704
704
  if (type === 'Uint8Array') {
705
705
  return 'Buffer';
706
706
  }
707
- return ['Date', 'Buffer', 'RegExp'].includes(type) ? type : type.toLowerCase();
707
+ return type;
708
708
  }
709
709
  /**
710
710
  * Checks whether the value is POJO (e.g. `{ foo: 'bar' }`, and not instance of `Foo`)