@mikro-orm/core 6.5.10-dev.8 → 6.6.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.
- package/EntityManager.d.ts +12 -9
- package/EntityManager.js +71 -47
- package/decorators/Property.d.ts +53 -3
- package/entity/Collection.js +2 -0
- package/entity/EntityFactory.d.ts +1 -0
- package/entity/EntityFactory.js +6 -3
- package/entity/EntityHelper.js +17 -2
- package/entity/EntityLoader.d.ts +3 -3
- package/entity/EntityLoader.js +20 -2
- package/entity/Reference.d.ts +1 -0
- package/entity/Reference.js +6 -2
- package/entity/defineEntity.d.ts +10 -6
- package/entity/defineEntity.js +9 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +25 -22
- package/index.d.ts +1 -1
- package/metadata/MetadataDiscovery.d.ts +1 -0
- package/metadata/MetadataDiscovery.js +34 -4
- package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
- package/naming-strategy/AbstractNamingStrategy.js +7 -1
- package/naming-strategy/NamingStrategy.d.ts +11 -1
- package/package.json +2 -2
- package/serialization/EntitySerializer.js +1 -1
- package/serialization/EntityTransformer.js +1 -1
- package/typings.d.ts +11 -4
- package/utils/Configuration.d.ts +4 -0
- package/utils/Configuration.js +9 -0
- package/utils/EntityComparator.js +11 -1
- package/utils/QueryHelper.d.ts +3 -1
- package/utils/QueryHelper.js +18 -0
- package/utils/RawQueryFragment.d.ts +2 -2
- package/utils/Utils.js +2 -2
package/EntityManager.d.ts
CHANGED
|
@@ -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>],
|
|
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>],
|
|
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>],
|
|
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>[],
|
|
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>,
|
|
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
|
|
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:
|
|
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,
|
|
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,
|
|
201
|
-
|
|
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,
|
|
280
|
+
async getJoinedFilters(meta, options) {
|
|
281
|
+
if (!this.config.get('filtersOnRelations') || !options.populate) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
281
284
|
const ret = {};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
|
321
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
}
|
package/decorators/Property.d.ts
CHANGED
|
@@ -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
|
package/entity/Collection.js
CHANGED
|
@@ -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);
|
package/entity/EntityFactory.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/entity/EntityHelper.js
CHANGED
|
@@ -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 = {
|
|
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
|
}
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AnyEntity, ConnectionType,
|
|
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?:
|
|
17
|
+
filters?: FilterOptions;
|
|
18
18
|
strategy?: LoadStrategy;
|
|
19
19
|
lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
|
|
20
20
|
schema?: string;
|
package/entity/EntityLoader.js
CHANGED
|
@@ -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;
|
package/entity/Reference.d.ts
CHANGED
|
@@ -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?: {
|
package/entity/Reference.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -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 {
|
|
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 ?
|
|
518
|
+
} ? Value extends object ? Ref<Value> : never : Options extends {
|
|
515
519
|
ref: true;
|
|
516
520
|
kind: 'm:1';
|
|
517
|
-
} ? Value extends object ?
|
|
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';
|