Skip to content

Commit 1589d6d

Browse files
committed
fix: made type-metadata.storage.ts much faster
Changed the data modeling of type-metadata storage to store the data in maps instead of arrays. This results in a dramatic improvement of application startup time since many of the lookups for metadata are now in O(1) instead of O(n)
1 parent 760f154 commit 1589d6d

10 files changed

+163
-161
lines changed

packages/graphql/lib/schema-builder/collections/array.collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export class ArrayCollection<T> {
2-
private array: T[] = [];
2+
private readonly array: T[] = [];
33

44
constructor(private globalArray: Array<T>) {}
55

Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import { MetadataListByNameCollection } from './metadata.list.by.name.collection';
1+
import { MetadataListByNameCollection } from './metadata-list-by-name.collection';
22
import { PropertyDirectiveMetadata } from '../metadata';
33

44
export class FieldDirectiveCollection extends MetadataListByNameCollection<PropertyDirectiveMetadata> {
55
sdls = new Set<string>();
66
fieldNames = new Set<string>();
77

88
add(value: PropertyDirectiveMetadata) {
9-
if (this.sdls.has(value.sdl) && this.fieldNames.has(value.fieldName))
9+
if (this.sdls.has(value.sdl) && this.fieldNames.has(value.fieldName)) {
1010
return;
11+
}
1112

1213
super.add(value, value.fieldName);
1314

1415
this.sdls.add(value.sdl);
1516
this.fieldNames.add(value.fieldName);
16-
this.globalArray && this.globalArray.push(value);
17+
this.globalArray?.push(value);
1718
}
1819
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export * from './metadata.storage.collection.list';
2-
export * from './field.directive.collection';
2+
export * from './field-directive.collection';
33
export * from './array.collection';
44
export * from './metada.collection.model.interface';
5-
export * from './metadata.by.name.collection';
6-
export * from './metadata.list.by.name.collection';
5+
export * from './metadata-by-name.collection';
6+
export * from './metadata-list-by-name.collection';
77
export * from './metadata.storage.collection';

packages/graphql/lib/schema-builder/collections/metadata.list.by.name.collection.ts renamed to packages/graphql/lib/schema-builder/collections/metadata-list-by-name.collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MetadataByNameCollection } from './metadata.by.name.collection';
1+
import { MetadataByNameCollection } from './metadata-by-name.collection';
22

33
export class MetadataListByNameCollection<T> extends MetadataByNameCollection<
44
T[]

packages/graphql/lib/schema-builder/collections/metadata.storage.collection.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MetadataByNameCollection } from './metadata.by.name.collection';
1+
import { MetadataByNameCollection } from './metadata-by-name.collection';
22
import {
33
ClassDirectiveMetadata,
44
ClassExtensionsMetadata,
@@ -8,8 +8,8 @@ import {
88
PropertyMetadata,
99
ResolverClassMetadata,
1010
} from '../metadata';
11-
import { MetadataListByNameCollection } from './metadata.list.by.name.collection';
12-
import { FieldDirectiveCollection } from './field.directive.collection';
11+
import { MetadataListByNameCollection } from './metadata-list-by-name.collection';
12+
import { FieldDirectiveCollection } from './field-directive.collection';
1313
import { ObjectTypeMetadata } from '../metadata/object-type.metadata';
1414
import { ArrayCollection } from './array.collection';
1515
import { MetadataCollectionModelInterface } from './metada.collection.model.interface';
@@ -35,6 +35,7 @@ export class MetadataStorageCollection {
3535
this._argumentType = val;
3636
this.all.argumentType.push(val);
3737
}
38+
3839
get argumentType() {
3940
return this._argumentType;
4041
}
@@ -43,6 +44,7 @@ export class MetadataStorageCollection {
4344
this._interface = val;
4445
this.all.interface.push(val);
4546
}
47+
4648
get interface() {
4749
return this._interface;
4850
}
@@ -51,6 +53,7 @@ export class MetadataStorageCollection {
5153
this._inputType = val;
5254
this.all.inputType.push(val);
5355
}
56+
5457
get inputType() {
5558
return this._inputType;
5659
}
@@ -59,6 +62,7 @@ export class MetadataStorageCollection {
5962
this._objectType = val;
6063
this.all.objectType.push(val);
6164
}
65+
6266
get objectType() {
6367
return this._objectType;
6468
}
@@ -67,6 +71,7 @@ export class MetadataStorageCollection {
6771
this._resolver = val;
6872
this.all.resolver.push(val);
6973
}
74+
7075
get resolver() {
7176
return this._resolver;
7277
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { FieldDirectiveCollection } from '../../../lib/schema-builder/collections/';
2+
import { PropertyDirectiveMetadata } from '../../../lib/schema-builder/metadata';
3+
4+
describe('FieldDirectiveCollection', () => {
5+
const directive1: PropertyDirectiveMetadata = {
6+
fieldName: 'foo',
7+
sdl: '@foo',
8+
target: () => {},
9+
};
10+
const directive2: PropertyDirectiveMetadata = {
11+
fieldName: 'bar',
12+
sdl: '@bar',
13+
target: () => {},
14+
};
15+
const directive3: PropertyDirectiveMetadata = {
16+
fieldName: 'baz',
17+
sdl: '@baz',
18+
target: () => {},
19+
};
20+
21+
it('should keep a list of all SDLs added', () => {
22+
const map = new FieldDirectiveCollection();
23+
map.add(directive1);
24+
map.add(directive2);
25+
map.add(directive3);
26+
27+
expect([...map.sdls]).toEqual(['@foo', '@bar', '@baz']);
28+
});
29+
30+
it('should keep a list of all fieldName added', () => {
31+
const map = new FieldDirectiveCollection();
32+
map.add(directive1);
33+
map.add(directive2);
34+
map.add(directive3);
35+
36+
expect([...map.fieldNames]).toEqual(['foo', 'bar', 'baz']);
37+
});
38+
39+
it('should add 2 different directives on the same field', () => {
40+
const map = new FieldDirectiveCollection();
41+
const directive1Alt: PropertyDirectiveMetadata = {
42+
fieldName: 'foo',
43+
sdl: '@bar',
44+
target: () => {},
45+
};
46+
map.add(directive1);
47+
map.add(directive1Alt);
48+
49+
expect(map.getByName('foo')).toEqual([directive1, directive1Alt]);
50+
});
51+
52+
it('should add 2 different fields with the same directive', () => {
53+
const map = new FieldDirectiveCollection();
54+
const directive1Alt: PropertyDirectiveMetadata = {
55+
fieldName: 'bar',
56+
sdl: '@foo',
57+
target: () => {},
58+
};
59+
map.add(directive1);
60+
map.add(directive1Alt);
61+
62+
expect(map.getByName('foo')).toEqual([directive1]);
63+
expect(map.getByName('bar')).toEqual([directive1Alt]);
64+
});
65+
66+
it('should NOT the same directive on the same field twice', () => {
67+
const map = new FieldDirectiveCollection();
68+
map.add(directive1);
69+
map.add(directive1);
70+
71+
const directives = map.getByName('foo');
72+
73+
expect(directives).toEqual([directive1]);
74+
expect(directives.length).toEqual(1);
75+
});
76+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { MetadataByNameCollection } from '../../../lib/schema-builder/collections/';
2+
3+
describe('MetadataByNameCollection', () => {
4+
it('should not add multiple values under the same name', () => {
5+
const map = new MetadataByNameCollection<string>();
6+
map.add('foo', 'uniqueName1');
7+
map.add('bar', 'uniqueName2');
8+
map.add('baz', 'uniqueName1');
9+
10+
expect(map.getByName('uniqueName1')).toBe('foo');
11+
expect(map.getByName('uniqueName2')).toBe('bar');
12+
expect(map.getAll().length).toBe(2);
13+
});
14+
15+
it('should not add multiple values under the same name even in reverse', () => {
16+
const map = new MetadataByNameCollection<string>();
17+
map.unshift('foo', 'uniqueName1');
18+
map.unshift('bar', 'uniqueName2');
19+
map.unshift('baz', 'uniqueName1');
20+
21+
expect(map.getByName('uniqueName1')).toBe('foo');
22+
expect(map.getByName('uniqueName2')).toBe('bar');
23+
expect(map.getAll().length).toBe(2);
24+
});
25+
26+
it('should add items in reverse', () => {
27+
const map = new MetadataByNameCollection<string>();
28+
map.add('foo', 'uniqueName1');
29+
map.unshift('bar', 'uniqueName2');
30+
31+
const [first, second] = map.getAll();
32+
33+
expect(first).toBe('bar');
34+
expect(second).toBe('foo');
35+
});
36+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { MetadataListByNameCollection } from '../../../lib/schema-builder/collections/';
2+
3+
describe('MapArrayByName', () => {
4+
it('should return an empty array if a name is not found', () => {
5+
const map = new MetadataListByNameCollection<string>();
6+
const possibleArray = map.getByName('unknown');
7+
expect(Array.isArray(possibleArray)).toBe(true);
8+
expect(possibleArray.length).toBe(0);
9+
});
10+
11+
it('should continuously add values to an array', () => {
12+
const map = new MetadataListByNameCollection<string>();
13+
map.add('foo', 'uniqueName1');
14+
map.add('baz', 'uniqueName1');
15+
map.add('bar', 'uniqueName2');
16+
17+
const uniqueName1Value = map.getByName('uniqueName1');
18+
const uniqueName2Value = map.getByName('uniqueName2');
19+
20+
expect(uniqueName1Value).toEqual(['foo', 'baz']);
21+
expect(uniqueName2Value).toEqual(['bar']);
22+
});
23+
24+
it('should return a flat array of all values', () => {
25+
const map = new MetadataListByNameCollection<string>();
26+
map.add('foo', 'uniqueName1');
27+
map.add('baz', 'uniqueName1');
28+
map.add('bar', 'uniqueName2');
29+
30+
const allValues = map.getAll();
31+
32+
expect(allValues).toEqual(['foo', 'baz', 'bar']);
33+
});
34+
});

0 commit comments

Comments
 (0)