Skip to content

Commit

Permalink
perf(core): do not redefine Collection properties as non-enumerable
Browse files Browse the repository at this point in the history
Closes 2543
  • Loading branch information
B4nan committed Dec 18, 2021
1 parent 6dc184c commit 8a604a6
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 36 deletions.
21 changes: 15 additions & 6 deletions packages/core/src/entity/ArrayCollection.ts
@@ -1,3 +1,4 @@
import { inspect } from 'util';
import type { AnyEntity, Dictionary, EntityProperty, IPrimaryKey, Primary } from '../typings';
import { Reference } from './Reference';
import { wrap } from './wrap';
Expand All @@ -17,12 +18,6 @@ export class ArrayCollection<T, O> {
this.items = new Set(items);
this.items.forEach(item => this[i++] = item);
}

Object.defineProperty(this, 'items', { enumerable: false });
Object.defineProperty(this, 'owner', { enumerable: false, writable: true });
Object.defineProperty(this, '_property', { enumerable: false, writable: true });
Object.defineProperty(this, '_count', { enumerable: false, writable: true });
Object.defineProperty(this, '__collection', { value: true });
}

async loadCount(): Promise<number> {
Expand Down Expand Up @@ -211,8 +206,22 @@ export class ArrayCollection<T, O> {
}
}

[inspect.custom](depth: number) {
const object = { ...this };
const hidden = ['items', 'owner', '_property', '_count', 'snapshot', '_populated', '_lazyInitialized'];
hidden.forEach(k => delete object[k]);
const ret = inspect(object, { depth });
const name = `${this.constructor.name}<${this.property.type}>`;

return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
}

}

Object.defineProperties(ArrayCollection.prototype, {
__collection: { value: true, enumerable: false, writable: false },
});

export interface ArrayCollection<T, O> {
[k: number]: T;
}
6 changes: 2 additions & 4 deletions packages/core/src/entity/BaseEntity.ts
Expand Up @@ -5,10 +5,6 @@ import { EntityAssigner } from './EntityAssigner';

export abstract class BaseEntity<T, PK extends keyof T, P extends string = never> implements IWrappedEntity<T, PK, P> {

constructor() {
Object.defineProperty(this, '__baseEntity', { value: true });
}

isInitialized(): boolean {
return (this as unknown as AnyEntity<T>).__helper!.__initialized;
}
Expand Down Expand Up @@ -54,3 +50,5 @@ export abstract class BaseEntity<T, PK extends keyof T, P extends string = never
}

}

Object.defineProperty(BaseEntity.prototype, '__baseEntity', { value: true, writable: false, enumerable: false });
10 changes: 5 additions & 5 deletions packages/core/src/entity/Collection.ts
Expand Up @@ -25,11 +25,6 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {
constructor(owner: O, items?: T[], initialized = true) {
super(owner, items);
this.initialized = !!items || initialized;
Object.defineProperty(this, 'snapshot', { enumerable: false });
Object.defineProperty(this, '_populated', { enumerable: false });
Object.defineProperty(this, '_lazyInitialized', { enumerable: false });
Object.defineProperty(this, '$', { get: () => this });
Object.defineProperty(this, 'get', { value: () => this });
}

/**
Expand Down Expand Up @@ -400,6 +395,11 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {

}

Object.defineProperties(Collection.prototype, {
$: { get() { return this; } },
get: { get() { return () => this; } },
});

export interface InitOptions<T, P extends string = never> {
populate?: Populate<T, P>;
orderBy?: QueryOrderMap<T> | QueryOrderMap<T>[];
Expand Down
28 changes: 15 additions & 13 deletions packages/core/src/entity/EntityHelper.ts
Expand Up @@ -92,21 +92,23 @@ export class EntityHelper {
private static defineProperties<T extends AnyEntity<T>>(meta: EntityMetadata<T>): void {
Object
.values(meta.properties)
.filter(prop => [ReferenceType.ONE_TO_ONE, ReferenceType.MANY_TO_ONE].includes(prop.reference) && (prop.inversedBy || prop.mappedBy) && !prop.mapToPk)
.forEach(prop => {
Object.defineProperty(meta.prototype, prop.name, {
set(val: AnyEntity) {
EntityHelper.defineReferenceProperty(meta, prop, this);
this[prop.name] = val;
},
});
});
const isCollection = [ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(prop.reference);
const isReference = [ReferenceType.ONE_TO_ONE, ReferenceType.MANY_TO_ONE].includes(prop.reference) && (prop.inversedBy || prop.mappedBy) && !prop.mapToPk;

if (isReference) {
return Object.defineProperty(meta.prototype, prop.name, {
set(val: AnyEntity) {
EntityHelper.defineReferenceProperty(meta, prop, this);
this[prop.name] = val;
},
});
}

if (prop.inherited || prop.primary || prop.persist === false || prop.embedded || isCollection) {
return;
}

Object
.values(meta.properties)
.filter(prop => !(prop.inherited || prop.primary || prop.persist === false || prop.embedded))
.filter(prop => !([ReferenceType.ONE_TO_ONE, ReferenceType.MANY_TO_ONE].includes(prop.reference) && (prop.inversedBy || prop.mappedBy) && !prop.mapToPk))
.forEach(prop => {
Object.defineProperty(meta.prototype, prop.name, {
set(val) {
Object.defineProperty(this, prop.name, {
Expand Down
14 changes: 7 additions & 7 deletions tests/EntityHelper.mongo.test.ts
Expand Up @@ -209,34 +209,34 @@ describe('EntityHelperMongo', () => {
expect(actual).toBe('Author {\n' +
' hookTest: false,\n' +
' termsAccepted: false,\n' +
' books: Collection {\n' +
' books: Collection<Book> {\n' +
" '0': Book {\n" +
' createdAt: ISODate(\'2020-07-18T17:31:08.535Z\'),\n' +
' tags: [Collection],\n' +
' tags: [Collection<BookTag>],\n' +
" title: 'Bible',\n" +
' author: [Author],\n' +
' publisher: [Reference]\n' +
' },\n' +
' initialized: true,\n' +
' dirty: true\n' +
' },\n' +
' friends: Collection { initialized: true, dirty: false },\n' +
' friends: Collection<Author> { initialized: true, dirty: false },\n' +
" name: 'God',\n" +
" email: 'hello@heaven.god',\n" +
" foo: 'bar',\n" +
' favouriteAuthor: Author {\n' +
' hookTest: false,\n' +
' termsAccepted: false,\n' +
" books: Collection { '0': [Book], initialized: true, dirty: true },\n" +
' friends: Collection { initialized: true, dirty: false },\n' +
" books: Collection<Book> { '0': [Book], initialized: true, dirty: true },\n" +
' friends: Collection<Author> { initialized: true, dirty: false },\n' +
" name: 'God',\n" +
" email: 'hello@heaven.god',\n" +
" foo: 'bar',\n" +
' favouriteAuthor: Author {\n' +
' hookTest: false,\n' +
' termsAccepted: false,\n' +
' books: [Collection],\n' +
' friends: [Collection],\n' +
' books: [Collection<Book>],\n' +
' friends: [Collection<Author>],\n' +
" name: 'God',\n" +
" email: 'hello@heaven.god',\n" +
" foo: 'bar',\n" +
Expand Down
2 changes: 1 addition & 1 deletion tests/bootstrap.ts
Expand Up @@ -37,7 +37,6 @@ export async function initORMMongo() {
type: 'mongo',
ensureIndexes,
implicitTransactions: true,
populateAfterFlush: true,
validate: true,
filters: { allowedFooBars: { cond: args => ({ id: { $in: args.allowed } }), entity: ['FooBar'], default: false } },
pool: { min: 1, max: 3 },
Expand All @@ -60,6 +59,7 @@ export async function initORMMySql<D extends MySqlDriver | MariaDbDriver = MySql
charset: 'utf8mb4',
logger: (i: any) => i,
multipleStatements: true,
populateAfterFlush: false,
entityRepository: SqlEntityRepository,
type,
replicas: [{ name: 'read-1' }, { name: 'read-2' }], // create two read replicas with same configuration, just for testing purposes
Expand Down

0 comments on commit 8a604a6

Please sign in to comment.