Skip to content

Commit

Permalink
cache input mappings accross resolvers to reduce memory ussage in lar…
Browse files Browse the repository at this point in the history
…ge schemas
  • Loading branch information
hayes committed Feb 25, 2025
1 parent 47de341 commit 9cfb6a7
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 32 deletions.
9 changes: 9 additions & 0 deletions .changeset/many-parrots-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@pothos/plugin-prisma-utils": minor
"@pothos/plugin-drizzle": minor
"@pothos/plugin-relay": minor
"@pothos/plugin-zod": minor
"@pothos/core": minor
---

cache input mappings accross resolvers to reduce memory ussage in large schemas
10 changes: 2 additions & 8 deletions packages/core/src/utils/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,12 @@ export function mapInputFields<Types extends SchemaTypes, T>(
inputs: Record<string, PothosInputFieldConfig<Types>>,
buildCache: BuildCache<Types>,
mapper: (config: PothosInputFieldConfig<Types>) => T | null,
cache: Map<string, InputTypeFieldsMapping<Types, T>> = new Map(),
): InputFieldsMapping<Types, T> | null {
const filterMappings = new Map<InputFieldsMapping<Types, T>, InputFieldsMapping<Types, T>>();
const hasMappings = new Map<InputFieldsMapping<Types, T>, boolean>();

return filterMapped(
internalMapInputFields(
inputs,
buildCache,
mapper,
new Map<string, InputTypeFieldsMapping<Types, T>>(),
),
);
return filterMapped(internalMapInputFields(inputs, buildCache, mapper, cache));

function filterMapped(map: InputFieldsMapping<Types, T>) {
if (filterMappings.has(map)) {
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-drizzle/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import './schema-builder';
import SchemaBuilder, {
BasePlugin,
createInputValueMapper,
type InputTypeFieldsMapping,
mapInputFields,
type PothosOutputFieldConfig,
type SchemaTypes,
Expand All @@ -26,6 +27,10 @@ const pluginName = 'drizzle';
export default pluginName;

export class PothosDrizzlePlugin<Types extends SchemaTypes> extends BasePlugin<Types> {
private mappingCache = new Map<
string,
InputTypeFieldsMapping<Types, DrizzleGraphQLInputExtensions>
>();
override onOutputFieldConfig(
fieldConfig: PothosOutputFieldConfig<Types>,
): PothosOutputFieldConfig<Types> | null {
Expand All @@ -41,6 +46,7 @@ export class PothosDrizzlePlugin<Types extends SchemaTypes> extends BasePlugin<T

return null;
},
this.mappingCache,
);

const argMapper = argMappings
Expand Down
41 changes: 26 additions & 15 deletions packages/plugin-prisma-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SchemaBuilder, {
BasePlugin,
type BuildCache,
createInputValueMapper,
type InputTypeFieldsMapping,
mapInputFields,
type PothosOutputFieldConfig,
type SchemaTypes,
Expand Down Expand Up @@ -43,24 +44,34 @@ export class PothosPrismaUtilsPlugin<Types extends SchemaTypes> extends BasePlug
super(cache, pluginName);
}

private mappingCache = new Map<
string,
InputTypeFieldsMapping<Types, { nullableFields: Set<string> }>
>();

override onOutputFieldConfig(
fieldConfig: PothosOutputFieldConfig<Types>,
): PothosOutputFieldConfig<Types> | null {
const argMappings = mapInputFields(fieldConfig.args, this.buildCache, (inputField) => {
const inputType = this.buildCache.getTypeConfig(unwrapInputFieldType(inputField.type));

if (inputType.extensions?.pothosPrismaInput) {
return (
typeof inputType.extensions?.pothosPrismaInput === 'object'
? inputType.extensions?.pothosPrismaInput
: {
nullableFields: new Set(),
}
) as { nullableFields: Set<string> };
}

return null;
});
const argMappings = mapInputFields(
fieldConfig.args,
this.buildCache,
(inputField) => {
const inputType = this.buildCache.getTypeConfig(unwrapInputFieldType(inputField.type));

if (inputType.extensions?.pothosPrismaInput) {
return (
typeof inputType.extensions?.pothosPrismaInput === 'object'
? inputType.extensions?.pothosPrismaInput
: {
nullableFields: new Set(),
}
) as { nullableFields: Set<string> };
}

return null;
},
this.mappingCache,
);

if (!argMappings) {
return fieldConfig;
Expand Down
31 changes: 23 additions & 8 deletions packages/plugin-relay/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './schema-builder';
import SchemaBuilder, {
BasePlugin,
createInputValueMapper,
type InputTypeFieldsMapping,
mapInputFields,
type PartialResolveInfo,
type PothosOutputFieldConfig,
Expand All @@ -21,18 +22,32 @@ const pluginName = 'relay';
export default pluginName;

export class PothosRelayPlugin<Types extends SchemaTypes> extends BasePlugin<Types> {
private mappingCache = new Map<
string,
InputTypeFieldsMapping<
Types,
{ typename: string; parseId: (id: string, ctx: object) => unknown }[] | boolean
>
>();
override onOutputFieldConfig(
fieldConfig: PothosOutputFieldConfig<Types>,
): PothosOutputFieldConfig<Types> | null {
const argMappings = mapInputFields(fieldConfig.args, this.buildCache, (inputField) => {
if (inputField.extensions?.isRelayGlobalID) {
return (inputField.extensions?.relayGlobalIDFor ??
inputField.extensions?.relayGlobalIDAlwaysParse ??
false) as { typename: string; parseId: (id: string, ctx: object) => unknown }[] | boolean;
}
const argMappings = mapInputFields(
fieldConfig.args,
this.buildCache,
(inputField) => {
if (inputField.extensions?.isRelayGlobalID) {
return (inputField.extensions?.relayGlobalIDFor ??
inputField.extensions?.relayGlobalIDAlwaysParse ??
false) as
| { typename: string; parseId: (id: string, ctx: object) => unknown }[]
| boolean;
}

return null;
});
return null;
},
this.mappingCache,
);

if (!argMappings) {
return fieldConfig;
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-zod/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './global-types';
import SchemaBuilder, {
BasePlugin,
type InputTypeFieldsMapping,
mapInputFields,
type PothosInputFieldConfig,
type PothosInputFieldType,
Expand Down Expand Up @@ -64,6 +65,8 @@ export class PothosZodPlugin<Types extends SchemaTypes> extends BasePlugin<Types
return fieldConfig;
}

private mappingCache = new Map<string, InputTypeFieldsMapping<Types, zod.ZodType<unknown>>>();

override wrapResolve(
resolver: GraphQLFieldResolver<unknown, Types['Context'], object>,
fieldConfig: PothosOutputFieldConfig<Types>,
Expand All @@ -73,6 +76,7 @@ export class PothosZodPlugin<Types extends SchemaTypes> extends BasePlugin<Types
fieldConfig.args,
this.buildCache,
(field) => field.extensions?.validator ?? null,
this.mappingCache,
);

if (!argMap && !fieldConfig.pothosOptions.validate) {
Expand Down
6 changes: 5 additions & 1 deletion website/content/docs/guide/writing-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@ The relay plugin uses these methods to decode `globalID` inputs:
```typescript
export class PothosRelayPlugin<Types extends SchemaTypes> extends BasePlugin<Types> {
// Optionally create a cache for input mappings so that mappings can be reused across multiple fields
// Be sure to only provide a mapping cache if your argument mappings are not specific to the current field
private mappingCache = new Map<string, InputTypeFieldsMapping<Types, boolean>>();

wrapResolve(
resolver: GraphQLFieldResolver<unknown, Types['Context'], object>,
fieldConfig: PothosOutputFieldConfig<Types>,
Expand All @@ -493,7 +497,7 @@ export class PothosRelayPlugin<Types extends SchemaTypes> extends BasePlugin<Typ

// returning null means no mapping will be created for this input field
return null;
});
}, this.mappingCache);

// If all fields reachable through args return null for their mapping, we don't need to wrap the resolver
if (!argMappings) {
Expand Down

0 comments on commit 9cfb6a7

Please sign in to comment.