Skip to content

Commit 2f58fbe

Browse files
committed
refactor: unify resolver registration by replacing onQuery and onMutation with a single resolver method
1 parent 291d507 commit 2f58fbe

File tree

4 files changed

+74
-205
lines changed

4 files changed

+74
-205
lines changed

packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,24 @@ export class AppSyncGraphQLResolver extends Router {
108108
}
109109

110110
/**
111-
* Executes the appropriate resolver (query or mutation) for a given AppSync GraphQL event.
111+
* Executes the appropriate resolver for a given AppSync GraphQL event.
112112
*
113113
* This method attempts to resolve the handler for the specified field and type name
114-
* from the query and mutation registries. If a matching handler is found, it invokes
115-
* the handler with the event arguments. If no handler is found, it throws a
116-
* `ResolverNotFoundException`.
114+
* from the resolver registry. If a matching handler is found, it invokes the handler
115+
* with the event arguments. If no handler is found, it throws a `ResolverNotFoundException`.
117116
*
118117
* @param event - The AppSync GraphQL event containing resolver information.
119118
* @throws {ResolverNotFoundException} If no resolver is registered for the given field and type.
120119
*/
121120
async #executeSingleResolver(event: AppSyncGraphQLEvent): Promise<unknown> {
122121
const { fieldName, parentTypeName: typeName } = event.info;
123-
const queryHandlerOptions = this.onQueryRegistry.resolve(
124-
typeName,
125-
fieldName
126-
);
127-
if (queryHandlerOptions) {
128-
return await queryHandlerOptions.handler.apply(this, [event.arguments]);
129-
}
130122

131-
const mutationHandlerOptions = this.onMutationRegistry.resolve(
123+
const resolverHandlerOptions = this.resolverRegistry.resolve(
132124
typeName,
133125
fieldName
134126
);
135-
if (mutationHandlerOptions) {
136-
return await mutationHandlerOptions.handler.apply(this, [
127+
if (resolverHandlerOptions) {
128+
return await resolverHandlerOptions.handler.apply(this, [
137129
event.arguments,
138130
]);
139131
}

packages/event-handler/src/appsync-graphql/RouteHandlerRegistry.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
} from '../types/appsync-graphql.js';
66

77
/**
8-
* Registry for storing route handlers for the `query` and `mutation` events in AWS AppSync GraphQL API's.
8+
* Registry for storing route handlers for GraphQL resolvers in AWS AppSync GraphQL API's.
99
*
1010
* This class should not be used directly unless you are implementing a custom router.
1111
* Instead, use the {@link Router} class, which is the recommended way to register routes.
@@ -19,14 +19,9 @@ class RouteHandlerRegistry {
1919
* A logger instance to be used for logging debug and warning messages.
2020
*/
2121
readonly #logger: GenericLogger;
22-
/**
23-
* The event type stored in the registry.
24-
*/
25-
readonly #eventType: 'onQuery' | 'onMutation';
2622

2723
public constructor(options: RouteHandlerRegistryOptions) {
2824
this.#logger = options.logger;
29-
this.#eventType = options.eventType ?? 'onQuery';
3025
}
3126

3227
/**
@@ -40,9 +35,7 @@ class RouteHandlerRegistry {
4035
*/
4136
public register(options: RouteHandlerOptions): void {
4237
const { fieldName, handler, typeName } = options;
43-
this.#logger.debug(
44-
`Adding ${this.#eventType} resolver for field ${typeName}.${fieldName}`
45-
);
38+
this.#logger.debug(`Adding resolver for field ${typeName}.${fieldName}`);
4639
const cacheKey = this.#makeKey(typeName, fieldName);
4740
if (this.resolvers.has(cacheKey)) {
4841
this.#logger.warn(
@@ -67,7 +60,7 @@ class RouteHandlerRegistry {
6760
fieldName: string
6861
): RouteHandlerOptions | undefined {
6962
this.#logger.debug(
70-
`Looking for ${this.#eventType} resolver for type=${typeName}, field=${fieldName}`
63+
`Looking for resolver for type=${typeName}, field=${fieldName}`
7164
);
7265
return this.resolvers.get(this.#makeKey(typeName, fieldName));
7366
}

packages/event-handler/src/appsync-graphql/Router.ts

Lines changed: 53 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
1-
import {
2-
EnvironmentVariablesService,
3-
isRecord,
4-
} from '@aws-lambda-powertools/commons';
1+
import { EnvironmentVariablesService } from '@aws-lambda-powertools/commons';
52
import type { GenericLogger } from '@aws-lambda-powertools/commons/types';
63
import { getStringFromEnv } from '@aws-lambda-powertools/commons/utils/env';
74
import type {
85
GraphQlRouteOptions,
96
GraphQlRouterOptions,
10-
OnMutationHandler,
11-
OnQueryHandler,
7+
ResolverHandler,
128
} from '../types/appsync-graphql.js';
139
import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';
1410

1511
/**
16-
* Class for registering routes for the `query` and `mutation` events in AWS AppSync GraphQL APIs.
12+
* Class for registering resolvers for GraphQL events in AWS AppSync GraphQL APIs.
1713
*/
1814
class Router {
1915
/**
20-
* A map of registered routes for the `query` event, keyed by their fieldNames.
16+
* A map of registered routes for all GraphQL events, keyed by their fieldNames.
2117
*/
22-
protected readonly onQueryRegistry: RouteHandlerRegistry;
23-
/**
24-
* A map of registered routes for the `mutation` event, keyed by their fieldNames.
25-
*/
26-
protected readonly onMutationRegistry: RouteHandlerRegistry;
18+
protected readonly resolverRegistry: RouteHandlerRegistry;
2719
/**
2820
* A logger instance to be used for logging debug, warning, and error messages.
2921
*
@@ -50,133 +42,39 @@ class Router {
5042
error: console.error,
5143
warn: console.warn,
5244
};
53-
this.onQueryRegistry = new RouteHandlerRegistry({
54-
logger: this.logger,
55-
eventType: 'onQuery',
56-
});
57-
this.onMutationRegistry = new RouteHandlerRegistry({
45+
this.resolverRegistry = new RouteHandlerRegistry({
5846
logger: this.logger,
59-
eventType: 'onMutation',
6047
});
6148
this.isDev = this.envService.isDevMode();
6249
}
6350

6451
/**
65-
* Register a handler function for the `query` event.
66-
67-
* Registers a handler for a specific GraphQL Query field. The handler will be invoked when a request is made
68-
* for the specified field in the Query type.
69-
*
70-
* @example
71-
* ```ts
72-
* import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql';
52+
* Register a resolver function for any GraphQL event.
7353
*
74-
* const app = new AppSyncGraphQLResolver();
54+
* Registers a handler for a specific GraphQL field. The handler will be invoked when a request is made
55+
* for the specified field.
7556
*
76-
* app.onQuery('getPost', async (payload) => {
77-
* // your business logic here
78-
* return payload;
79-
* });
80-
81-
* export const handler = async (event, context) =>
82-
* app.resolve(event, context);
83-
* ```
84-
*
85-
* You can also specify the type of the arguments using a generic type parameter:
86-
*
8757
* @example
8858
* ```ts
8959
* import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql';
9060
*
9161
* const app = new AppSyncGraphQLResolver();
9262
*
93-
* app.onQuery<{ postId: string }>('getPost', async ({ postId }) => {
94-
* // postId is now typed as string
95-
* return { id: postId };
63+
* // Register a Query resolver
64+
* app.resolver(async (payload) => {
65+
* // your business logic here
66+
* return payload;
67+
* }, {
68+
* fieldName: 'getPost'
9669
* });
97-
98-
* export const handler = async (event, context) =>
99-
* app.resolve(event, context);
100-
* ```
101-
*
102-
* As a decorator:
103-
*
104-
* @example
105-
* ```ts
106-
* import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql';
107-
*
108-
* const app = new AppSyncGraphQLResolver();
109-
*
110-
* class Lambda {
111-
* @app.onQuery('getPost')
112-
* async handleGetPost(payload) {
113-
* // your business logic here
114-
* return payload;
115-
* }
116-
*
117-
* async handler(event, context) {
118-
* return app.resolve(event, context);
119-
* }
120-
* }
12170
*
122-
* const lambda = new Lambda();
123-
* export const handler = lambda.handler.bind(lambda);
124-
* ```
125-
*
126-
* @param fieldName - The name of the Query field to register the handler for.
127-
* @param handler - The handler function to be called when the event is received.
128-
* @param options - Optional route options.
129-
* @param options.typeName - The name of the GraphQL type to use for the resolver (defaults to 'Query').
130-
*/
131-
public onQuery<TParams extends Record<string, unknown>>(
132-
fieldName: string,
133-
handler: OnQueryHandler<TParams>,
134-
options?: GraphQlRouteOptions
135-
): void;
136-
public onQuery(
137-
fieldName: string,
138-
options?: GraphQlRouteOptions
139-
): MethodDecorator;
140-
public onQuery<TParams extends Record<string, unknown>>(
141-
fieldName: string,
142-
handler?: OnQueryHandler<TParams> | GraphQlRouteOptions,
143-
options?: GraphQlRouteOptions
144-
): MethodDecorator | undefined {
145-
if (handler && typeof handler === 'function') {
146-
this.onQueryRegistry.register({
147-
fieldName,
148-
handler: handler as OnQueryHandler<Record<string, unknown>>,
149-
typeName: options?.typeName ?? 'Query',
150-
});
151-
return;
152-
}
153-
154-
return (_target, _propertyKey, descriptor: PropertyDescriptor) => {
155-
const routeOptions = isRecord(handler) ? handler : options;
156-
this.onQueryRegistry.register({
157-
fieldName,
158-
handler: descriptor.value as OnQueryHandler<Record<string, unknown>>,
159-
typeName: routeOptions?.typeName ?? 'Query',
160-
});
161-
return descriptor;
162-
};
163-
}
164-
165-
/**
166-
* Register a handler function for the `mutation` event.
167-
*
168-
* Registers a handler for a specific GraphQL Mutation field. The handler will be invoked when a request is made
169-
* for the specified field in the Mutation type.
170-
*
171-
* @example
172-
* ```ts
173-
* import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql';
174-
*
175-
* const app = new AppSyncGraphQLResolver();
176-
*
177-
* app.onMutation('createPost', async (payload) => {
71+
* // Register a Mutation resolver
72+
* app.resolver(async (payload) => {
17873
* // your business logic here
17974
* return payload;
75+
* }, {
76+
* fieldName: 'createPost',
77+
* typeName: 'Mutation'
18078
* });
18179
*
18280
* export const handler = async (event, context) =>
@@ -191,9 +89,11 @@ class Router {
19189
*
19290
* const app = new AppSyncGraphQLResolver();
19391
*
194-
* app.onMutation<{ title: string; content: string }>('createPost', async ({ title, content }) => {
195-
* // title and content are now typed as string
196-
* return { id: '123', title, content };
92+
* app.resolver<{ postId: string }>(async ({ postId }) => {
93+
* // postId is now typed as string
94+
* return { id: postId };
95+
* }, {
96+
* fieldName: 'getPost'
19797
* });
19898
*
19999
* export const handler = async (event, context) =>
@@ -209,8 +109,8 @@ class Router {
209109
* const app = new AppSyncGraphQLResolver();
210110
*
211111
* class Lambda {
212-
* @app.onMutation('createPost')
213-
* async handleCreatePost(payload) {
112+
* @app.resolver({ fieldName: 'getPost' })
113+
* async handleGetPost(payload) {
214114
* // your business logic here
215115
* return payload;
216116
* }
@@ -224,41 +124,43 @@ class Router {
224124
* export const handler = lambda.handler.bind(lambda);
225125
* ```
226126
*
227-
* @param fieldName - The name of the Mutation field to register the handler for.
228127
* @param handler - The handler function to be called when the event is received.
229-
* @param options - Optional route options.
230-
* @param options.typeName - The name of the GraphQL type to use for the resolver (defaults to 'Mutation').
128+
* @param options - Route options including the required fieldName and optional typeName.
129+
* @param options.fieldName - The name of the field to register the handler for.
130+
* @param options.typeName - The name of the GraphQL type to use for the resolver (defaults to 'Query').
231131
*/
232-
public onMutation<TParams extends Record<string, unknown>>(
233-
fieldName: string,
234-
handler: OnMutationHandler<TParams>,
235-
options?: GraphQlRouteOptions
132+
public resolver<TParams extends Record<string, unknown>>(
133+
handler: ResolverHandler<TParams>,
134+
options: GraphQlRouteOptions
236135
): void;
237-
public onMutation(
238-
fieldName: string,
239-
options?: GraphQlRouteOptions
240-
): MethodDecorator;
241-
public onMutation<TParams extends Record<string, unknown>>(
242-
fieldName: string,
243-
handler?: OnMutationHandler<TParams> | GraphQlRouteOptions,
136+
public resolver(options: GraphQlRouteOptions): MethodDecorator;
137+
public resolver<TParams extends Record<string, unknown>>(
138+
handler: ResolverHandler<TParams> | GraphQlRouteOptions,
244139
options?: GraphQlRouteOptions
245140
): MethodDecorator | undefined {
246-
if (handler && typeof handler === 'function') {
247-
this.onMutationRegistry.register({
248-
fieldName,
249-
handler: handler as OnMutationHandler<Record<string, unknown>>,
250-
typeName: options?.typeName ?? 'Mutation',
141+
if (typeof handler === 'function') {
142+
const resolverOptions = options as GraphQlRouteOptions;
143+
const typeName = resolverOptions.typeName ?? 'Query';
144+
145+
this.resolverRegistry.register({
146+
fieldName: resolverOptions.fieldName,
147+
handler: handler as ResolverHandler<Record<string, unknown>>,
148+
typeName,
251149
});
150+
252151
return;
253152
}
254153

154+
const resolverOptions = handler;
255155
return (_target, _propertyKey, descriptor: PropertyDescriptor) => {
256-
const routeOptions = isRecord(handler) ? handler : options;
257-
this.onMutationRegistry.register({
258-
fieldName,
259-
handler: descriptor.value as OnMutationHandler<Record<string, unknown>>,
260-
typeName: routeOptions?.typeName ?? 'Mutation',
156+
const typeName = resolverOptions.typeName ?? 'Query';
157+
158+
this.resolverRegistry.register({
159+
fieldName: resolverOptions.fieldName,
160+
handler: descriptor.value,
161+
typeName,
261162
});
163+
262164
return descriptor;
263165
};
264166
}

0 commit comments

Comments
 (0)