@mikro-orm/core 6.5.10-dev.9 → 6.6.1-dev.0

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.
@@ -3,8 +3,8 @@ import DataLoader from 'dataloader';
3
3
  import { type Configuration, Cursor } from './utils';
4
4
  import { type AssignOptions, EntityFactory, EntityLoader, type EntityLoaderOptions, type EntityRepository, EntityValidator, Reference } from './entity';
5
5
  import { UnitOfWork } from './unit-of-work';
6
- import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers';
7
- import type { AnyEntity, AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityData, EntityDictionary, EntityDTO, EntityMetadata, EntityName, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MaybePromise, MergeLoaded, MergeSelected, NoInfer, ObjectQuery, Primary, Ref, RequiredEntityData, UnboxArray } from './typings';
6
+ import type { CountOptions, DeleteOptions, FilterOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers';
7
+ import type { AnyEntity, AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityData, EntityDictionary, EntityDTO, EntityMetadata, EntityName, FilterDef, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MaybePromise, MergeLoaded, MergeSelected, NoInfer, ObjectQuery, Primary, Ref, RequiredEntityData, UnboxArray } from './typings';
8
8
  import { FlushMode, LockMode, PopulatePath, type TransactionOptions } from './enums';
9
9
  import type { MetadataStorage } from './metadata';
10
10
  import type { Transaction } from './connections';
@@ -83,19 +83,19 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
83
83
  /**
84
84
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
85
85
  */
86
- addFilter<T1>(name: string, cond: FilterQuery<T1> | ((args: Dictionary) => MaybePromise<FilterQuery<T1>>), entityName?: EntityName<T1> | [EntityName<T1>], enabled?: boolean): void;
86
+ addFilter<T1>(name: string, cond: FilterQuery<T1> | ((args: Dictionary) => MaybePromise<FilterQuery<T1>>), entityName?: EntityName<T1> | [EntityName<T1>], options?: boolean | Partial<FilterDef>): void;
87
87
  /**
88
88
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
89
89
  */
90
- addFilter<T1, T2>(name: string, cond: FilterQuery<T1 | T2> | ((args: Dictionary) => MaybePromise<FilterQuery<T1 | T2>>), entityName?: [EntityName<T1>, EntityName<T2>], enabled?: boolean): void;
90
+ addFilter<T1, T2>(name: string, cond: FilterQuery<T1 | T2> | ((args: Dictionary) => MaybePromise<FilterQuery<T1 | T2>>), entityName?: [EntityName<T1>, EntityName<T2>], options?: boolean | Partial<FilterDef>): void;
91
91
  /**
92
92
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
93
93
  */
94
- addFilter<T1, T2, T3>(name: string, cond: FilterQuery<T1 | T2 | T3> | ((args: Dictionary) => MaybePromise<FilterQuery<T1 | T2 | T3>>), entityName?: [EntityName<T1>, EntityName<T2>, EntityName<T3>], enabled?: boolean): void;
94
+ addFilter<T1, T2, T3>(name: string, cond: FilterQuery<T1 | T2 | T3> | ((args: Dictionary) => MaybePromise<FilterQuery<T1 | T2 | T3>>), entityName?: [EntityName<T1>, EntityName<T2>, EntityName<T3>], options?: boolean | Partial<FilterDef>): void;
95
95
  /**
96
96
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
97
97
  */
98
- addFilter(name: string, cond: Dictionary | ((args: Dictionary) => MaybePromise<FilterQuery<AnyEntity>>), entityName?: EntityName<AnyEntity> | EntityName<AnyEntity>[], enabled?: boolean): void;
98
+ addFilter(name: string, cond: Dictionary | ((args: Dictionary) => MaybePromise<FilterQuery<AnyEntity>>), entityName?: EntityName<AnyEntity> | EntityName<AnyEntity>[], options?: boolean | Partial<FilterDef>): void;
99
99
  /**
100
100
  * Sets filter parameter values globally inside context defined by this entity manager.
101
101
  * If you want to set shared value for all contexts, be sure to use the root entity manager.
@@ -119,15 +119,18 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
119
119
  protected processWhere<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: string, where: FilterQuery<Entity>, options: FindOptions<Entity, Hint, Fields, Excludes> | FindOneOptions<Entity, Hint, Fields, Excludes>, type: 'read' | 'update' | 'delete'): Promise<FilterQuery<Entity>>;
120
120
  protected applyDiscriminatorCondition<Entity extends object>(entityName: string, where: FilterQuery<Entity>): FilterQuery<Entity>;
121
121
  protected createPopulateWhere<Entity extends object>(cond: ObjectQuery<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any> | CountOptions<Entity, any>): ObjectQuery<Entity>;
122
- protected getJoinedFilters<Entity extends object>(meta: EntityMetadata<Entity>, cond: ObjectQuery<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any>): Promise<ObjectQuery<Entity>>;
122
+ protected getJoinedFilters<Entity extends object>(meta: EntityMetadata<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any>): Promise<ObjectQuery<Entity> | undefined>;
123
123
  /**
124
124
  * When filters are active on M:1 or 1:1 relations, we need to ref join them eagerly as they might affect the FK value.
125
125
  */
126
- protected autoJoinRefsForFilters<T extends object>(meta: EntityMetadata<T>, options: FindOptions<T, any, any, any> | FindOneOptions<T, any, any, any>): Promise<void>;
126
+ protected autoJoinRefsForFilters<T extends object>(meta: EntityMetadata<T>, options: FindOptions<T, any, any, any> | FindOneOptions<T, any, any, any>, parent?: {
127
+ className: string;
128
+ propName: string;
129
+ }): Promise<void>;
127
130
  /**
128
131
  * @internal
129
132
  */
130
- applyFilters<Entity extends object>(entityName: string, where: FilterQuery<Entity> | undefined, options: Dictionary<boolean | Dictionary> | string[] | boolean, type: 'read' | 'update' | 'delete', findOptions?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>): Promise<FilterQuery<Entity> | undefined>;
133
+ applyFilters<Entity extends object>(entityName: string, where: FilterQuery<Entity> | undefined, options: FilterOptions | undefined, type: 'read' | 'update' | 'delete', findOptions?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>): Promise<FilterQuery<Entity> | undefined>;
131
134
  /**
132
135
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
133
136
  * where the first element is the array of entities, and the second is the count.
package/EntityManager.js CHANGED
@@ -143,7 +143,7 @@ class EntityManager {
143
143
  // save the original hint value so we know it was infer/all
144
144
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
145
145
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
146
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
146
+ options.populateFilter = await this.getJoinedFilters(meta, options);
147
147
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
148
148
  if (results.length === 0) {
149
149
  await em.storeCache(options.cache, cached, []);
@@ -197,8 +197,8 @@ class EntityManager {
197
197
  /**
198
198
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
199
199
  */
200
- addFilter(name, cond, entityName, enabled = true) {
201
- const options = { name, cond, default: enabled };
200
+ addFilter(name, cond, entityName, options = true) {
201
+ options = typeof options === 'object' ? { name, cond, default: true, ...options } : { name, cond, default: options };
202
202
  if (entityName) {
203
203
  options.entity = utils_1.Utils.asArray(entityName).map(n => utils_1.Utils.className(n));
204
204
  }
@@ -277,29 +277,39 @@ class EntityManager {
277
277
  }
278
278
  return ret;
279
279
  }
280
- async getJoinedFilters(meta, cond, options) {
280
+ async getJoinedFilters(meta, options) {
281
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
282
+ return undefined;
283
+ }
281
284
  const ret = {};
282
- if (options.populate) {
283
- for (const hint of options.populate) {
284
- const field = hint.field.split(':')[0];
285
- const prop = meta.properties[field];
286
- const strategy = (0, utils_2.getLoadingStrategy)(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
287
- const joined = strategy === enums_1.LoadStrategy.JOINED && prop.kind !== enums_1.ReferenceKind.SCALAR;
288
- if (!joined && !hint.filter) {
289
- continue;
290
- }
291
- const where = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', { ...options, populate: hint.children });
292
- const where2 = await this.getJoinedFilters(prop.targetMeta, {}, { ...options, populate: hint.children, populateWhere: enums_1.PopulateHint.ALL });
293
- if (utils_1.Utils.hasObjectKeys(where)) {
294
- ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
285
+ for (const hint of options.populate) {
286
+ const field = hint.field.split(':')[0];
287
+ const prop = meta.properties[field];
288
+ const strategy = (0, utils_2.getLoadingStrategy)(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
289
+ const joined = strategy === enums_1.LoadStrategy.JOINED && prop.kind !== enums_1.ReferenceKind.SCALAR;
290
+ if (!joined && !hint.filter) {
291
+ continue;
292
+ }
293
+ const filters = utils_1.QueryHelper.mergePropertyFilters(prop.filters, options.filters);
294
+ const where = await this.applyFilters(prop.type, {}, filters, 'read', {
295
+ ...options,
296
+ populate: hint.children,
297
+ });
298
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
299
+ ...options,
300
+ filters,
301
+ populate: hint.children,
302
+ populateWhere: enums_1.PopulateHint.ALL,
303
+ });
304
+ if (utils_1.Utils.hasObjectKeys(where)) {
305
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
306
+ }
307
+ if (where2 && utils_1.Utils.hasObjectKeys(where2)) {
308
+ if (ret[field]) {
309
+ utils_1.Utils.merge(ret[field], where2);
295
310
  }
296
- if (utils_1.Utils.hasObjectKeys(where2)) {
297
- if (ret[field]) {
298
- utils_1.Utils.merge(ret[field], where2);
299
- }
300
- else {
301
- ret[field] = where2;
302
- }
311
+ else {
312
+ ret[field] = where2;
303
313
  }
304
314
  }
305
315
  }
@@ -308,29 +318,30 @@ class EntityManager {
308
318
  /**
309
319
  * When filters are active on M:1 or 1:1 relations, we need to ref join them eagerly as they might affect the FK value.
310
320
  */
311
- async autoJoinRefsForFilters(meta, options) {
312
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
321
+ async autoJoinRefsForFilters(meta, options, parent) {
322
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
313
323
  return;
314
324
  }
315
- const props = meta.relations.filter(prop => {
316
- return !prop.object && [enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
317
- && ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
318
- });
319
325
  const ret = options.populate;
320
- for (const prop of props) {
321
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
326
+ for (const prop of meta.relations) {
327
+ if (prop.object
328
+ || ![enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
329
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
330
+ || (parent?.className === prop.targetMeta.root.className && parent.propName === prop.inversedBy)) {
331
+ continue;
332
+ }
333
+ options = { ...options, filters: utils_1.QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
334
+ const cond = await this.applyFilters(prop.type, {}, options.filters, 'read', options);
322
335
  if (!utils_1.Utils.isEmpty(cond)) {
323
336
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
324
337
  let found = false;
325
- if (populated.length > 0) {
326
- for (const hint of populated) {
327
- if (!hint.all) {
328
- hint.filter = true;
329
- found = true;
330
- }
331
- else if (hint.field === `${prop.name}:ref`) {
332
- found = true;
333
- }
338
+ for (const hint of populated) {
339
+ if (!hint.all) {
340
+ hint.filter = true;
341
+ }
342
+ const strategy = (0, utils_2.getLoadingStrategy)(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
343
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === enums_1.LoadStrategy.JOINED)) {
344
+ found = true;
334
345
  }
335
346
  }
336
347
  if (!found) {
@@ -338,6 +349,14 @@ class EntityManager {
338
349
  }
339
350
  }
340
351
  }
352
+ for (const hint of ret) {
353
+ const [field, ref] = hint.field.split(':');
354
+ const prop = meta?.properties[field];
355
+ if (prop && !ref) {
356
+ hint.children ??= [];
357
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { className: meta.root.className, propName: prop.name });
358
+ }
359
+ }
341
360
  }
342
361
  /**
343
362
  * @internal
@@ -367,7 +386,7 @@ class EntityManager {
367
386
  let cond;
368
387
  if (filter.cond instanceof Function) {
369
388
  // @ts-ignore
370
- const args = utils_1.Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
389
+ const args = utils_1.Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
371
390
  if (!args && filter.cond.length > 0 && filter.args !== false) {
372
391
  throw new Error(`No arguments provided for filter '${filter.name}'`);
373
392
  }
@@ -376,13 +395,17 @@ class EntityManager {
376
395
  else {
377
396
  cond = filter.cond;
378
397
  }
379
- ret.push(utils_1.QueryHelper.processWhere({
398
+ cond = utils_1.QueryHelper.processWhere({
380
399
  where: cond,
381
400
  entityName,
382
401
  metadata: this.metadata,
383
402
  platform: this.driver.getPlatform(),
384
403
  aliased: type === 'read',
385
- }));
404
+ });
405
+ if (filter.strict) {
406
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
407
+ }
408
+ ret.push(cond);
386
409
  }
387
410
  const conds = [...ret, where].filter(c => utils_1.Utils.hasObjectKeys(c));
388
411
  return conds.length > 1 ? { $and: conds } : conds[0];
@@ -568,7 +591,7 @@ class EntityManager {
568
591
  // save the original hint value so we know it was infer/all
569
592
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
570
593
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
571
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
594
+ options.populateFilter = await this.getJoinedFilters(meta, options);
572
595
  const data = await em.driver.findOne(entityName, where, {
573
596
  ctx: em.transactionContext,
574
597
  em,
@@ -1204,6 +1227,7 @@ class EntityManager {
1204
1227
  ...options,
1205
1228
  newEntity: !options.managed,
1206
1229
  merge: options.managed,
1230
+ normalizeAccessors: true,
1207
1231
  });
1208
1232
  options.persist ??= em.config.get('persistOnCreate');
1209
1233
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1253,7 +1277,7 @@ class EntityManager {
1253
1277
  const meta = em.metadata.find(entityName);
1254
1278
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1255
1279
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1256
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1280
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1257
1281
  em.validator.validateParams(where);
1258
1282
  delete options.orderBy;
1259
1283
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
@@ -1387,7 +1411,7 @@ class EntityManager {
1387
1411
  const em = this.getContext();
1388
1412
  em.prepareOptions(options);
1389
1413
  const entityName = arr[0].constructor.name;
1390
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1414
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1391
1415
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1392
1416
  return entities;
1393
1417
  }
@@ -3,6 +3,7 @@ import type { EntityName, Constructor, CheckCallback, GeneratedColumnCallback, A
3
3
  import type { Type, types } from '../types';
4
4
  import type { EntityManager } from '../EntityManager';
5
5
  import type { SerializeOptions } from '../serialization/EntitySerializer';
6
+ import type { FilterOptions } from '../drivers/IDatabaseDriver';
6
7
  export declare function Property<T extends object>(options?: PropertyOptions<T>): (target: any, propertyName: string) => any;
7
8
  export interface PropertyOptions<Owner> {
8
9
  /**
@@ -161,7 +162,7 @@ export interface PropertyOptions<Owner> {
161
162
  * Set true to define the properties as setter. (virtual)
162
163
  *
163
164
  * @example
164
- * ```
165
+ * ```ts
165
166
  * @Property({ setter: true })
166
167
  * set address(value: string) {
167
168
  * this._address = value.toLocaleLowerCase();
@@ -173,7 +174,7 @@ export interface PropertyOptions<Owner> {
173
174
  * Set true to define the properties as getter. (virtual)
174
175
  *
175
176
  * @example
176
- * ```
177
+ * ```ts
177
178
  * @Property({ getter: true })
178
179
  * get fullName() {
179
180
  * return this.firstName + this.lastName;
@@ -186,7 +187,7 @@ export interface PropertyOptions<Owner> {
186
187
  * to the method name.
187
188
  *
188
189
  * @example
189
- * ```
190
+ * ```ts
190
191
  * @Property({ getter: true })
191
192
  * getFullName() {
192
193
  * return this.firstName + this.lastName;
@@ -194,6 +195,53 @@ export interface PropertyOptions<Owner> {
194
195
  * ```
195
196
  */
196
197
  getterName?: keyof Owner;
198
+ /**
199
+ * When using a private property backed by a public get/set pair, use the `accessor` option to point to the other side.
200
+ *
201
+ * > The `fieldName` will be inferred based on the accessor name unless specified explicitly.
202
+ *
203
+ * If the `accessor` option points to something, the ORM will use the backing property directly.
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * @Entity()
208
+ * export class User {
209
+ * // the ORM will use the backing field directly
210
+ * @Property({ accessor: 'email' })
211
+ * private _email: string;
212
+ *
213
+ * get email() {
214
+ * return this._email;
215
+ * }
216
+ *
217
+ * set email() {
218
+ * return this._email;
219
+ * }
220
+ * }
221
+ *```
222
+ *
223
+ * If you want to the ORM to use your accessor internally too, use `accessor: true` on the get/set property instead.
224
+ * This is handy if you want to use a native private property for the backing field.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * @Entity({ forceConstructor: true })
229
+ * export class User {
230
+ * #email: string;
231
+ *
232
+ * // the ORM will use the accessor internally
233
+ * @Property({ accessor: true })
234
+ * get email() {
235
+ * return this.#email;
236
+ * }
237
+ *
238
+ * set email() {
239
+ * return this.#email;
240
+ * }
241
+ * }
242
+ * ```
243
+ */
244
+ accessor?: keyof Owner | boolean;
197
245
  /**
198
246
  * Set to define serialized primary key for MongoDB. (virtual)
199
247
  * Alias for `@SerializedPrimaryKey()` decorator.
@@ -242,6 +290,8 @@ export interface ReferenceOptions<Owner, Target> extends PropertyOptions<Owner>
242
290
  eager?: boolean;
243
291
  /** Override the default loading strategy for this property. This option has precedence over the global `loadStrategy`, but can be overridden by `FindOptions.strategy`. */
244
292
  strategy?: LoadStrategy | `${LoadStrategy}`;
293
+ /** Control filter parameters for the relation. This will serve as a default value when processing filters on this relation. It's value can be overridden via `em.fork()` or `FindOptions`. */
294
+ filters?: FilterOptions;
245
295
  }
246
296
  /**
247
297
  * Inspired by https://github.com/typeorm/typeorm/blob/941b584ba135617e55d6685caef671172ec1dc03/src/driver/types/ColumnTypes.ts
@@ -35,6 +35,7 @@ class Collection extends ArrayCollection_1.ArrayCollection {
35
35
  async load(options = {}) {
36
36
  if (this.isInitialized(true) && !options.refresh) {
37
37
  const em = this.getEntityManager(this.items, false);
38
+ options = { ...options, filters: utils_1.QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
38
39
  await em?.populate(this.items, options.populate, options);
39
40
  this.setSerializationContext(options);
40
41
  }
@@ -220,6 +221,7 @@ class Collection extends ArrayCollection_1.ArrayCollection {
220
221
  return this;
221
222
  }
222
223
  const em = this.getEntityManager();
224
+ options = { ...options, filters: utils_1.QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
223
225
  if (options.dataloader ?? [enums_1.DataloaderType.ALL, enums_1.DataloaderType.COLLECTION].includes(utils_1.DataloaderUtils.getDataloaderType(em.config.get('dataloader')))) {
224
226
  const order = [...this.items]; // copy order of references
225
227
  const orderBy = this.createOrderBy(options.orderBy);
@@ -15,6 +15,7 @@ export interface FactoryOptions {
15
15
  recomputeSnapshot?: boolean;
16
16
  schema?: string;
17
17
  parentSchema?: string;
18
+ normalizeAccessors?: boolean;
18
19
  }
19
20
  export declare class EntityFactory {
20
21
  private readonly em;
@@ -7,6 +7,7 @@ const enums_1 = require("../enums");
7
7
  const Reference_1 = require("./Reference");
8
8
  const wrap_1 = require("./wrap");
9
9
  const EntityHelper_1 = require("./EntityHelper");
10
+ const JsonType_1 = require("../types/JsonType");
10
11
  class EntityFactory {
11
12
  em;
12
13
  driver;
@@ -76,7 +77,9 @@ class EntityFactory {
76
77
  if ([enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils_1.Utils.isPlainObject(data[prop.name])) {
77
78
  data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
78
79
  }
79
- data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
80
+ if (prop.customType instanceof JsonType_1.JsonType && this.platform.convertsJsonAutomatically()) {
81
+ data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
82
+ }
80
83
  }
81
84
  }
82
85
  }
@@ -248,10 +251,10 @@ class EntityFactory {
248
251
  }
249
252
  hydrate(entity, meta, data, options) {
250
253
  if (options.initialized) {
251
- this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
254
+ this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options), options.normalizeAccessors);
252
255
  }
253
256
  else {
254
- this.hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
257
+ this.hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options), options.normalizeAccessors);
255
258
  }
256
259
  Utils_1.Utils.keys(data).forEach(key => {
257
260
  (0, wrap_1.helper)(entity)?.__loadedProperties.add(key);
@@ -90,7 +90,7 @@ class EntityHelper {
90
90
  });
91
91
  return;
92
92
  }
93
- if (prop.inherited || prop.primary || prop.persist === false || prop.trackChanges === false || prop.embedded || isCollection) {
93
+ if (prop.inherited || prop.primary || prop.accessor || prop.persist === false || prop.trackChanges === false || prop.embedded || isCollection) {
94
94
  return;
95
95
  }
96
96
  Object.defineProperty(meta.prototype, prop.name, {
@@ -116,7 +116,18 @@ class EntityHelper {
116
116
  static defineCustomInspect(meta) {
117
117
  // @ts-ignore
118
118
  meta.prototype[node_util_1.inspect.custom] ??= function (depth = 2) {
119
- const object = { ...this };
119
+ const object = {};
120
+ const keys = new Set(Utils_1.Utils.keys(this)); // .sort((a, b) => (meta.propertyOrder.get(a) ?? 0) - (meta.propertyOrder.get(b) ?? 0));
121
+ for (const prop of meta.props) {
122
+ if (keys.has(prop.name) || (prop.getter && prop.accessor === prop.name)) {
123
+ object[prop.name] = this[prop.name];
124
+ }
125
+ }
126
+ for (const key of keys) {
127
+ if (!meta.properties[key]) {
128
+ object[key] = this[key];
129
+ }
130
+ }
120
131
  // ensure we dont have internal symbols in the POJO
121
132
  [typings_1.OptionalProps, typings_1.EntityRepositoryType, typings_1.PrimaryKeyProp, typings_1.EagerProps, typings_1.HiddenProps].forEach(sym => delete object[sym]);
122
133
  meta.props
@@ -175,6 +186,10 @@ class EntityHelper {
175
186
  continue;
176
187
  }
177
188
  const inverse = value?.[prop2.name];
189
+ if (prop.ref && owner[prop.name]) {
190
+ // eslint-disable-next-line dot-notation
191
+ owner[prop.name]['property'] = prop;
192
+ }
178
193
  if (Utils_1.Utils.isCollection(inverse) && inverse.isPartial()) {
179
194
  continue;
180
195
  }
@@ -1,7 +1,7 @@
1
- import type { AnyEntity, ConnectionType, Dictionary, EntityProperty, FilterQuery, PopulateOptions } from '../typings';
1
+ import type { AnyEntity, ConnectionType, EntityProperty, FilterQuery, PopulateOptions } from '../typings';
2
2
  import type { EntityManager } from '../EntityManager';
3
3
  import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums';
4
- import type { EntityField } from '../drivers/IDatabaseDriver';
4
+ import type { EntityField, FilterOptions } from '../drivers/IDatabaseDriver';
5
5
  import type { LoggingOptions } from '../logging/Logger';
6
6
  export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL, Excludes extends string = never> = {
7
7
  where?: FilterQuery<Entity>;
@@ -14,7 +14,7 @@ export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL
14
14
  lookup?: boolean;
15
15
  convertCustomTypes?: boolean;
16
16
  ignoreLazyScalarProperties?: boolean;
17
- filters?: Dictionary<boolean | Dictionary> | string[] | boolean;
17
+ filters?: FilterOptions;
18
18
  strategy?: LoadStrategy;
19
19
  lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
20
20
  schema?: string;
@@ -35,7 +35,6 @@ class EntityLoader {
35
35
  const visited = options.visited ??= new Set();
36
36
  options.where ??= {};
37
37
  options.orderBy ??= {};
38
- options.filters ??= {};
39
38
  options.lookup ??= true;
40
39
  options.validate ??= true;
41
40
  options.refresh ??= false;
@@ -213,7 +212,7 @@ class EntityLoader {
213
212
  }
214
213
  }
215
214
  async findChildren(entities, prop, populate, options, ref) {
216
- const children = this.getChildReferences(entities, prop, options, ref);
215
+ const children = Utils_1.Utils.unique(this.getChildReferences(entities, prop, options, ref));
217
216
  const meta = prop.targetMeta;
218
217
  let fk = Utils_1.Utils.getPrimaryKeyHash(meta.primaryKeys);
219
218
  let schema = options.schema;
@@ -269,6 +268,24 @@ class EntityLoader {
269
268
  // @ts-ignore not a public option, will be propagated to the populate call
270
269
  visited: options.visited,
271
270
  });
271
+ if ([enums_1.ReferenceKind.ONE_TO_ONE, enums_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
272
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
273
+ const itemsMap = new Set();
274
+ const childrenMap = new Set();
275
+ for (const item of items) {
276
+ itemsMap.add((0, wrap_1.helper)(item).getSerializedPrimaryKey());
277
+ }
278
+ for (const child of children) {
279
+ childrenMap.add((0, wrap_1.helper)(child).getSerializedPrimaryKey());
280
+ }
281
+ for (const entity of entities) {
282
+ const key = (0, wrap_1.helper)(entity[prop.name] ?? {})?.getSerializedPrimaryKey();
283
+ if (childrenMap.has(key) && !itemsMap.has(key)) {
284
+ entity[prop.name] = nullVal;
285
+ (0, wrap_1.helper)(entity).__originalEntityData[prop.name] = null;
286
+ }
287
+ }
288
+ }
272
289
  for (const item of items) {
273
290
  if (ref && !(0, wrap_1.helper)(item).__onLoadFired) {
274
291
  (0, wrap_1.helper)(item).__initialized = false;
@@ -292,6 +309,7 @@ class EntityLoader {
292
309
  if (prop.kind === enums_1.ReferenceKind.SCALAR && !prop.lazy) {
293
310
  return;
294
311
  }
312
+ options = { ...options, filters: QueryHelper_1.QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
295
313
  const populated = await this.populateMany(entityName, entities, populate, options);
296
314
  if (!populate.children && !populate.all) {
297
315
  return;
@@ -3,6 +3,7 @@ import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityP
3
3
  import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver';
4
4
  export declare class Reference<T extends object> {
5
5
  private entity;
6
+ private property?;
6
7
  constructor(entity: T);
7
8
  static create<T extends object>(entity: T | Ref<T>): Ref<T>;
8
9
  static createFromPK<T extends object>(entityType: EntityClass<T>, pk: Primary<T>, options?: {
@@ -10,6 +10,7 @@ const utils_1 = require("../utils");
10
10
  const errors_1 = require("../errors");
11
11
  class Reference {
12
12
  entity;
13
+ property;
13
14
  constructor(entity) {
14
15
  this.entity = entity;
15
16
  this.set(entity);
@@ -69,7 +70,9 @@ class Reference {
69
70
  */
70
71
  static wrapReference(entity, prop) {
71
72
  if (entity && prop.ref && !Reference.isReference(entity)) {
72
- return Reference.create(entity);
73
+ const ref = Reference.create(entity);
74
+ ref.property = prop;
75
+ return ref;
73
76
  }
74
77
  return entity;
75
78
  }
@@ -89,6 +92,7 @@ class Reference {
89
92
  if (!wrapped.__em) {
90
93
  return this.entity;
91
94
  }
95
+ options = { ...options, filters: utils_1.QueryHelper.mergePropertyFilters(this.property?.filters, options.filters) };
92
96
  if (this.isInitialized() && !options.refresh && options.populate) {
93
97
  await wrapped.__em.populate(this.entity, options.populate, options);
94
98
  }
@@ -149,7 +153,7 @@ class Reference {
149
153
  /** @ignore */
150
154
  [node_util_1.inspect.custom](depth = 2) {
151
155
  const object = { ...this };
152
- const hidden = ['meta'];
156
+ const hidden = ['meta', 'property'];
153
157
  hidden.forEach(k => delete object[k]);
154
158
  const ret = (0, node_util_1.inspect)(object, { depth });
155
159
  const wrapped = (0, wrap_1.helper)(this.entity);
@@ -6,8 +6,8 @@ import type { ManyToOneOptions } from '../decorators/ManyToOne';
6
6
  import type { OneToManyOptions } from '../decorators/OneToMany';
7
7
  import type { OneToOneOptions } from '../decorators/OneToOne';
8
8
  import type { ManyToManyOptions } from '../decorators/ManyToMany';
9
- import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, Hidden, Opt, Primary, EntityClass } from '../typings';
10
- import type { Reference, ScalarReference } from './Reference';
9
+ import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, Hidden, Opt, Primary, EntityClass, Ref } from '../typings';
10
+ import type { ScalarReference } from './Reference';
11
11
  import type { SerializeOptions } from '../serialization/EntitySerializer';
12
12
  import type { Cascade, DeferMode, EventType, LoadStrategy, QueryOrderMap } from '../enums';
13
13
  import type { IType, Type } from '../types/Type';
@@ -15,6 +15,7 @@ import { types } from '../types';
15
15
  import { EntitySchema } from '../metadata/EntitySchema';
16
16
  import type { Collection } from './Collection';
17
17
  import type { EventSubscriber } from '../events';
18
+ import type { FilterOptions } from '../drivers/IDatabaseDriver';
18
19
  export type UniversalPropertyKeys = keyof PropertyOptions<any> | keyof EnumOptions<any> | keyof EmbeddedOptions<any, any> | keyof ReferenceOptions<any, any> | keyof ManyToOneOptions<any, any> | keyof OneToManyOptions<any, any> | keyof OneToOneOptions<any, any> | keyof ManyToManyOptions<any, any>;
19
20
  type BuilderExtraKeys = '~options' | '~type' | '$type';
20
21
  type ExcludeKeys = 'entity' | 'items';
@@ -110,14 +111,12 @@ export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys
110
111
  returning(returning?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
111
112
  /**
112
113
  * Automatically set the property value when entity gets created, executed during flush operation.
113
- * @param entity
114
114
  */
115
115
  onCreate(onCreate: (entity: any, em: EntityManager) => Value): Pick<UniversalPropertyOptionsBuilder<Value, Options & {
116
116
  onCreate: (...args: any[]) => any;
117
117
  }, IncludeKeys>, IncludeKeys>;
118
118
  /**
119
119
  * Automatically update the property value every time entity gets updated, executed during flush operation.
120
- * @param entity
121
120
  */
122
121
  onUpdate(onUpdate: (entity: any, em: EntityManager) => Value): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
123
122
  /**
@@ -134,6 +133,10 @@ export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys
134
133
  defaultRaw(defaultRaw: string): Pick<UniversalPropertyOptionsBuilder<Value, Options & {
135
134
  defaultRaw: string;
136
135
  }, IncludeKeys>, IncludeKeys>;
136
+ /**
137
+ * Allow controlling `filters` option. This will be overridden with `em.fork` or `FindOptions` if provided.
138
+ */
139
+ filters(filters: FilterOptions): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
137
140
  /**
138
141
  * Set to map some SQL snippet for the entity.
139
142
  *
@@ -361,6 +364,7 @@ export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys
361
364
  foreignKeyName(foreignKeyName: string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
362
365
  /** Remove the entity when it gets disconnected from the relationship (see {@doclink cascading | Cascading}). */
363
366
  orphanRemoval(orphanRemoval?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
367
+ accessor(accessor?: string | boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
364
368
  }
365
369
  export interface EmptyOptions extends Partial<Record<UniversalPropertyKeys, unknown>> {
366
370
  }
@@ -511,10 +515,10 @@ type MaybeRelationRef<Value, Options> = Options extends {
511
515
  } ? Value : Options extends {
512
516
  ref: true;
513
517
  kind: '1:1';
514
- } ? Value extends object ? Reference<Value> : never : Options extends {
518
+ } ? Value extends object ? Ref<Value> : never : Options extends {
515
519
  ref: true;
516
520
  kind: 'm:1';
517
- } ? Value extends object ? Reference<Value> : never : Options extends {
521
+ } ? Value extends object ? Ref<Value> : never : Options extends {
518
522
  kind: '1:m';
519
523
  } ? Value extends object ? Collection<Value> : never : Options extends {
520
524
  kind: 'm:n';