From d7fccdceacb80bcf2ea41026d0b7c4cfdad77c1f Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Tue, 20 Oct 2026 07:30:10 +0000 Subject: [PATCH 1/4] feat: add createAppSyncBatchHandler for AppSync BatchInvoke Add the batched counterpart to createAppSyncHandler. It takes an array of resolver events, authenticates once per batch off the first event, and returns R[] in source order. The context carries both ctx.event (events[0]) and the full ctx.events array. Factor the typed-error normalisation and envelope construction out of createAppSyncHandler into shared toTypedError/buildErrorEnvelope helpers so both handlers use the identical error model. createAppSyncHandler behavior is unchanged. With APPSYNC_TYPED_ERRORS=true the batch handler returns one error envelope per event; otherwise it rethrows. --- llm.md | 10 +++ src/lambda/handler.ts | 169 +++++++++++++++++++++++++++++++++--------- 2 files changed, 144 insertions(+), 35 deletions(-) diff --git a/llm.md b/llm.md index e8fce70..43414dc 100644 --- a/llm.md +++ b/llm.md @@ -182,6 +182,16 @@ export const handler = createAppSyncHandler(async }); ``` +For AppSync BatchInvoke, use `createAppSyncBatchHandler`. It takes an array of resolver events and returns an array of results in the same order (array in, array out, order preserved). It uses the same context as `createAppSyncHandler`, with the logger and the Cognito authorizer resolved once per batch off the first event. The context exposes both the first event as `ctx.event` (so handler code that reads `ctx.event.arguments` / `ctx.event.identity` keeps working) and the full batch as `ctx.events`. The typed-error model is identical to `createAppSyncHandler`: with `APPSYNC_TYPED_ERRORS === 'true'` a thrown `errors.appsync.*` or `errors.HttpError` returns an array of error envelopes aligned to the batch length, and otherwise rethrows. A plain `Error` always fails the invocation. + +```typescript +import { createAppSyncBatchHandler } from 'cdk-serverless/lambda'; + +export const handler = createAppSyncBatchHandler(async (ctx) => { + return Promise.all(ctx.events.map((event) => getItem(event.arguments.id))); +}); +``` + AppSync resolvers can throw typed errors from the `errors.appsync` namespace. `createAppSyncHandler` catches them and logs at `warn`. Whether it returns a `{ data, errorType, errorMessage, errorInfo }` envelope or rethrows is gated on the `APPSYNC_TYPED_ERRORS` environment variable. When `APPSYNC_TYPED_ERRORS === 'true'` the handler returns the envelope so the invocation stays off the Lambda Errors metric. Otherwise it rethrows the typed error, logging at `error`, so the invocation fails. A plain `Error` always fails the invocation regardless of the flag. ```typescript diff --git a/src/lambda/handler.ts b/src/lambda/handler.ts index d640bf5..bc2aba6 100644 --- a/src/lambda/handler.ts +++ b/src/lambda/handler.ts @@ -283,6 +283,58 @@ function httpStatusToErrorType(statusCode: number): string { } } +/** + * The typed-error shape shared by createAppSyncHandler and createAppSyncBatchHandler. + */ +interface TypedAppSyncError { + errorType: string; + message: string; + errorInfo?: Record; +} + +/** + * Normalises the two recognised typed-error shapes to a common + * { errorType, message, errorInfo } so the gate, envelope and rethrow are a + * single code path across the single and batch handlers. Returns undefined for + * anything that is neither (plain Error, Boom), which the callers rethrow. + * + * - appsyncErrors.AppSyncError carries its own GraphQL errorType and an optional + * errorInfo, which flow through unchanged. + * - errors.HttpError (the HTTP base thrown by the shared JwtAuthorizer via + * assertAuthenticated/assertGroup/assertAdmin, and by resolver code directly) + * has no GraphQL errorType, so map its statusCode with httpStatusToErrorType. + * HttpError carries no errorInfo, so errorInfo is undefined. + * + * The two instanceof checks are disjoint: HttpError and AppSyncError share no + * ancestry, so an appsync.ForbiddenError takes the AppSyncError branch and an + * errors.ForbiddenError the HttpError branch. + */ +function toTypedError(error: any): TypedAppSyncError | undefined { + if (error instanceof appsyncErrors.AppSyncError) { + return { errorType: error.errorType, message: error.message, errorInfo: error.errorInfo }; + } else if (error instanceof errors.HttpError) { + return { errorType: httpStatusToErrorType(error.statusCode), message: error.message, errorInfo: undefined }; + } + return undefined; +} + +/** + * Builds the direct-Lambda error envelope AppSync surfaces as a typed GraphQL + * error. __appSyncError is a reserved sentinel key the resolver response handler + * keys on to recognise this envelope. Branching on the sentinel (rather than on + * a bare errorMessage) means a legitimate resolver result that merely contains + * an errorMessage field is not misclassified as a typed error. + */ +function buildErrorEnvelope(typed: TypedAppSyncError): R { + return { + __appSyncError: true, + data: null, + errorType: typed.errorType, + errorMessage: typed.message, + errorInfo: typed.errorInfo, + } as unknown as R; +} + /** * Creates an AppSync resolver handler from the given AppSyncHandler. * Returns a function that can be used as an AppSync resolver. @@ -313,28 +365,7 @@ export const createAppSyncHandler = // invoke the handler function with the context return await handler(ctx); } catch (error: any) { - // Normalise the two recognised typed-error shapes to a common - // { errorType, message, errorInfo } so the gate, envelope and rethrow - // below are a single code path. - // - // - appsyncErrors.AppSyncError carries its own GraphQL errorType and an - // optional errorInfo, which flow through unchanged. - // - errors.HttpError (the HTTP base thrown by the shared JwtAuthorizer - // via assertAuthenticated/assertGroup/assertAdmin, and by resolver - // code directly) has no GraphQL errorType, so map its statusCode with - // httpStatusToErrorType. HttpError carries no errorInfo, so errorInfo - // is undefined. - // - // The two instanceof checks are disjoint: HttpError and AppSyncError - // share no ancestry, so an appsync.ForbiddenError takes the AppSyncError - // branch and an errors.ForbiddenError the HttpError branch. Anything that - // is neither (plain Error, Boom) leaves typed undefined and rethrows. - let typed: { errorType: string; message: string; errorInfo?: Record } | undefined; - if (error instanceof appsyncErrors.AppSyncError) { - typed = { errorType: error.errorType, message: error.message, errorInfo: error.errorInfo }; - } else if (error instanceof errors.HttpError) { - typed = { errorType: httpStatusToErrorType(error.statusCode), message: error.message, errorInfo: undefined }; - } + const typed = toTypedError(error); if (typed && process.env.APPSYNC_TYPED_ERRORS === 'true') { // The envelope-return is gated on APPSYNC_TYPED_ERRORS === 'true'. @@ -352,20 +383,88 @@ export const createAppSyncHandler = // exact string 'true' means rethrow. This is why a default UNIT // resolver whose handler hits an auth assertion (an HttpError) still // fails loudly unless typedErrors is active. - // - // __appSyncError is a reserved sentinel key the resolver response - // handler keys on to recognise this envelope. Branching on the sentinel - // (rather than on a bare errorMessage) means a legitimate resolver result - // that merely contains an errorMessage field is not misclassified as a - // typed error. ctx.logger.warn(typed.message, { errorType: typed.errorType, errorInfo: typed.errorInfo }); - return { - __appSyncError: true, - data: null, - errorType: typed.errorType, - errorMessage: typed.message, - errorInfo: typed.errorInfo, - } as unknown as R; + return buildErrorEnvelope(typed); + } + // log the error and throw it again + ctx.logger.error(error); + throw error; + } + }; + }; + +/** + * The context for a batched AppSync resolver function. + * Extends AppSyncHandlerContext so handler code that reads ctx.event.arguments / + * ctx.event.identity keeps working against the first event, and additionally + * exposes the full batch of events so the handler can map over it. + */ +export interface AppSyncBatchHandlerContext | null = Record | null> extends AppSyncHandlerContext { + event: AWSLambda.AppSyncResolverEvent; + events: AWSLambda.AppSyncResolverEvent[]; +} + +/** + * A function that handles an AppSync BatchInvoke event. + * Receives the batch context and returns the responses in source order. + */ +export type AppSyncBatchHandler | null = Record | null> = ( + context: AppSyncBatchHandlerContext, +) => Promise; + +/** + * Creates a batched AppSync resolver handler from the given AppSyncBatchHandler. + * Returns a function that can be used as an AppSync BatchInvoke resolver: array + * in, array out, order preserved. + * + * It mirrors createAppSyncHandler's context and auth plumbing but operates on an + * array of events. Authentication runs once per batch off the first event, and + * the context carries both the first event (ctx.event = events[0]) and the full + * ctx.events array. The handler returns R[] in the same order as the input events. + * + * @param handler The AppSyncBatchHandler to use as the resolver. + */ +export const createAppSyncBatchHandler = + | null = Record | null>( + handler: AppSyncBatchHandler, + ): AWSLambda.AppSyncBatchResolverHandler => { + return async (events, context) => { + // BatchInvoke always delivers a non-empty array; authenticate and build + // the context off the first event, exactly like createAppSyncHandler. + const first = events[0]; + const ctx: AppSyncBatchHandlerContext = { + event: first, + events, + lambdaContext: context, + logger: logger as unknown as logger.LambdaLog, + cognitoAuth: new AppSyncCognitoAuthorizer(first), + }; + + // set the meta information in logger + ctx.logger.options.meta.requestId = context.awsRequestId; + // set the debug flag for logger + ctx.logger.options.debug = process.env.DEBUG === 'true'; + + // log the received batch of events + ctx.logger.debug(JSON.stringify(events)); + + try { + // authenticate once per batch off the first event + await ctx.cognitoAuth.authenticate(); + // invoke the handler function with the batch context; it returns R[] in source order + return await handler(ctx); + } catch (error: any) { + const typed = toTypedError(error); + + if (typed && process.env.APPSYNC_TYPED_ERRORS === 'true') { + // Same gate and envelope as createAppSyncHandler. AppSync expects one + // result per event in a BatchInvoke response, so a batch-level typed + // error returns an array of envelopes aligned to the batch length; + // each item surfaces the same typed error. Returning (not throwing) + // keeps the invocation off the Lambda Errors metric. Without the flag + // the typed error falls through to the rethrow below. + ctx.logger.warn(typed.message, { errorType: typed.errorType, errorInfo: typed.errorInfo }); + return events.map(() => buildErrorEnvelope(typed)); } // log the error and throw it again ctx.logger.error(error); From 0b0a9a4779389d3bb16e1131a4b74ceabba3d8b6 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Tue, 20 Oct 2026 07:32:34 +0000 Subject: [PATCH 2/4] feat: add maxBatchSize option to GraphQlApi.addLambdaResolver for AppSync BatchInvoke --- src/constructs/graphql.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/constructs/graphql.ts b/src/constructs/graphql.ts index a08e0c2..fd4204b 100644 --- a/src/constructs/graphql.ts +++ b/src/constructs/graphql.ts @@ -50,6 +50,25 @@ export interface GraphQlApiProps extends BaseApiProps { typedErrors?: boolean; } +export interface LambdaResolverOptions { + /** + * If set, addLambdaResolver wires an AppSync BatchInvoke resolver instead of a + * single invoke. The generated UNIT resolver gets MaxBatchSize set to this + * value, a BatchInvoke request template + * (MappingTemplate.lambdaRequest('$utils.toJson($context)', 'BatchInvoke')) and + * a MappingTemplate.lambdaResult() response template, so AppSync groups up to + * this many field invocations into one Lambda call. Pair this with + * createAppSyncBatchHandler, whose handler receives the batched events. + * + * This option applies only to the default direct-Lambda UNIT path. It is not + * compatible with `typedErrors` (the APPSYNC_JS pipeline resolver), which does + * not use these BatchInvoke mapping templates. + * + * @default - undefined (single invoke, the pre-batch behaviour; no MaxBatchSize and no mapping templates) + */ + readonly maxBatchSize?: number; +} + export interface VtlResolverOptions { /** * The datasource the VTL resolver targets @@ -307,7 +326,15 @@ export class GraphQlApi extends BaseApi { public addLambdaResolver>( typeName: TYPE, fieldName: keyof FIELDTYPE, + options?: LambdaResolverOptions, ): LambdaFunction { + // BatchInvoke mapping templates belong to the direct-Lambda UNIT resolver and + // have no equivalent on the APPSYNC_JS pipeline resolver built for typedErrors, + // so the two cannot be combined. Fail fast rather than silently ignore one. + if (options?.maxBatchSize !== undefined && this.props.typedErrors) { + throw new Error('maxBatchSize is not supported together with typedErrors'); + } + const operationId = `${typeName as string}.${fieldName as String}`; const description = `Type ${typeName as string} Field ${fieldName as String} Resolver`; @@ -359,11 +386,23 @@ export class GraphQlApi extends BaseApi { // source set directly. No response handler, so the error envelope from // createAppSyncHandler would be surfaced as field data rather than a // GraphQL error. Opt in via `typedErrors: true` to get the pipeline below. + // + // When maxBatchSize is set, this stays a UNIT resolver but gets the + // BatchInvoke mapping templates and MaxBatchSize, so AppSync batches field + // invocations into a single Lambda call handled by createAppSyncBatchHandler. + // The L2 BaseResolverProps.maxBatchSize sets MaxBatchSize directly, so no + // escape hatch is needed. Leaving the option unset keeps the exact prior + // single-invoke shape (no MaxBatchSize, no mapping templates). new aws_appsync.Resolver(scope, `Resolver${operationId}`, { api: this.api, typeName: typeName as string, fieldName: fieldName as string, dataSource, + ...options?.maxBatchSize !== undefined && { + maxBatchSize: options.maxBatchSize, + requestMappingTemplate: aws_appsync.MappingTemplate.lambdaRequest('$utils.toJson($context)', 'BatchInvoke'), + responseMappingTemplate: aws_appsync.MappingTemplate.lambdaResult(), + }, }); return fn; } From ce3a6c4be219b46f4d1056d28eb00a4e2ce011dc Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Tue, 20 Oct 2026 07:36:48 +0000 Subject: [PATCH 3/4] test: cover createAppSyncBatchHandler and maxBatchSize resolver wiring --- docs/constructs/assets/hierarchy.js | 2 +- docs/constructs/assets/navigation.js | 2 +- docs/constructs/assets/search.js | 2 +- docs/constructs/classes/GraphQlApi.html | 7 +- docs/constructs/classes/McpAuth.html | 2 +- docs/constructs/classes/McpCognitoAuth.html | 2 +- docs/constructs/classes/RestApi.html | 2 +- .../classes/SingleTableDatastore.html | 2 +- docs/constructs/classes/Workflow.html | 2 +- docs/constructs/hierarchy.html | 10 +- .../interfaces/LambdaResolverOptions.html | 68 ++++++ docs/constructs/interfaces/RestApiProps.html | 2 +- docs/constructs/modules.html | 4 +- docs/lambda/assets/hierarchy.js | 2 +- docs/lambda/assets/navigation.js | 2 +- docs/lambda/assets/search.js | 2 +- .../classes/errors.BadRequestError.html | 2 +- .../lambda/classes/errors.ForbiddenError.html | 2 +- docs/lambda/classes/errors.HttpError.html | 2 +- docs/lambda/classes/errors.NotFoundError.html | 2 +- .../classes/errors.UnauthenticatedError.html | 2 +- .../classes/errors.appsync.AppSyncError.html | 2 +- .../classes/errors.appsync.ConflictError.html | 2 +- .../errors.appsync.ForbiddenError.html | 2 +- .../classes/errors.appsync.NotFoundError.html | 2 +- .../errors.appsync.ValidationError.html | 2 +- .../api.createAppSyncBatchHandler.html | 57 +++++ docs/lambda/hierarchy.html | 24 ++- .../api.AppSyncBatchHandlerContext.html | 89 ++++++++ .../interfaces/api.AppSyncHandlerContext.html | 6 + docs/lambda/modules/api.html | 12 +- .../lambda/types/api.AppSyncBatchHandler.html | 53 +++++ test/constructs/graphql.test.ts | 69 ++++++ test/lambda/appsync-batch-handler.test.ts | 196 ++++++++++++++++++ 34 files changed, 591 insertions(+), 48 deletions(-) create mode 100644 docs/constructs/interfaces/LambdaResolverOptions.html create mode 100644 docs/lambda/functions/api.createAppSyncBatchHandler.html create mode 100644 docs/lambda/interfaces/api.AppSyncBatchHandlerContext.html create mode 100644 docs/lambda/types/api.AppSyncBatchHandler.html create mode 100644 test/lambda/appsync-batch-handler.test.ts diff --git a/docs/constructs/assets/hierarchy.js b/docs/constructs/assets/hierarchy.js index 6bb347a..6691c6e 100644 --- a/docs/constructs/assets/hierarchy.js +++ b/docs/constructs/assets/hierarchy.js @@ -1 +1 @@ -window.hierarchyData = "eJyVkztvwyAUhf/LnUnjQrANW9JKVbe+tigDtUmMSiACMkX+7xVp2tLG8mNhgHPvOR9cTuCsDR74umSoIAhjVJByg8DJrZZVUNZ44CfAOK5G7CVweLyzO6OCXR5DI01QlYg6QPChTA0c0xzB0WngoEyQbisq6efdRTdN2GtAUGnhPXAIvp7FLrOfynjYKF07aYCvF2zTIliwJE1/mFtcfoc5e0g/nxbka6NFUJDEdCW8XB7Uk7MHPwCeSqfiEspQntHIXLJr+z7Ki2SA668by1BOcHQjNLV7cOLQPOuRwP/UI5ijIcs6DfsQf1XDr5dnNOn/In0YSZNKx6HkBF9b9XFcJGNGsEy/4asyOy3fxLuW9yIIH6yTQ7+wq2bqVBb0PCMFTTl7w3RATwpyuYG2/QTETYzO" \ No newline at end of file +window.hierarchyData = "eJyVkztvwyAUhf/LnUmTYGMbtrSVqm59bVEGapMYlYAFZIr83ytSt6WN5cfCAOfecz64nMEa4x2wbUFRniCMUZ6udwis2CtRemm0A3YGjMOq+VEAg8c7c9DSm83J10J7WfKgAwQfUlfAMMkQnKwCBlJ7Yfe8FG7ZX3RT+6MCBKXizgED76pF6LL4qQyHtVSVFRrYNqW7FkFKozTDYda4+A5z8RBuOS/I10aLIE8i01vuxKaRT9Y0bgQ8ls7FTQhF2aoIzAW9th+i7CQjXH/dKEZZQoJbQmK7B8ub+llNBP6nnsAcDCnuNRxC/FWNv162KqL+L8L5iTSxdBpKlpBrqyGOTjJhBNN1/A1fpT4o8cbflbjnnjtvrBj7hX01c6cyJ5cZyUnMORimB3pWkO4G2vYTyU+M2A==" \ No newline at end of file diff --git a/docs/constructs/assets/navigation.js b/docs/constructs/assets/navigation.js index 5233b22..0f9b791 100644 --- a/docs/constructs/assets/navigation.js +++ b/docs/constructs/assets/navigation.js @@ -1 +1 @@ -window.navigationData = "eJyNlU1PAjEQQP9Lz0SFBD+4IUYi0ahI9GA8lKXsNpR20w6iMfx3o9mFLp3O9jxvXqftTPv+w0B8ARuwoXMCRgvNOqzkULAByxR3TrjTOnJSwFqxDltJvWCDbu9y19lnX3MnhqUMk6sAlTsyuZZghhsohAaZcZAGKQPFKO/Y8rJ4VmhZhxhluOfr+YLfbnSGl9SMU6aHrPwrPFRUgZZcb++owotTpqlwgB5HFaByX6TOlZjxuRI3HLgDY0UowijK+mbsaqnMNjTVESq77swna0p3UEgNwi555vXuP9FU9frnYQPHTT5AidA2jWvjOLXIoX3j5iOG0t21jKBnxVFSPtlCijjASCndjr63vSWb6ombCmfUp7CP5V8Z+OkGFKVsvhPxK0O4du3M8kzqnCoWAylx9ShVV02ZUTJBXe+RtO6hBOFYaGFlllBrk0xQJzjTZfHL94EWkTeDpO+Yo7TVFxD3+QAlwuYtbo3SqUsIJ3P8RQkoSvkKKmXsQ4yS1n9YfPsNon3Wg9Lgu9zPN17Q2dVFt9/bffwCDG1v9A==" \ No newline at end of file +window.navigationData = "eJyNld9PwjAQx/+XPhMVEvzBG2IkEo2KRB+MD2WUrWG0S3uIxvC/G80GHb1e+3yf++y77q57/2EgvoAN2NBaAaOFYh1WcSjYgGUlt1bY06ZyUsC6ZB22kmrBBt3e5a6z777mVgwr6TfXBap3pHMlQQ83UAgFMuMgNRIDxSjv2PCqeC7RWIcaZbjn6/mC325Uhkdq1ynTQ1b9BfcVdSHS67w7qnDqlGkqLKDHUReo3hep8lLM+LwUNxy4BW2EL8IoyvqmzWpZ6q1vaipUdzOZT0ZX9qCQCoRZ8syZ3X+irer1z/0BDptcgBKhYxrWhnHqIYfxDZuPGEp3F1lBx4qjpHyyhRSxh5FSehxdb3wk2+qJnQqry09hHqu/GPjpehSlbN8T4U+GcHFtSlqUjKtnhmdS5XFzG6TE9X1XTxFlRskEdXN8pHUPJQjHQgkjs4SsbTJBneBMl4XnygUiIme9Sd8xR2nrv0vY5wKUCFvlsDVIpz5CWJnjl5VHUcpXKFN21McoafN7DL9+i4jvuhcNvqv9fuOBzq4uuv3e7uMXTxiQWw==" \ No newline at end of file diff --git a/docs/constructs/assets/search.js b/docs/constructs/assets/search.js index 0c1556c..2f7d02e 100644 --- a/docs/constructs/assets/search.js +++ b/docs/constructs/assets/search.js @@ -1 +1 @@ -window.searchData = "eJy1XW2T3LaR/itXs183umnwXd8U2c45cRLHspyqU7muqCF2l9IMyZCclTcq//crgORMA2iQzZnJJ6l22C8AHjQa/QDk101bf+k2rz983Xwuq2LzGkR6v6nyg9y83rzpOtm/LarN/ebY7jevN7t93nWy++/ph1dP/WG/uZ/+vnm92fx+P2mKQJw07eqq69vjrq/beWV35pNI8f2myVtZ9divszHYivBkTcn0fzzuPst+wZp+8uP05BXWvqkPeVn9Tf2FYbHQT1fD05dZ/XddLZkaH+HrR0P/x7yTb5rSsTD+/SYDj3Wxxn1yyjcQTTkzDIa1vCk9Y9B3xR/K7g9NW/dy18tinf3v/vHN3xYtP/xLT6lb2fyfuutZLX6qu/6W7VX6ZPG/FBAN08NzDhpXWkbgfFs/VmVfvzn2T7Lqy13el7UbpMinbgJcv2YWjGn3PZ28O3Z9ffir7Lr8UX53rHYr23o3KDgMCh7OCm7gW1moZ/qXH+t6v8alSa4Z5G7gSdPKd+Vj9b65pIeaVnblY3Vsbts7TSt/rj/L6k+ykq1+8kLneqXl8aTltl4eO9k2K8cPyVzmgdiG59mcF8X7TrYKRG/3pVKwwpW8KCZvdpPwhb2CAsyf2rx5+seeWgDPP90klFjqWPEDeedfFRYtDc9cYWFmrSVsMZfb/xr/UFZPsi2tVYHlFrkEEw4trsLXu+JdmQl3WIvzlS7NLNi2R8w1+0qH+vzjXn6T9/m7+tjulr3Szxd5n3fT8yvwawedb16q/FB/8/HP736SXb1/lsuzMy+KQksVHz917VnqBl780u8vceO539/Gj3W9cKvW/5AfPhb5Gst7LXEb6yv7/DZ9/Sj7KQ/4rm7/3oyr+qIDj7KfVv6Huq2R3MWetHnVv9ntZNctW1fP5tOzN7Co1uE1VvPh+RtYfl+ttX2s1lvH6cSAcm/2Z/58k7SCUMlKLSxP5zrzG7mXvdRlBJZ5LVRooXwUutqHn2ReaA9cAHtdaGVe5JPM1R68k1Xx7SEv9ys86GRVyEnmag9+VmviP9Xiy/dAr6NfRpmrPXjf7Ov143DUUrcbiffjNkBhYoUXo1Q7SN3Ui5WDgl25xdh0csAGy4NODqC4gc3yIOsjLyYoq6fHr7Q77SG5hhc3sV7LOLT/ddeQq8n495sEc6yLFcUnpzxZ95RGuJPVsIQfu8jOYdd8Wz3/krcLhg67RlbPz8NzbEvmIKDdPWUM/XyrIbFVckcGe+rvOG5DVOfNJic8i8f5AgxlmFl28dtHA/iT7Hqq2DL+/SZDhnWxxmpyakWNxbAxV2BZ1j1TXbGt3KK0suwQWVexXbmuqLLshLeiYjtyfTll2Zl3jdwtOtIND10Eg5l6jWHmFsUa1xV7t/ymqquXQ33sTntWN66brS+KfJKpsQy3M2wP3moqQz2ntu1kxch2YGA/Wtn1rVwoGi3bX2X5FjZ3e5lXx2YZaOODDtjmOLYF29w6heHH2iIF34cFrCHDFyPsUfa+dM62tbTqLlg61EX58HLqU2YbB6lTp17d3ibvd0/v5O7Ylv3LvGn9aHd+9CYIM+y/2z3Jg1zoAcON7iRxE2862a+OcJ3sr4twOAl6V1aPe/nzVBLv+rp1owz10E3SI69iVq5E+j5X9F9hf35fumDZGOKHtj7QW2K/dSVzlQd4iP9Zt58f9vUXx/70w02G0lDGGr6TX54h0/WJH9uy2pVN7u6wTYP64QY9fJnNtibGybQ0PnKZ/i/j729ad00zzUxP5q1/ISOsiSh2TtL92NbNOaaUVS/bh3yHTm7pB2ZRYDSicHcJs0rvVpw8G3ydSU1XmCXTcY5R3InjoSBvH+Lf+V2YF0Wpwna+/7Z6Zqm9O4tILUI3yXB3fktTeXqSMO3Z2lxg2Dd8pNHrDdpnSectqqd3hX++sUzS58PmDdsyl5ufn5uuacbU5JglNo2zZj2bx7VmB172742ZNc1aHkTqZj5p4hv/uc13ZfW4xnh/Ernc+KFW1a2Wbdl4/nKz3Vy6OOvAIHk+SzFKXuFKnz9KPtD145fgHC8G5HEu79Lgf5q/UDy29fEi7XcnSbq1M01Zc/ByhUOXH8Ccx4HsNBeqSFF0pGKFY53sBmZ00nAr1/q2fHyU7UXDh2Rv5c5Ub/dNmiWXpuL77Cy63K1L59HJr2ZUcIVjeLKfT1h4PbMeuWH+R2meSQF5J+KuyQpph+Zr3le648Opz5X/iBszGSTtB5VE3sKRxbySdsefWt7AqUI+lJWG5Hfl3rsYk46dRR/K/fzCzHVmNvWlnZglsa7pmfmEmHRmnlC5xpnFNJn0x58p38ylmeR5xiUqf76BS/MpNemPJ6u+gTPcRJt0azHXvoWDc+k37RWZgd/Alf6lkcW3bVvTmRbpjJaRk8xVcefYyb9JNXXf9fnuM9+FYycrLddNcmvdwNnK9/P305Az9JP83MW5vMPTvXyBx9MET3v//KVntNV5akWOdixKWe1IgHv03iEZTxtdt33bra47yvZ9S/ezx/4gdGxnOpntwKcvn7uV5pXIxcaN0Z3lpbAnq7mpGVqIpXeBGqI9p1v55266CTCzNjsP8VvXlI3cl5V818tGheoV+u8m2a6XTZXblCdusdsK/5LRPf2S74+rHNFSz5PUehdwh5unK73bOuKxG27tfNqZFX6qDXObF+uVBCxHGC8oWOsGcbSM7QqjSL3CnUJ2u7ZsfEuG1xVT7mo3ZNW3L6scmCSuNv2UV8WerpV5jZ9lrjZfVrv9sZDvvvnLKg9Gsa74fAsnPn1ZNyeG5682u7gV8zrAIy5WuzKzBVtwZYnGWOHK7GbC68ZySX+FC95MwGt+zdWFZdP63sY6RJwv1NwED0f7LgPHh5UXGuZ2MD9gPC7OEPM5/vL820/5yyLmCeV3v7X5Cw/yVhPo1o6H/sYtz0xzyQcvSUfe5vv9x3z3+X27X2kIZSa7UcmxnblIRrdtprC51p2FuwfrjA+ZyQUuFJPgDRwZLjS8nY5NfV+s9GeQPx27Kv33ypbcIkDqHhV1nTo9s2aD3T/VbflvvRF8J9tnOiUhDdwZwt0kPNvkczMWHCLXgXk3/AsB1/jp+Khz2HvRiZPo4tFvrjOtfCy7fs14IInrTOsXz/DtTo+vNkrgXL80p9wtB2PzwfWIl99WRVOXFZmC+m2c0SbP8rMNt1o0G3xmYw7lzSC2HGp4TuiBvLBftOzVfUIgYhkK6zGw39df1Cwvylbu+vdtyVV/N4q2o+hxEJ1t7NLID6sA14Hz49cYfRwGgGv0/Pg1Rhc3XpRp3p6L54AK0vWu3v8i226ND5Pc81nuGje6Xd3Q+wzK+Onpq0zq1fn76oGNs0GiHCSuMt23ZfNj3uYH2XsO4pD2lViDxVY6QYQSb7UR/37DMqOjlllfNNy9SQzDLqyPYEunRBbTd/OQCC9pXzyKwV3FraMYqxbvWScYazayzV2qr6tduXZXBdBZ4yvCJ7K/NnjOV6mWQieuTbEC58LBRkbYNI4xcoPm5bU4wupiEY5nlBunDdOrovR8DY6ZfeLy25qkc67wZb6tYG6dsB/7D5WBSDMXFIGcVl2xli35tLCksV1hr2weh9YtcGy3FtY5jzOc5Y7tgr9O57HOf0PIouFLGs+q0HGd4NbnaF9WVee4LnGWY9od9qrMdYW5ONPerFmjuQ7NLtW0G8srNtv40sLtcYC1frOdWFjGPT5wVvMZF/CyNl48965n+Pcb7nsctRcemzfc9zkzc3d/3qX1F/ivObxPOHDByX2mIz7QkU7c3oGZA/uEB2tP67NcWDyWSDhy2Tl9rjtnmsUbGGmfzhTLfHi80JE/vrjveFnr08cX9itfltyrx9fmy5/qo4eNJ12qxxfly3aSu9yNnecYs2t4N3d4mWOKd33DNbzi7gbLjdmjYYT59bc2OG7MX9lw3bjgvgbHjU/DEVldKWp/fmmY3nz60p8KTG0/iF0+JIu5pevAZddFVjuzOmIYft0qViyenfL5sfbuCscZ+82Ns24wXyI1b3D2pgxhc/01GY4b7doE805J5E05f1GUY5p7Pcd14aq7OSzX5hJ/wp/Vt3I8TuDUnzoD7x0m78P8TUGhv6WzXvndSZCGgr8d3kO9u/bFe7R4wRlD+OYO/UWSp43ZPn2W/rPHHLd8+GANnX5oxVsT9vXHfP99Vcjf6CyO1n03yJUnueXWDt77Vol6d4kXWuxmTjRtecjbF8bwIxdGIe6YzzqgXrb9c/1D+Szf9H1bfjz2ZGjyeKKk+3pfPsscSa93CcMPfYJjJsVxn1oRkNzPyyxrvmN8ZIZw3ePCc96WqifWtO0OC13gAO7k6eVk3rhvPLAm1nM2La7yNbsW0/dFR94dP3Z92R+9UJr1prOkr3BpXz8+ltXjW9XEx6M/Syb8GUV3lugVznR93su/5runspK+fQzhiBY7DGKz+xiWE2O+fUmPjKLX9YjYZglE51co/kBurFQ7p7PnM7Hm1/uNXhY2r79uxmr55vVGvApeZZv7zUMp94X6qu5mwrb6AqOmJot6d9T//XV87BepXs2oHh6e/u/t5v7D9j7IXmUB/Prr/YdJWP+g/zDpOP9FC8Lm/gPcB/AqjFJDEBxBMATF5v6DuA+jV+k2MwSFIygMwWBz/yGgBANHMDAEw839h/A+2r7KIDEEQ0cwNASjzf2HiBKMHMHIEIx9nRM7grEhmGzuP8T3oXgVhqEhmDiCiSGYbu4/JJRg6gimhmC2uf+Q3gfxKxGZgpkjmJkAUHjIKElwsQMWeDR6tlTXAoEfE0AgvNBzIQQmhkAhA4A07MIITByBQgeQ2AUXSmBiCRRCICAtu3ACE0+gAUViGFxIgYkpUEiBiAIHuLACE1eg0AIxadmFFpjYAoUYSEhhF15g4ktsfcMsXHwJE19C4yslo4yLL2EFKOGbhIIIUSa+ROCbh8KFlzDhJULfVBQuuoSJLhH5JqNwwSVMcAkNrowaJeGCS5jgEgougpzJwgWXMMElFFwEORuFCy5hgksouAhBCrvgEia4AoUXQc7GwEVXYKIrUHgR5GwMXHQFJroCvf5F5DrmwiuwlkCFGEHOxoBYBU18BQoygpyNgQuwwARYoDAjUlLYRVhgIixQmBEkwgIXYYGJsCDxxYHABVhgAixQkAlIdAYuwAITYIGCTECiM3ABFpgACxVkAhKdoQuw0ARYqCATkOgMXYCFJsBCBZmAzndcgIUmwEKdZJE5T+gCLLTyLAWZgERnSKRaJsBCBZmARGfoAiw0ARYqyAQkOkMXYKEJsFBhJiDRGboIC02EhQoz4ZaK+KGLsNBEWJj5oB26AAtNgEUKMiGJzsgFWGQCLFKQCUl0Ri7AIhNgkYJMSKIzcgEWmQCLAl+TIxdfkYmvKPT2deTiK7JyeYWYkJwWEZHOm/iKFGJCeivg4isy8RV5A1jkwisy4RWl3kQmcuEVmfCKMl8iE7nwikx4xVtfIhO76IpNdMXgS2RiF1yxCa5Yg4uMIbELrtgEVxz4sqDYBVdsgivW4CLjT+yCKzbBFWtwkfEndsEVW5tFDS4y/sTEftEEV6zwEpErXOyiKzbRFafeXCR20RWb6IoVYCIyAMUuvGITXolCTEQGoMTFV2LiK1GQicgAlLgAS0yAJQoyERkHEhdgiQmwRGEmIuNA4iIsMRGWKMxEJLQTF2GJibBE1yISqlKTuAhLTIQl3npE4gIssSoSGmApWZMgihImwBIFmYiEduICLDEBlujwRUI7cQGWmABLvbvH1MVXauIrVYiJSWSnLr5SE1+p3j2SyE5dfKUmvlKFmJhEduriKzXxlSrExCSyUxdfqYmvVCEmjih8pS6+UhNfqYJMTCI7dQGWmgBLdc2LDLypC7DUKnspyMTkWpMSlS8TYKm39pW6+EpNfGUKMnFGTYvMBVhmAiwDX9U1c/GVmfjKFGISMgnKXHxlJr6ywDvKmYuvzMRXFvpW18yFV2bCK1OASeA+CF5FiWXYhVdmwitTgEkE6bULr8yEV6YAkwRkf7nwykx4Zak36mYuvDKrsqoQk4RkhxHFVbu6qhMwcqkZfjPF0d9GeQWbhC7cbYka69Yqsm6Fv9i5JeqsW6vQutWVMLr2tyVKrVur1rpVAErIlGr4zZa3yq1bjThy6Rl+s+WtiutWwSilS9Rboua6tYquW4WklK40b4my69aqu279kW34zZa3Sq9bb3AbfrLFLfiBP74BVd93CvzeEAdkhd9CH/ijHFBVfrvMryv3qaDlCfTZlX5dvKcDJVC1frvYD956LFDVfrvcryv4dLgEquBvV/x1EZ+OmEDV/O2iv67j00ETqLK/XffXpXw6bgJV+bdK/6DL+XToBKL6D1b5H3RFP6XJFoIAAIsBAF3VT2m+hSABwGIBQFf2Uzp4E0QAWEwA6Oq+h7IhyACw2ADQFf6UDv4EIQAWIwC6yJ/SwZvgBMAiBUDX+VM6eBO0AFi8AOhSf0oHb4IZAIsaAF3tz+jgTZADYLEDoAv+9KYHCH4ALIIAdM0/89CMBP4sjgB02T8jNxFAsARg0QSgK/8ZjX+CKACLKQBd/M9o/BNcAVhkAej6v2fxIugCsPgCGAgDMvcgCAOwGAPQLAB5VAAIzgAs0gA0D5DRs5egDcDiDUBTARk9+wjmACzqADQbkNGzjyAPwGIPQBMCGT37CP4ALAIBNCeQ0bOPoBDA4hBA0wKe1YtgEcCiEUAzA57ViyASwGISQJMDntWD4BLAIhNA8wOe2U/QCWDxCaApAtjS4YegFMDiFEDTBJ7wT7AKYNEKMPAKWzr+ENQCWNwCaLoAtnQAIugFsPgFiIYDHp7jDgQGLY4BNG0A25CcxATNABbPAAPRsCWLm0BwDWCRDaD5A9jGtAIChhbhAJpDgG1CK6DOfFg41DwCbOmJTPAOYBEPMDAPW3omE+QDWOwDDPSD56AOQUCAxUCAZhXAc+CGYCHAoiFAUwsANBIJKgIsLgI0vwCegzcEHwEWIQGaYwDf4RsCiRYpAfFw5ohGIkFMgMVMgCYbAGgkEuQEWOwEaMIBgEYiQVCAxVCAJh0AaCQSJAVYLAVo4gGA3k8SRAVYTAVo8gHogyZAkBVgsRWgCQigD5sAQViAxViAJiE8qxrBWYBFWoDmIUDQe1KCtwCLuIBkqMjQuzKCuwCLvADNR4AIaQUEEC0CA5LhAByNZILDAIvEAM1LAH2ABQgeAywiAzQ54UksCS4DLDIDkjkcEnwGWIQGJAMO6alEcBpgkRqQZDPRgOA1wCI2YGA2PNGAIDfAYjdAExYeIBP8BlgEB6RiBsgExwEWyQFpMANkgucAi+iANJwBMsF1gEV2QDqTIRJ0B1h8B2gKwzcRCMoDLM4D0mQmIhO0B1i8B6QDDumITFAfYHEfkGYzCRbBf4BFgMDAgNBTkaBAwOJAIIOZqUjwIGARIZCJmalIcCFgkSGQzaWIBB8CFiEC2VyKSJAiYLEikM2liAQxAhYzAtlMoZDgRsAiR0DzHb65TPAjYBEkkKUzc5ngSMAiSSDLZuYywZOARZSIrb9WKAiiRFhEidgO63JGHhYmmBJhMSVCMx/0RBAEUyIspkRo5gPoU4qCoEqERZWIrf/gMMGUCIspEZr5oEv9gmBKhMWUCM18kKV+QRAlwiJKhCY+6FK/IIgSYRElQhMfQJ/TFARTIiymRGjqA+izmoLgSoTFlQjNfQB9XlMQZImwyBIxXIegz2wKgi4RFl0iNP1B0x2CoEuERZcI8PPCgqBLhEWXCPBSw4JgS4TFlojxbgRZcRMEXyIsvkTM8CWC4EuExZeI4YoEfXRVEISJsAgTMRAmZMlKEISJsAgToQkQ+mijIAgTYd+VGAgTMgoL6rqEfV9CzAVB6sqEc2dCeBkfQV6bsBA4XJygozh1dcK+OzEwJvT5YUHdn7AvUIgBg2RCJahLFPYtiuEaBX2OWFAXKeybFJoEgZBeB6jLFPZtCs2CAH0mWFAXKuwbFZoGAfpcsKAuVVi8idA8CNBngwVBnAiLOBGaCAH6mK4gmBNhMSdCMyFAH9UVBHUiLOpEaCoE6BOoguBOhMWdCM2FAH2SVBDkibDIE6HJEKBPkwqCPREWeyKC4bYYjUSCPxEWfyI0IeJZTggCRVgEigiG+jWNZIJBERaDIjQjAvThUkFQKMKiUISmRIA+YCoIDkVYHIrQnAjQh0wFQaIIi0QRmhTxLEkEiTL9TV/1fZZtL4vvhyu/Hz6odx+SLw78uvm/8V5wmEw3jb9u4nDz+uvvv5/vAb/++ju6Cqx+U6bzotgdu74+qJfjnD8gg3SmSGfE1Vm8VPmhLj5+6trxdQFYp9iedYpkrc7nfk8qBaQ05Sol32l8Vqq2xietil3QitQueJ1+/erJs9oYdWqajkq303+CaPxPEo//ybbDf9SugWnY0/MCdRK7EcPLq0h9wVlfsOXq82ItQ1iLudqmj9ANr9k1Ri9Gg8eGhA9fCAkB8JRRr4NG7gFqr6p1j6M+/SdjWpkPCmpPeR7zbcDT2ZSGDoRXAYNMxOzQ81uokT6BIpUqjo4NZqJ7Uqk14Gl1Vjr2oRhnbMTEelM+/KswXEWOjm6KYPg3ZuIdv2v1rBf1aDbqDUe9Ywen2RQYpge2zJg+vVgV9QxC2hRM1Gm9US8zrjdl18idMWNRl8fM0cOfF0bTCyFsioPMEHB6i+tZG4L82MgMplbD1Op16sfXxqE+RQEhZY6M/T3jszYcSKf2M5fG01fnEbrw/BLMhuoXA+K1L8Jr3+RVxowg6AXsWCcOyVE46WR2n/WaXDRL0YBnE35ATCPN7Ujijbc4kOIMY8sMfwtvrMXq8RTYMgMW9R1E3N14hYnWdYM016ot1rSu8eibN1gjhlc8wSthN9x57S7uywAvWLxA/THvpLXuIQ95IXLU4cYKvIzwhmG3l3l1bOyIG6FxiLmanO8PYIjgUBFNqWfGa+/5y0J4YPEcj6c5nvJG4fSROZwtYOzFPOyNevwhwwA0L9MidTpjnWBcb7mtHgenNucv0jQmB2NvnkLb8G8w5jvhmCpM0Toa/z7tJNiD0FrzKcTzidlbel95kF2XP8qH8RucJlBQ83irCn5HHlKEN76qksJSRbwQDi2gAqNj7E1x6nbm0ux9zRvyHU9oVQLh6e12bdnYHZph5PFV6Tdw4sHG2b7gBQJPQo4cSsfQkk2RHsKpV3ljj1+Qir3NsLe8VMd6rylGEh4NZm1CVn1raMlwAAx4C9pDWx/Gb8qjYI/alvDG8+H8tWaUpqOZFvFCwOm7mzgO4wkb89r1KPvJpYe6JTMfgVASjDOMGeWRdqPBEfKU7+jp7dhIEYoDCbPnzNfIYnDhDI9Zfnls86rPdzvZGboEgljAmz9Ik91KvB1nhk+k7Vg5+lAECXgBROsr5F72Uu+SjL0RmpTMsKbVNW1Z7com3xsxCUEj5a1kWlkr80J7ZowD3hMwZ7rW1smqkIe8NMuNeAsQMtGmtOnA8UW9q9vQhpLgcEVLj82+JtuKkoCQt9kf9I0FOtWDhj4078MVGMb63EajiRGyodw8/Wtv17xwXswe2VGRkxSmOHQy96OPbX20Uku8sGx5Q/qUV8Xe3BZmuGlMnOHvXSBN2J9xOR/X+WlHl00bAIim9Z43LFTVLEYdkPLW+nJxJ5DitEnwgFgWSlf/onBorpG4a8eWM6v25fgVD4+XeOyZWVlZ7fbHQnbFZ2PQUuwjD0Ul/eUCpBRvp5hrW9l1R9mql+DidmLvmBnYmew4fYEEeWZsM3jN/fTlc2f7hbHOJJg+fTHWsQyrYEZQ4ssuOPlF0BXAA8XA5lAbMoH8YxZfTWVO2MswfcbMLKxPyaDohwYymwoVMO2Iw1MFaapvp1PJN4unyDP9h9n5/q/H4DGI8RjwAGt9Dga1EcWPyW2AiREKT1smHv4MM0SPqtthCJG8WWt+oACnt7joxmQD6ReP4+0xLroyMywij8f7unAkcqJTb65SO64nZHeiHlCX2VZoJbcx6toXUsibkqPCcQNHuQl4VjJpm1ErqQ47yeRtR3VOwAC8sVEXP5jK0CJvDDsakIjdUKSMcBC3NuXNw8OukdXzc25W1fB2kAuWuigfznGI3vuiNidctedPNKFYhBRlUwiCiQUVvAne5P3uqZO7Y1v2RpEkQiBMeENj6Op2T/JgRqAIrYYJL49rykbuy0p2vWyUqLl64WQuYPrY6oLasSErnkZ84OrTnxofyQ406GbsxVOQqfj8kResCFd6mdmS+oav3PXqpANRnDXKvEwi3v0qMJ6BeFiSaZFPJ9ov4y3BrXwsu97iy4z0nUnwj58VM2Y2Jo54w2F+nAy3Ftc3txN5LXgj09ZmQTFG7Ut5gCaIUHzqB5LToagTQ8wLiviLx1g5hnIy6UxPVDEPQJ3shvKKqrMYY5zgzS/zOEwn+4VTNhE+e8LLczvZOwXfADkX8gKEUlMeZH00S2ZolQp5COzkqbJiaEL4C3lTa2mvGKFWJhMtMB0Cgum0i2DikzDmziLcsUIwBwhpdlkSXFMXzP0g+tweAiQCfDad+ZmIPQinjc7En4jtVGhhliq6Pu+envP90Vrc8CRmlpDdr/PgTB1Xt5lVpa5vy6bJ2/wge2lxjpgWgOREI/PA7FIpaEKcTtgFp15eodXDQeIwvUKbLmBaST8upTCnHPmhNIxWXChgpjI67zCXR4yZmLcEaS30ARAcieMTzpkNJr/PhOGId6PMKlfflo+PFg4TnO8zT+CpuVHItrVo9BR3H3MFP3ZSZ6dF1+e7z6Y6vFIyoyUV4Y2DONPUmE4jQsgLbf5TuDijjHhr2qTMCZVGvXmdLrckj6cZ8zAg+kIenlyYMGfyXeiUMbWnxgcrBTM6fxm/QmYkfKiRzKNVk5q8NSZUjMCbrnPIXY7xSW/BLDX+1uYvRMVMvaAI7S94647NY2Bma1nBr/enfePm9Ydff//9/wH+b9zt"; \ No newline at end of file +window.searchData = "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"; \ No newline at end of file diff --git a/docs/constructs/classes/GraphQlApi.html b/docs/constructs/classes/GraphQlApi.html index 045dcb9..a5121cb 100644 --- a/docs/constructs/classes/GraphQlApi.html +++ b/docs/constructs/classes/GraphQlApi.html @@ -27,7 +27,7 @@

Type Parameters

  • RESOLVERS

    The type definition for the GraphQL resolvers.

  • -
    +

    Hierarchy (View Summary)

    diff --git a/docs/constructs/classes/McpAuth.html b/docs/constructs/classes/McpAuth.html index 4658844..02683b6 100644 --- a/docs/constructs/classes/McpAuth.html +++ b/docs/constructs/classes/McpAuth.html @@ -23,7 +23,7 @@
    -
    +

    Hierarchy

    -
    +

    Hierarchy

    • Construct diff --git a/docs/constructs/classes/RestApi.html b/docs/constructs/classes/RestApi.html index e2ee503..7101733 100644 --- a/docs/constructs/classes/RestApi.html +++ b/docs/constructs/classes/RestApi.html @@ -30,7 +30,7 @@

      Type Parameters

    • OPS

      The type definition for the API operations.

    -
    +

    Hierarchy (View Summary)

    • BaseApi diff --git a/docs/constructs/classes/SingleTableDatastore.html b/docs/constructs/classes/SingleTableDatastore.html index f0b8044..63561e1 100644 --- a/docs/constructs/classes/SingleTableDatastore.html +++ b/docs/constructs/classes/SingleTableDatastore.html @@ -21,7 +21,7 @@
    -
    +

    Hierarchy

    -
    +

    Hierarchy

    +
  • RestApiProps
  • +
  • SingleTableDatastore
  • -
    +

    Hierarchy (View Summary)

    • BaseApiProps diff --git a/docs/constructs/modules.html b/docs/constructs/modules.html index f15aaef..222015e 100644 --- a/docs/constructs/modules.html +++ b/docs/constructs/modules.html @@ -12,7 +12,7 @@

      cdk-serverless

      AssetCdn
      BaseApi
      CognitoAuthentication
      GraphQlApi
      LambdaFunction
      McpAuth
      McpCognitoAuth
      RestApi
      SingleTableDatastore
      Workflow
      -
      AssetCdnProps
      BaseApiProps
      CognitoAuthenticationProps
      GraphQlApiProps
      ICognitoAuthentication
      IJwtAuthentication
      ISingleTableDatastore
      JsResolverOptions
      LambdaFunctionProps
      LambdaTracingOptions
      McpAuthCognitoOptions
      McpAuthFunctions
      McpAuthGenericOptions
      McpAuthOptions
      McpAuthProps
      McpCognitoAuthProps
      RestApiProps
      SingleTableDatastoreProps
      SingleTableDesign
      VtlResolverOptions
      WorkflowProps
      +
      AssetCdnProps
      BaseApiProps
      CognitoAuthenticationProps
      GraphQlApiProps
      ICognitoAuthentication
      IJwtAuthentication
      ISingleTableDatastore
      JsResolverOptions
      LambdaFunctionProps
      LambdaResolverOptions
      LambdaTracingOptions
      McpAuthCognitoOptions
      McpAuthFunctions
      McpAuthGenericOptions
      McpAuthOptions
      McpAuthProps
      McpCognitoAuthProps
      RestApiProps
      SingleTableDatastoreProps
      SingleTableDesign
      VtlResolverOptions
      WorkflowProps
      LambdaOptions

      Represents a 400 Bad Request error.

      -
      +

      Hierarchy (View Summary)

      • HttpError diff --git a/docs/lambda/classes/errors.ForbiddenError.html b/docs/lambda/classes/errors.ForbiddenError.html index 2de94bd..cf5b7f2 100644 --- a/docs/lambda/classes/errors.ForbiddenError.html +++ b/docs/lambda/classes/errors.ForbiddenError.html @@ -14,7 +14,7 @@

        Class ForbiddenError

        Represents a 403 Forbidden error.

        -
        +

        Hierarchy (View Summary)

        • HttpError diff --git a/docs/lambda/classes/errors.HttpError.html b/docs/lambda/classes/errors.HttpError.html index d6817e2..b0d7535 100644 --- a/docs/lambda/classes/errors.HttpError.html +++ b/docs/lambda/classes/errors.HttpError.html @@ -14,7 +14,7 @@

          Class HttpError

          A base class for HTTP errors.

          -
          +

          Hierarchy (View Summary)

          • Error diff --git a/docs/lambda/classes/errors.NotFoundError.html b/docs/lambda/classes/errors.NotFoundError.html index a67bb38..53504f8 100644 --- a/docs/lambda/classes/errors.NotFoundError.html +++ b/docs/lambda/classes/errors.NotFoundError.html @@ -14,7 +14,7 @@

            Class NotFoundError

            Represents a 404 Not Found error.

            -
            +

            Hierarchy (View Summary)

            • HttpError diff --git a/docs/lambda/classes/errors.UnauthenticatedError.html b/docs/lambda/classes/errors.UnauthenticatedError.html index be7f165..6fc4e7f 100644 --- a/docs/lambda/classes/errors.UnauthenticatedError.html +++ b/docs/lambda/classes/errors.UnauthenticatedError.html @@ -14,7 +14,7 @@

              Class UnauthenticatedError

              Represents a 401 Unauthenticated error.

              -
              +

              Hierarchy (View Summary)

              • HttpError diff --git a/docs/lambda/classes/errors.appsync.AppSyncError.html b/docs/lambda/classes/errors.appsync.AppSyncError.html index 59da477..6e656f7 100644 --- a/docs/lambda/classes/errors.appsync.AppSyncError.html +++ b/docs/lambda/classes/errors.appsync.AppSyncError.html @@ -18,7 +18,7 @@

                Class AppSyncError

                direct-Lambda error envelope so AppSync surfaces the error as a GraphQL error on a successful Lambda invocation.

              -
              +

              Hierarchy (View Summary)

              • Error diff --git a/docs/lambda/classes/errors.appsync.ConflictError.html b/docs/lambda/classes/errors.appsync.ConflictError.html index 4e696ca..c4afa54 100644 --- a/docs/lambda/classes/errors.appsync.ConflictError.html +++ b/docs/lambda/classes/errors.appsync.ConflictError.html @@ -15,7 +15,7 @@

                Class ConflictError

                Represents a Conflict AppSync error.

                -
                +

                Hierarchy (View Summary)

                • AppSyncError diff --git a/docs/lambda/classes/errors.appsync.ForbiddenError.html b/docs/lambda/classes/errors.appsync.ForbiddenError.html index fc5b7c2..bfa7d7b 100644 --- a/docs/lambda/classes/errors.appsync.ForbiddenError.html +++ b/docs/lambda/classes/errors.appsync.ForbiddenError.html @@ -15,7 +15,7 @@

                  Class ForbiddenError

                  Represents a Forbidden AppSync error.

                  -
                  +

                  Hierarchy (View Summary)

                  • AppSyncError diff --git a/docs/lambda/classes/errors.appsync.NotFoundError.html b/docs/lambda/classes/errors.appsync.NotFoundError.html index 05d257c..a23a7bd 100644 --- a/docs/lambda/classes/errors.appsync.NotFoundError.html +++ b/docs/lambda/classes/errors.appsync.NotFoundError.html @@ -15,7 +15,7 @@

                    Class NotFoundError

                    Represents a NotFound AppSync error.

                    -
                    +

                    Hierarchy (View Summary)

                    • AppSyncError diff --git a/docs/lambda/classes/errors.appsync.ValidationError.html b/docs/lambda/classes/errors.appsync.ValidationError.html index eebde78..5edde84 100644 --- a/docs/lambda/classes/errors.appsync.ValidationError.html +++ b/docs/lambda/classes/errors.appsync.ValidationError.html @@ -15,7 +15,7 @@

                      Class ValidationError

                      Represents a ValidationError AppSync error.

                      -
                      +

                      Hierarchy (View Summary)

                      • AppSyncError diff --git a/docs/lambda/functions/api.createAppSyncBatchHandler.html b/docs/lambda/functions/api.createAppSyncBatchHandler.html new file mode 100644 index 0000000..26340d9 --- /dev/null +++ b/docs/lambda/functions/api.createAppSyncBatchHandler.html @@ -0,0 +1,57 @@ +createAppSyncBatchHandler | cdk-serverless
                        +
                        cdk-serverless + +
                          +
                          +
                          Preparing search index...
                          +
                          +
                          +
                          + +

                          Function createAppSyncBatchHandler

                          +
                          +
                            +
                          • + +
                            +

                            Creates a batched AppSync resolver handler from the given AppSyncBatchHandler. +Returns a function that can be used as an AppSync BatchInvoke resolver: array +in, array out, order preserved.

                            +

                            It mirrors createAppSyncHandler's context and auth plumbing but operates on an +array of events. Authentication runs once per batch off the first event, and +the context carries both the first event (ctx.event = events[0]) and the full +ctx.events array. The handler returns R[] in the same order as the input events.

                            +
                            +
                            +

                            Type Parameters

                            +
                              +
                            • T
                            • +
                            • R
                            • +
                            • S extends Record<string, any> | null = Record<string, any> | null
                            +
                            +

                            Parameters

                            +
                            +

                            Returns AppSyncBatchResolverHandler<T, R, S>

                          +
                          + +
                          +
                          diff --git a/docs/lambda/hierarchy.html b/docs/lambda/hierarchy.html index b2322b5..d15d781 100644 --- a/docs/lambda/hierarchy.html +++ b/docs/lambda/hierarchy.html @@ -10,19 +10,23 @@

                          cdk-serverless

                          Hierarchy Summary

                          +
                        • ForbiddenError
                        • +
                        • NotFoundError
                        • +
                        • ValidationError
                        • +
                        • ConflictError
                      +
                    • AppSyncBatchHandlerContext
                  +
                  • JwtAuthorizer
                      diff --git a/docs/lambda/interfaces/api.AppSyncBatchHandlerContext.html b/docs/lambda/interfaces/api.AppSyncBatchHandlerContext.html new file mode 100644 index 0000000..909667e --- /dev/null +++ b/docs/lambda/interfaces/api.AppSyncBatchHandlerContext.html @@ -0,0 +1,89 @@ +AppSyncBatchHandlerContext | cdk-serverless
                      +
                      cdk-serverless + +
                        +
                        +
                        Preparing search index...
                        +
                        +
                        +
                        + +

                        Interface AppSyncBatchHandlerContext<T, S>

                        +
                        +

                        The context for a batched AppSync resolver function. +Extends AppSyncHandlerContext so handler code that reads ctx.event.arguments / +ctx.event.identity keeps working against the first event, and additionally +exposes the full batch of events so the handler can map over it.

                        +
                        +
                        interface AppSyncBatchHandlerContext<
                            T,
                            S extends Record<string, any> | null = Record<string, any> | null,
                        > {
                            cognitoAuth: CognitoAuthorizer;
                            event: AppSyncResolverEvent<T, S>;
                            events: AppSyncResolverEvent<T, S>[];
                            lambdaContext: Context;
                            logger: LambdaLog;
                        }
                        +
                        +

                        Type Parameters

                        +
                          +
                        • T
                        • +
                        • S extends Record<string, any> | null = Record<string, any> | null
                        +
                        +

                        Hierarchy (View Summary)

                        +
                        +
                        +
                        +
                        +
                        Index
                        +
                        +
                        + +
                        +
                        + +
                        cognitoAuth: CognitoAuthorizer
                        +
                        + +
                        event: AppSyncResolverEvent<T, S>
                        +
                        + +
                        events: AppSyncResolverEvent<T, S>[]
                        +
                        + +
                        lambdaContext: Context
                        +
                        + +
                        logger: LambdaLog
                        +
                        + +
                        +
                        diff --git a/docs/lambda/interfaces/api.AppSyncHandlerContext.html b/docs/lambda/interfaces/api.AppSyncHandlerContext.html index c7ce601..c0ff83b 100644 --- a/docs/lambda/interfaces/api.AppSyncHandlerContext.html +++ b/docs/lambda/interfaces/api.AppSyncHandlerContext.html @@ -20,6 +20,12 @@

                        Interface AppSyncHandlerContext<T>

                        Type Parameters

                        • T
                    +
                    +

                    Hierarchy (View Summary)

                    +
                    diff --git a/docs/lambda/modules/api.html b/docs/lambda/modules/api.html index 91c006c..2c929c7 100644 --- a/docs/lambda/modules/api.html +++ b/docs/lambda/modules/api.html @@ -11,11 +11,11 @@
                  • api

                  Namespace api

                  -
                  AppSyncHandlerContext
                  HttpHandlerContext
                  HttpResponseContext
                  Operation
                  OperationWithRequestBody
                  +
                  AppSyncBatchHandlerContext
                  AppSyncHandlerContext
                  HttpHandlerContext
                  HttpResponseContext
                  Operation
                  OperationWithRequestBody
                  -
                  APIGatewayv1Handler
                  AppSyncHandler
                  HttpHandler
                  +
                  APIGatewayv1Handler
                  AppSyncBatchHandler
                  AppSyncHandler
                  HttpHandler
                  -
                  createAppSyncHandler
                  createHttpHandler
                  createOpenApiHandler
                  createOpenApiHandlerWithRequestBody
                  createOpenApiHandlerWithRequestBodyNoResponse
                  +
                  createAppSyncBatchHandler
                  createAppSyncHandler
                  createHttpHandler
                  createOpenApiHandler
                  createOpenApiHandlerWithRequestBody
                  createOpenApiHandlerWithRequestBodyNoResponse
                  diff --git a/llm.md b/llm.md index 43414dc..3847b34 100644 --- a/llm.md +++ b/llm.md @@ -216,6 +216,8 @@ The response handler recognises the envelope by a reserved `__appSyncError` sent Opting in with `typedErrors`: by default `addLambdaResolver` synthesizes a plain direct-Lambda UNIT resolver (the pre-#385 shape), so upgrading consumers who do not opt in see no resolver topology change and no `cdk diff` surprise. Set `typedErrors: true` on the `GraphQlApi` to get the typed `errors.appsync.*` behavior. With the flag enabled, each Lambda-backed field gains a new `AWS::AppSync::FunctionConfiguration` (`LambdaFn${type}.${field}`), and the resolver flips from a `dataSource` UNIT resolver to a `pipelineConfig` + `code` PIPELINE resolver with the response handler described above. The data source and resolver logical IDs are unchanged, so the resolver updates in place rather than being replaced, while the new per-field function resource is additive. The flag applies to all Lambda resolvers on the API, so run `cdk diff` after enabling it to review the topology change. +`maxBatchSize` wins over `typedErrors` per field. When a field passes `maxBatchSize` to `addLambdaResolver`, that field always gets the UNIT BatchInvoke resolver (MaxBatchSize plus the BatchInvoke request template and the `lambdaResult` response template), even when the API has `typedErrors: true`. The batch field gets no APPSYNC_JS pipeline, no `AWS::AppSync::FunctionConfiguration`, and no `APPSYNC_TYPED_ERRORS` env var on its Lambda, so it synthesizes identically whether or not the API enables `typedErrors`. The trade-off is that typed errors thrown by a batch handler are not converted to GraphQL errors. They rethrow and fail the invocation, so do not rely on the typed-error-to-GraphQL-error conversion in a batch handler. Non-batch fields on the same API keep the `typedErrors` pipeline behavior described above. + `APPSYNC_TYPED_ERRORS` is the coupling mechanism between the handler and the pipeline. `createAppSyncHandler` returns the `{ __appSyncError: true, ... }` sentinel envelope only when `APPSYNC_TYPED_ERRORS === 'true'` is set in the Lambda environment, and rethrows the `errors.appsync.*` error otherwise. `GraphQlApi.addLambdaResolver` injects `APPSYNC_TYPED_ERRORS=true` automatically on the `typedErrors: true` path, the same path that carries the response handler to translate the envelope. On the default UNIT path the flag is left unset, so a handler that throws an `errors.appsync.*` error fails the invocation loudly (rethrow) instead of returning a broken envelope as field data. If you wire `createAppSyncHandler` yourself without `GraphQlApi`, set `APPSYNC_TYPED_ERRORS=true` on the Lambda yourself to get envelope behavior and replicate the response handler. Without it, typed errors rethrow. Two classes share the short names `ForbiddenError` and `NotFoundError`, so pick the namespace deliberately: diff --git a/src/constructs/graphql.ts b/src/constructs/graphql.ts index fd4204b..df9dd5c 100644 --- a/src/constructs/graphql.ts +++ b/src/constructs/graphql.ts @@ -60,9 +60,13 @@ export interface LambdaResolverOptions { * this many field invocations into one Lambda call. Pair this with * createAppSyncBatchHandler, whose handler receives the batched events. * - * This option applies only to the default direct-Lambda UNIT path. It is not - * compatible with `typedErrors` (the APPSYNC_JS pipeline resolver), which does - * not use these BatchInvoke mapping templates. + * When both maxBatchSize and `typedErrors` are set, maxBatchSize wins for this + * field. The field gets the UNIT BatchInvoke resolver rather than the + * typedErrors APPSYNC_JS pipeline, because the BatchInvoke mapping templates + * have no equivalent on that pipeline. Typed errors thrown by a batch handler + * are not converted to GraphQL errors. They rethrow and fail the invocation, + * so a batch handler should not rely on the typed-error-to-GraphQL-error + * conversion. * * @default - undefined (single invoke, the pre-batch behaviour; no MaxBatchSize and no mapping templates) */ @@ -328,12 +332,7 @@ export class GraphQlApi extends BaseApi { fieldName: keyof FIELDTYPE, options?: LambdaResolverOptions, ): LambdaFunction { - // BatchInvoke mapping templates belong to the direct-Lambda UNIT resolver and - // have no equivalent on the APPSYNC_JS pipeline resolver built for typedErrors, - // so the two cannot be combined. Fail fast rather than silently ignore one. - if (options?.maxBatchSize !== undefined && this.props.typedErrors) { - throw new Error('maxBatchSize is not supported together with typedErrors'); - } + const isBatch = options?.maxBatchSize !== undefined; const operationId = `${typeName as string}.${fieldName as String}`; const description = `Type ${typeName as string} Field ${fieldName as String} Resolver`; @@ -381,28 +380,40 @@ export class GraphQlApi extends BaseApi { lambdaFunction: fn, }); + if (isBatch) { + // A batch field always takes the UNIT BatchInvoke path, even when the API + // has typedErrors enabled. maxBatchSize wins over typedErrors for this + // field because the BatchInvoke mapping templates have no equivalent on + // the APPSYNC_JS pipeline resolver. AppSync batches up to maxBatchSize + // field invocations into a single Lambda call handled by + // createAppSyncBatchHandler. The L2 BaseResolverProps.maxBatchSize sets + // MaxBatchSize directly, so no escape hatch is needed. + // + // APPSYNC_TYPED_ERRORS is intentionally not set here. A batch handler that + // throws a typed error rethrows and fails the invocation loudly instead of + // having the error envelope returned as field data. + new aws_appsync.Resolver(scope, `Resolver${operationId}`, { + api: this.api, + typeName: typeName as string, + fieldName: fieldName as string, + dataSource, + maxBatchSize: options!.maxBatchSize, + requestMappingTemplate: aws_appsync.MappingTemplate.lambdaRequest('$utils.toJson($context)', 'BatchInvoke'), + responseMappingTemplate: aws_appsync.MappingTemplate.lambdaResult(), + }); + return fn; + } + if (!this.props.typedErrors) { // Default (pre-#385) shape: a plain UNIT resolver with the Lambda data // source set directly. No response handler, so the error envelope from // createAppSyncHandler would be surfaced as field data rather than a // GraphQL error. Opt in via `typedErrors: true` to get the pipeline below. - // - // When maxBatchSize is set, this stays a UNIT resolver but gets the - // BatchInvoke mapping templates and MaxBatchSize, so AppSync batches field - // invocations into a single Lambda call handled by createAppSyncBatchHandler. - // The L2 BaseResolverProps.maxBatchSize sets MaxBatchSize directly, so no - // escape hatch is needed. Leaving the option unset keeps the exact prior - // single-invoke shape (no MaxBatchSize, no mapping templates). new aws_appsync.Resolver(scope, `Resolver${operationId}`, { api: this.api, typeName: typeName as string, fieldName: fieldName as string, dataSource, - ...options?.maxBatchSize !== undefined && { - maxBatchSize: options.maxBatchSize, - requestMappingTemplate: aws_appsync.MappingTemplate.lambdaRequest('$utils.toJson($context)', 'BatchInvoke'), - responseMappingTemplate: aws_appsync.MappingTemplate.lambdaResult(), - }, }); return fn; } diff --git a/test/constructs/graphql.test.ts b/test/constructs/graphql.test.ts index 68845c9..bd746f9 100644 --- a/test/constructs/graphql.test.ts +++ b/test/constructs/graphql.test.ts @@ -484,7 +484,7 @@ type Post { }); }); - test('maxBatchSize together with typedErrors throws', () => { + test('maxBatchSize wins over typedErrors: UNIT BatchInvoke resolver, no pipeline, no env var', () => { const api = new GraphQlApi(stack, 'TestApi', { apiName: 'TestAPI', stageName: 'test', @@ -493,8 +493,35 @@ type Post { typedErrors: true, }); - expect(() => api.addLambdaResolver('Query', 'getUser', { maxBatchSize: 10 })) - .toThrow('maxBatchSize is not supported together with typedErrors'); + api.addLambdaResolver('Query', 'getUser', { maxBatchSize: 10 }); + + const template = Template.fromStack(stack); + + // A batch field takes the UNIT BatchInvoke path even when the API enables + // typedErrors, so it synthesizes identically to a batch field on a + // non-typedErrors API: a direct-Lambda UNIT resolver with MaxBatchSize and + // the BatchInvoke mapping templates, no inline Code and no PipelineConfig. + template.hasResourceProperties('AWS::AppSync::Resolver', { + TypeName: 'Query', + FieldName: 'getUser', + Kind: 'UNIT', + MaxBatchSize: 10, + RequestMappingTemplate: Match.stringLikeRegexp('BatchInvoke'), + ResponseMappingTemplate: '$util.toJson($ctx.result)', + Code: Match.absent(), + PipelineConfig: Match.absent(), + }); + + // No APPSYNC_JS pipeline function on the batch path. + template.resourceCountIs('AWS::AppSync::FunctionConfiguration', 0); + + // The batch Lambda carries no APPSYNC_TYPED_ERRORS env var, so a batch + // handler that throws a typed error rethrows and fails the invocation + // instead of returning the envelope as field data. + template.hasResourceProperties('AWS::Lambda::Function', { + Description: '[test] Type Query Field getUser Resolver', + Environment: Match.absent(), + }); }); });