Skip to content

Commit e955e4e

Browse files
committed
feat: Optimize metadata-storage build with HashMap caching for O(1) lookups
1 parent 2305f78 commit e955e4e

3 files changed

Lines changed: 196 additions & 75 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "type-graphql",
3-
"version": "2.0.0-rc.2",
3+
"version": "2.0.0-rc.3",
44
"private": false,
55
"description": "Create GraphQL schema and resolvers with TypeScript, using classes and decorators!",
66
"keywords": [

src/metadata/metadata-storage.ts

Lines changed: 167 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,44 @@ export class MetadataStorage {
4646

4747
objectTypes: ObjectClassMetadata[] = [];
4848

49+
objectTypesCache = new Map<Function, ObjectClassMetadata>();
50+
4951
inputTypes: ClassMetadata[] = [];
5052

5153
argumentTypes: ClassMetadata[] = [];
5254

5355
interfaceTypes: InterfaceClassMetadata[] = [];
5456

57+
interfaceTypesCache = new Map<Function, InterfaceClassMetadata>();
58+
5559
authorizedFields: AuthorizedMetadata[] = [];
5660

61+
authorizedFieldsByTargetAndFieldCache = new Map<any, AuthorizedMetadata>();
62+
5763
authorizedResolver: AuthorizedClassMetadata[] = [];
5864

65+
authorizedResolverByTargetCache = new Map<Function, AuthorizedClassMetadata>();
66+
5967
enums: EnumMetadata[] = [];
6068

6169
unions: UnionMetadataWithSymbol[] = [];
6270

6371
middlewares: MiddlewareMetadata[] = [];
6472

73+
middlewaresByTargetAndFieldCache = new Map<string, MiddlewareMetadata[]>();
74+
6575
resolverMiddlewares: ResolverMiddlewareMetadata[] = [];
6676

77+
resolverMiddlewaresByTargetCache = new Map<Function, ResolverMiddlewareMetadata[]>();
78+
6779
classDirectives: DirectiveClassMetadata[] = [];
6880

81+
classDirectivesByTargetCache = new Map<Function, DirectiveClassMetadata[]>();
82+
6983
fieldDirectives: DirectiveFieldMetadata[] = [];
7084

85+
fieldDirectivesByTargetAndFieldCache = new Map<any, DirectiveFieldMetadata[]>();
86+
7187
argumentDirectives: DirectiveArgumentMetadata[] = [];
7288

7389
classExtensions: ExtensionsClassMetadata[] = [];
@@ -76,10 +92,16 @@ export class MetadataStorage {
7692

7793
resolverClasses: ResolverClassMetadata[] = [];
7894

95+
resolverClassesCache = new Map<Function, ResolverClassMetadata>();
96+
7997
fields: FieldMetadata[] = [];
8098

99+
fieldsCache = new Map<any, FieldMetadata[]>();
100+
81101
params: ParamMetadata[] = [];
82102

103+
paramsCache = new Map<string, ParamMetadata[]>();
104+
83105
collectQueryHandlerMetadata(definition: ResolverMetadata) {
84106
this.queries.push(definition);
85107
}
@@ -173,13 +195,114 @@ export class MetadataStorage {
173195
this.fieldExtensions.push(definition);
174196
}
175197

198+
initCache() {
199+
if (this.resolverClasses?.length) {
200+
this.resolverClasses.forEach(resolverClass => {
201+
if (!this.resolverClassesCache.has(resolverClass.target)) {
202+
this.resolverClassesCache.set(resolverClass.target, resolverClass);
203+
}
204+
});
205+
}
206+
207+
if (this.params?.length) {
208+
this.params.forEach(param => {
209+
const key = `${param.target}-${param.methodName}`;
210+
if (!this.paramsCache.has(key)) {
211+
this.paramsCache.set(key, []);
212+
}
213+
this.paramsCache.get(key)?.push(param);
214+
});
215+
}
216+
217+
if (this.middlewares?.length) {
218+
this.middlewares.forEach(middleware => {
219+
const key = `${middleware.target}-${middleware.fieldName}`;
220+
if (!this.middlewaresByTargetAndFieldCache.has(key)) {
221+
this.middlewaresByTargetAndFieldCache.set(key, []);
222+
}
223+
this.middlewaresByTargetAndFieldCache.get(key)?.push(middleware);
224+
});
225+
}
226+
227+
if (this.resolverMiddlewares?.length) {
228+
this.resolverMiddlewares.forEach(middleware => {
229+
const key = middleware.target;
230+
if (!this.resolverMiddlewaresByTargetCache.has(key)) {
231+
this.resolverMiddlewaresByTargetCache.set(key, []);
232+
}
233+
this.resolverMiddlewaresByTargetCache.get(key)?.push(middleware);
234+
});
235+
}
236+
237+
if (this.fieldDirectives?.length) {
238+
this.fieldDirectives.forEach(directive => {
239+
const key = `${directive.target}-${directive.fieldName}`;
240+
if (!this.fieldDirectivesByTargetAndFieldCache.has(key)) {
241+
this.fieldDirectivesByTargetAndFieldCache.set(key, []);
242+
}
243+
this.fieldDirectivesByTargetAndFieldCache.get(key)?.push(directive);
244+
});
245+
}
246+
247+
if (this.classDirectives?.length) {
248+
this.classDirectives.forEach(directive => {
249+
const key = directive.target;
250+
if (!this.classDirectivesByTargetCache.has(key)) {
251+
this.classDirectivesByTargetCache.set(key, []);
252+
}
253+
this.classDirectivesByTargetCache.get(key)?.push(directive);
254+
});
255+
}
256+
257+
if (this.authorizedFields?.length) {
258+
this.authorizedFields.forEach(field => {
259+
const key = `${field.target}-${field.fieldName}`;
260+
if (!this.authorizedFieldsByTargetAndFieldCache.has(key)) {
261+
this.authorizedFieldsByTargetAndFieldCache.set(key, field);
262+
}
263+
});
264+
}
265+
266+
if (this.authorizedResolver?.length) {
267+
this.authorizedResolver.forEach(resolver => {
268+
const key = resolver.target;
269+
if (!this.authorizedResolverByTargetCache.has(key)) {
270+
this.authorizedResolverByTargetCache.set(key, resolver);
271+
}
272+
});
273+
}
274+
275+
if (this.fields?.length) {
276+
this.fields.forEach(field => {
277+
if (!this.fieldsCache.has(field.target)) {
278+
this.fieldsCache.set(field.target, []);
279+
}
280+
this.fieldsCache.get(field.target)?.push(field);
281+
});
282+
}
283+
284+
if (this.objectTypes?.length) {
285+
this.objectTypes.forEach(objType => {
286+
this.objectTypesCache.set(objType.target, objType);
287+
});
288+
}
289+
290+
if (this.interfaceTypes?.length) {
291+
this.interfaceTypes.forEach(interfaceType => {
292+
this.interfaceTypesCache.set(interfaceType.target, interfaceType);
293+
});
294+
}
295+
}
296+
176297
build(options: SchemaGeneratorOptions) {
177298
this.classDirectives.reverse();
178299
this.fieldDirectives.reverse();
179300
this.argumentDirectives.reverse();
180301
this.classExtensions.reverse();
181302
this.fieldExtensions.reverse();
182303

304+
this.initCache();
305+
183306
this.buildClassMetadata(this.objectTypes);
184307
this.buildClassMetadata(this.inputTypes);
185308
this.buildClassMetadata(this.argumentTypes);
@@ -223,34 +346,33 @@ export class MetadataStorage {
223346
private buildClassMetadata(definitions: ClassMetadata[]) {
224347
definitions.forEach(def => {
225348
if (!def.fields) {
226-
const fields = this.fields.filter(field => field.target === def.target);
349+
const fields = this.fieldsCache.get(def.target) || [];
227350
fields.forEach(field => {
228351
field.roles = this.findFieldRoles(field.target, field.name);
229-
field.params = this.params.filter(
230-
param => param.target === field.target && field.name === param.methodName,
352+
353+
const paramKey = `${field.target.name}-${field.name}`;
354+
field.params = this.paramsCache.get(paramKey) || [];
355+
356+
const middlewares1 = this.resolverMiddlewaresByTargetCache.get(field.target) || [];
357+
const middlewaresKey = `${field.target.name}-${field.name}`;
358+
const middlewares2 = this.middlewaresByTargetAndFieldCache.get(middlewaresKey) || [];
359+
360+
field.middlewares = mapMiddlewareMetadataToArray(middlewares1).concat(
361+
mapMiddlewareMetadataToArray(middlewares2),
231362
);
232-
field.middlewares = [
233-
...mapMiddlewareMetadataToArray(
234-
this.resolverMiddlewares.filter(middleware => middleware.target === field.target),
235-
),
236-
...mapMiddlewareMetadataToArray(
237-
this.middlewares.filter(
238-
middleware =>
239-
middleware.target === field.target && middleware.fieldName === field.name,
240-
),
241-
),
242-
];
243-
field.directives = this.fieldDirectives
244-
.filter(it => it.target === field.target && it.fieldName === field.name)
245-
.map(it => it.directive);
363+
364+
const directives =
365+
this.fieldDirectivesByTargetAndFieldCache.get(`${field.target.name}-${field.name}`) ||
366+
[];
367+
field.directives = directives.map(it => it.directive);
368+
246369
field.extensions = this.findExtensions(field.target, field.name);
247370
});
248371
def.fields = fields;
249372
}
250373
if (!def.directives) {
251-
def.directives = this.classDirectives
252-
.filter(it => it.target === def.target)
253-
.map(it => it.directive);
374+
const directives = this.classDirectivesByTargetCache.get(def.target) || [];
375+
def.directives = directives.map(directive => directive.directive);
254376
}
255377
if (!def.extensions) {
256378
def.extensions = this.findExtensions(def.target);
@@ -260,28 +382,21 @@ export class MetadataStorage {
260382

261383
private buildResolversMetadata(definitions: BaseResolverMetadata[]) {
262384
definitions.forEach(def => {
263-
const resolverClassMetadata = this.resolverClasses.find(
264-
resolver => resolver.target === def.target,
265-
)!;
266-
def.resolverClassMetadata = resolverClassMetadata;
267-
def.params = this.params.filter(
268-
param => param.target === def.target && def.methodName === param.methodName,
269-
);
385+
def.resolverClassMetadata = this.resolverClassesCache.get(def.target);
386+
def.params = this.paramsCache.get(`${def.target}-${def.methodName}`) || [];
270387
def.roles = this.findFieldRoles(def.target, def.methodName);
271-
def.middlewares = [
272-
...mapMiddlewareMetadataToArray(
273-
this.resolverMiddlewares.filter(middleware => middleware.target === def.target),
274-
),
275-
...mapMiddlewareMetadataToArray(
276-
this.middlewares.filter(
277-
middleware =>
278-
middleware.target === def.target && def.methodName === middleware.fieldName,
279-
),
388+
389+
def.middlewares = mapMiddlewareMetadataToArray(
390+
this.resolverMiddlewaresByTargetCache.get(def.target) || [],
391+
).concat(
392+
mapMiddlewareMetadataToArray(
393+
this.middlewaresByTargetAndFieldCache.get(`${def.target}-${def.methodName}`) || [],
280394
),
281-
];
282-
def.directives = this.fieldDirectives
283-
.filter(it => it.target === def.target && it.fieldName === def.methodName)
284-
.map(it => it.directive);
395+
);
396+
397+
def.directives = (
398+
this.fieldDirectivesByTargetAndFieldCache.get(`${def.target}-${def.methodName}`) || []
399+
).map(it => it.directive);
285400
def.extensions = this.findExtensions(def.target, def.methodName);
286401
});
287402
}
@@ -293,20 +408,21 @@ export class MetadataStorage {
293408
this.buildResolversMetadata(definitions);
294409
definitions.forEach(def => {
295410
def.roles = this.findFieldRoles(def.target, def.methodName);
296-
def.directives = this.fieldDirectives
297-
.filter(it => it.target === def.target && it.fieldName === def.methodName)
298-
.map(it => it.directive);
411+
def.directives = (
412+
this.fieldDirectivesByTargetAndFieldCache.get(`${def.target}-${def.methodName}`) || []
413+
).map(it => it.directive);
299414
def.extensions = this.findExtensions(def.target, def.methodName);
300415
def.getObjectType =
301416
def.kind === "external"
302-
? this.resolverClasses.find(resolver => resolver.target === def.target)!.getObjectType
417+
? this.resolverClassesCache.get(def.target)!.getObjectType
303418
: () => def.target as ClassType;
304419
if (def.kind === "external") {
305-
const typeClass = this.resolverClasses.find(resolver => resolver.target === def.target)!
306-
.getObjectType!();
420+
const typeClass = this.resolverClassesCache.get(def.target)!.getObjectType!();
421+
if (!typeClass) {
422+
throw new Error(`Unable to find type class for external resolver ${def.target.name}`);
423+
}
307424
const typeMetadata =
308-
this.objectTypes.find(objTypeDef => objTypeDef.target === typeClass) ||
309-
this.interfaceTypes.find(interfaceTypeDef => interfaceTypeDef.target === typeClass);
425+
this.objectTypesCache.get(typeClass) || this.interfaceTypesCache.get(typeClass);
310426
if (!typeMetadata) {
311427
throw new Error(
312428
`Unable to find type metadata for input type or object type named '${typeClass.name}'`,
@@ -367,8 +483,7 @@ export class MetadataStorage {
367483

368484
// copy and modify metadata of resolver from parent resolver class
369485
while (superResolver.prototype) {
370-
// eslint-disable-next-line @typescript-eslint/no-loop-func
371-
const superResolverMetadata = this.resolverClasses.find(it => it.target === superResolver);
486+
const superResolverMetadata = this.resolverClassesCache.get(superResolver);
372487
if (superResolverMetadata) {
373488
this.queries = mapSuperResolverHandlers(this.queries, superResolver, def);
374489
this.mutations = mapSuperResolverHandlers(this.mutations, superResolver, def);
@@ -386,9 +501,8 @@ export class MetadataStorage {
386501

387502
private findFieldRoles(target: Function, fieldName: string): any[] | undefined {
388503
const authorizedField =
389-
this.authorizedFields.find(
390-
authField => authField.target === target && authField.fieldName === fieldName,
391-
) ?? this.authorizedResolver.find(authScope => authScope.target === target);
504+
this.authorizedFieldsByTargetAndFieldCache.get(`${target}-${fieldName}`) ||
505+
this.authorizedResolverByTargetCache.get(target);
392506
if (!authorizedField) {
393507
return undefined;
394508
}

0 commit comments

Comments
 (0)