From 43f6bee68b9d41aa9f3ca655902465032ee83506 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 31 Dec 2025 11:13:05 +0100 Subject: [PATCH 1/3] chore(appsync): reference interfaces Reference interfaces for L2s, like https://github.com/aws/aws-cdk/pull/35271. "chore" because I plan to make a lot of these PRs and they're not adding value to the changelog. (Generated with AI) --- .../aws-cdk-lib/aws-appsync/lib/api-base.ts | 9 +++++- .../aws-appsync/lib/appsync-function.ts | 14 ++++++++- .../aws-appsync/lib/channel-namespace.ts | 14 ++++++++- .../aws-appsync/lib/data-source-common.ts | 31 ++++++++++++++++--- .../aws-appsync/lib/data-source.ts | 31 ++++++++++++++++--- .../aws-appsync/lib/graphqlapi-base.ts | 9 +++++- .../aws-appsync/lib/private/ref-utils.ts | 26 ++++++++++++++++ .../aws-cdk-lib/aws-appsync/lib/resolver.ts | 19 +++++++++--- .../aws-cdk-lib/aws-appsync/lib/schema.ts | 20 +++++++++--- .../aws-appsync/lib/source-api-association.ts | 14 ++++++++- .../aws-cdk-lib/aws-ecs/test/cluster.test.ts | 10 +++--- 11 files changed, 169 insertions(+), 28 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts diff --git a/packages/aws-cdk-lib/aws-appsync/lib/api-base.ts b/packages/aws-cdk-lib/aws-appsync/lib/api-base.ts index 67b0ef3dca37f..44c531f5ed385 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/api-base.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/api-base.ts @@ -1,9 +1,10 @@ import { IResource, Resource } from '../../core'; +import { IApiRef, ApiReference } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * Interface for an API */ -export interface IApi extends IResource { +export interface IApi extends IResource, IApiRef { /** * The unique identifier for the AWS AppSync Api generated by the service. @@ -33,4 +34,10 @@ export abstract class ApiBase extends Resource implements IApi { * The ARN of the AWS AppSync Api. */ public abstract readonly apiArn: string; + + public get apiRef(): ApiReference { + return { + apiArn: this.apiArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts b/packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts index 62389150de30e..21cd81ffbaf30 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts @@ -8,6 +8,7 @@ import { FunctionRuntime } from './runtime'; import { Resource, IResource, Lazy, Fn, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; +import { IFunctionConfigurationRef, FunctionConfigurationReference } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * the base properties for AppSync Functions @@ -91,7 +92,7 @@ export interface AppsyncFunctionAttributes { /** * Interface for AppSync Functions */ -export interface IAppsyncFunction extends IResource { +export interface IAppsyncFunction extends IResource, IFunctionConfigurationRef { /** * the name of this AppSync Function * @@ -130,6 +131,11 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction { constructor(s: Construct, i: string) { super(s, i); } + public get functionConfigurationRef(): FunctionConfigurationReference { + return { + functionArn: this.functionArn, + }; + } } return new Import(scope, id); } @@ -201,4 +207,10 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction { this.function.addDependency(this.dataSource.ds); props.api.addSchemaDependency(this.function); } + + public get functionConfigurationRef(): FunctionConfigurationReference { + return { + functionArn: this.functionArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts b/packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts index 3d03ed5655e0b..8a7f4916d2d94 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts @@ -13,11 +13,12 @@ import { IGrantable } from '../../aws-iam'; import { IResource, Resource, Token, ValidationError } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; +import { IChannelNamespaceRef, ChannelNamespaceReference } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * An AppSync channel namespace */ -export interface IChannelNamespace extends IResource { +export interface IChannelNamespace extends IResource, IChannelNamespaceRef { /** * The ARN of the AppSync channel namespace * @@ -187,6 +188,11 @@ export class ChannelNamespace extends Resource implements IChannelNamespace { public static fromChannelNamespaceArn(scope: Construct, id: string, channelNamespaceArn: string): IChannelNamespace { class Import extends Resource implements IChannelNamespace { public readonly channelNamespaceArn = channelNamespaceArn; + public get channelNamespaceRef(): ChannelNamespaceReference { + return { + channelNamespaceArn: this.channelNamespaceArn, + }; + } } return new Import(scope, id); } @@ -359,4 +365,10 @@ export class ChannelNamespace extends Resource implements IChannelNamespace { throw new ValidationError('LambdaInvokeType is only supported for Direct handler behavior type', this); } } + + public get channelNamespaceRef(): ChannelNamespaceReference { + return { + channelNamespaceArn: this.channelNamespaceArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts index 1858089b7cf2e..ef53546da7632 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts @@ -15,7 +15,20 @@ import { IFunction } from '../../aws-lambda'; import { IDomain } from '../../aws-opensearchservice'; import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; -import { IResolvable, Token, Lazy, Stack } from '../../core'; +import { Fn, IResolvable, Token, Lazy, Stack } from '../../core'; +import { toIApi } from './private/ref-utils'; +import { IApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; + +function extractApiIdFromApiRef(apiRef: IApiRef): string { + // Check if this is actually an IApi (which has apiId directly) + const api = apiRef as any; + if (api.apiId !== undefined) { + return api.apiId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/apiId + return Fn.select(1, Fn.split('/', apiRef.apiRef.apiArn)); +} /** * Valid data source types for AppSync @@ -83,7 +96,7 @@ export interface AppSyncBaseDataSourceProps { /** * The API to attach this data source to */ - readonly api: IApi; + readonly api: IApiRef; /** * The name of the data source. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}. * Any invalid characters will be automatically removed. @@ -171,7 +184,7 @@ export abstract class AppSyncBaseDataSource extends Construct { */ public readonly resource: CfnDataSource; - protected api: IApi; + private _api: IApiRef; protected serviceRole?: IRole; constructor(scope: Construct, id: string, props: AppSyncBackedDataSourceProps, extended: AppSyncExtendedDataSourceProps) { @@ -182,15 +195,23 @@ export abstract class AppSyncBaseDataSource extends Construct { // Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*} const name = (props.name ?? id); const supportedName = Token.isUnresolved(name) ? name : name.replace(/[\W]+/g, ''); + const apiId = extractApiIdFromApiRef(props.api); this.resource = new CfnDataSource(this, 'Resource', { - apiId: props.api.apiId, + apiId: apiId, name: supportedName, description: props.description, serviceRoleArn: this.serviceRole?.roleArn, ...extended, }); this.name = supportedName; - this.api = props.api; + this._api = props.api; + } + + /** + * The API this data source is attached to + */ + protected get api(): IApi { + return toIApi(this._api); } } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts index fd92f4f9a9542..5b3e0c750263d 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { BaseAppsyncFunctionProps, AppsyncFunction } from './appsync-function'; import { CfnDataSource } from './appsync.generated'; import { IGraphqlApi } from './graphqlapi-base'; +import { toIGraphqlApi } from './private/ref-utils'; import { BaseResolverProps, Resolver } from './resolver'; import { ITable } from '../../aws-dynamodb'; import { IDomain as IElasticsearchDomain } from '../../aws-elasticsearch'; @@ -11,8 +12,9 @@ import { IFunction } from '../../aws-lambda'; import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice'; import { IServerlessCluster, IDatabaseCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; -import { IResolvable, Lazy, Stack, Token } from '../../core'; +import { Fn, IResolvable, Lazy, Stack, Token } from '../../core'; import { propertyInjectable } from '../../core/lib/prop-injectable'; +import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * Base properties for an AppSync datasource @@ -21,7 +23,7 @@ export interface BaseDataSourceProps { /** * The API to attach this data source to */ - readonly api: IGraphqlApi; + readonly api: IGraphQLApiRef; /** * The name of the data source * @@ -103,6 +105,17 @@ export interface ExtendedDataSourceProps { readonly relationalDatabaseConfig?: CfnDataSource.RelationalDatabaseConfigProperty | IResolvable; } +function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string { + // Check if this is actually an IGraphqlApi (which has apiId directly) + const api = apiRef as any; + if (api.apiId !== undefined) { + return api.apiId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/apiId + return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn)); +} + /** * Abstract AppSync datasource implementation. Do not use directly but use subclasses for concrete datasources */ @@ -116,7 +129,7 @@ export abstract class BaseDataSource extends Construct { */ public readonly ds: CfnDataSource; - protected api: IGraphqlApi; + private _api: IGraphQLApiRef; protected serviceRole?: IRole; constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { @@ -128,15 +141,23 @@ export abstract class BaseDataSource extends Construct { // Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*} const name = (props.name ?? id); const supportedName = Token.isUnresolved(name) ? name : name.replace(/[\W]+/g, ''); + const apiId = extractApiIdFromGraphQLApiRef(props.api); this.ds = new CfnDataSource(this, 'Resource', { - apiId: props.api.apiId, + apiId: apiId, name: supportedName, description: props.description, serviceRoleArn: this.serviceRole?.roleArn, ...extended, }); this.name = supportedName; - this.api = props.api; + this._api = props.api; + } + + /** + * The API this data source is attached to + */ + protected get api(): IGraphqlApi { + return toIGraphqlApi(this._api); } /** diff --git a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts index bee46be04dd73..29531a8d8bb35 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts @@ -19,6 +19,7 @@ import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice'; import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; import { ArnFormat, CfnResource, IResource, Resource, Stack, UnscopedValidationError } from '../../core'; +import { IGraphQLApiRef, GraphQLApiReference } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * Optional configuration for data sources @@ -156,7 +157,7 @@ export enum AuthorizationType { /** * Interface for GraphQL */ -export interface IGraphqlApi extends IResource { +export interface IGraphqlApi extends IResource, IGraphQLApiRef { /** * an unique AWS AppSync GraphQL API identifier @@ -595,4 +596,10 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi { public grantSubscription(grantee: IGrantable, ...fields: string[]): Grant { return this.grant(grantee, IamResource.ofType('Subscription', ...fields), 'appsync:GraphQL'); } + + public get graphQlApiRef(): GraphQLApiReference { + return { + graphQlApiArn: this.arn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts b/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts new file mode 100644 index 0000000000000..aceaece638e8b --- /dev/null +++ b/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts @@ -0,0 +1,26 @@ +import { UnscopedValidationError } from '../../../core'; +import { IGraphQLApiRef, IApiRef } from '../../../interfaces/generated/aws-appsync-interfaces.generated'; +import { IApi } from '../api-base'; +import { IGraphqlApi } from '../graphqlapi-base'; + +/** + * Converts an IGraphQLApiRef to IGraphqlApi, validating that it implements the full interface + */ +export function toIGraphqlApi(apiRef: IGraphQLApiRef): IGraphqlApi { + const api = apiRef as any; + if (typeof api.apiId !== 'string' || typeof api.arn !== 'string' || typeof api.addNoneDataSource !== 'function') { + throw new UnscopedValidationError(`'api' instance should implement IGraphqlApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`); + } + return apiRef as IGraphqlApi; +} + +/** + * Converts an IApiRef to IApi, validating that it implements the full interface + */ +export function toIApi(apiRef: IApiRef): IApi { + const api = apiRef as any; + if (typeof api.apiId !== 'string' || typeof api.apiArn !== 'string' || typeof api.addDynamoDbDataSource !== 'function') { + throw new UnscopedValidationError(`'api' instance should implement IApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`); + } + return apiRef as IApi; +} diff --git a/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts b/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts index cbd7b3c18dcfd..157cd5f58d570 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts @@ -1,5 +1,4 @@ import { Construct } from 'constructs'; -import { IAppsyncFunction } from './appsync-function'; import { CfnResolver } from './appsync.generated'; import { CachingConfig } from './caching-config'; import { BASE_CACHING_KEYS } from './caching-key'; @@ -8,7 +7,19 @@ import { BaseDataSource } from './data-source'; import { IGraphqlApi } from './graphqlapi-base'; import { MappingTemplate } from './mapping-template'; import { FunctionRuntime } from './runtime'; -import { Token, ValidationError } from '../../core'; +import { Fn, Token, ValidationError } from '../../core'; +import { IFunctionConfigurationRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; + +function extractFunctionIdFromFunctionRef(funcRef: IFunctionConfigurationRef): string { + // Check if this is actually an IAppsyncFunction (which has functionId directly) + const func = funcRef as any; + if (func.functionId !== undefined) { + return func.functionId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/apiId/functions/functionId + return Fn.select(3, Fn.split('/', funcRef.functionConfigurationRef.functionArn)); +} /** * Basic properties for an AppSync resolver @@ -28,7 +39,7 @@ export interface BaseResolverProps { * @default - no pipeline resolver configuration * An empty array | undefined sets resolver to be of kind, unit */ - readonly pipelineConfig?: IAppsyncFunction[]; + readonly pipelineConfig?: IFunctionConfigurationRef[]; /** * The request mapping template for this resolver * @@ -105,7 +116,7 @@ export class Resolver extends Construct { super(scope, id); const pipelineConfig = props.pipelineConfig && props.pipelineConfig.length ? - { functions: props.pipelineConfig.map((func) => func.functionId) } + { functions: props.pipelineConfig.map((func) => extractFunctionIdFromFunctionRef(func)) } : undefined; // If runtime is specified, code must also be diff --git a/packages/aws-cdk-lib/aws-appsync/lib/schema.ts b/packages/aws-cdk-lib/aws-appsync/lib/schema.ts index f9281176b3b9c..ad5921c01b028 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/schema.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/schema.ts @@ -1,5 +1,17 @@ import { readFileSync } from 'fs'; -import { IGraphqlApi } from './graphqlapi-base'; +import { Fn } from '../../core'; +import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; + +function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string { + // Check if this is actually an IGraphqlApi (which has apiId directly) + const api = apiRef as any; + if (api.apiId !== undefined) { + return api.apiId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/apiId + return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn)); +} /** * Configuration for bound graphql schema @@ -39,7 +51,7 @@ export interface ISchema { * @param api the api to bind the schema to * @param options configuration for bind behavior */ - bind(api: IGraphqlApi, options?: SchemaBindOptions): ISchemaConfig; + bind(api: IGraphQLApiRef, options?: SchemaBindOptions): ISchemaConfig; } /** @@ -86,9 +98,9 @@ export class SchemaFile implements ISchema { * * @param api The binding GraphQL Api */ - public bind(api: IGraphqlApi, _options?: SchemaBindOptions): ISchemaConfig { + public bind(api: IGraphQLApiRef, _options?: SchemaBindOptions): ISchemaConfig { return { - apiId: api.apiId, + apiId: extractApiIdFromGraphQLApiRef(api), definition: this.definition, }; } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/source-api-association.ts b/packages/aws-cdk-lib/aws-appsync/lib/source-api-association.ts index ee8122fea9598..3844093578dda 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/source-api-association.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/source-api-association.ts @@ -5,6 +5,7 @@ import { Effect, IRole, PolicyStatement } from '../../aws-iam'; import { Fn, IResource, Lazy, Resource } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; +import { ISourceApiAssociationRef, SourceApiAssociationReference } from '../../interfaces/generated/aws-appsync-interfaces.generated'; /** * Merge type used to associate the source API @@ -24,7 +25,7 @@ export enum MergeType { /** * Interface for AppSync Source Api Association */ -export interface ISourceApiAssociation extends IResource { +export interface ISourceApiAssociation extends IResource, ISourceApiAssociationRef { /** * The association id. @@ -126,6 +127,11 @@ export class SourceApiAssociation extends Resource implements ISourceApiAssociat constructor(s: Construct, i: string) { super(s, i); } + public get sourceApiAssociationRef(): SourceApiAssociationReference { + return { + associationArn: this.associationArn, + }; + } } return new Import(scope, id); } @@ -197,6 +203,12 @@ export class SourceApiAssociation extends Resource implements ISourceApiAssociat addSourceApiAutoMergePermission(this.association, this.mergedApiExecutionRole); } } + + public get sourceApiAssociationRef(): SourceApiAssociationReference { + return { + associationArn: this.associationArn, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts b/packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts index 5766bddee2808..07a30f8c52a97 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/unbound-method */ + import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Match, Template } from '../../assertions'; import * as autoscaling from '../../aws-autoscaling'; @@ -95,7 +95,7 @@ describe('cluster', () => { { Ref: 'EcsCluster97242B84', }, - // eslint-disable-next-line max-len + ' >> /etc/ecs/ecs.config', ], ], @@ -264,7 +264,7 @@ describe('cluster', () => { { Ref: 'EcsCluster97242B84', }, - // eslint-disable-next-line max-len + ' >> /etc/ecs/ecs.config', ], ], @@ -790,7 +790,7 @@ describe('cluster', () => { { Ref: 'EcsCluster97242B84', }, - // eslint-disable-next-line max-len + ' >> /etc/ecs/ecs.config', ], ], @@ -1783,7 +1783,7 @@ describe('cluster', () => { { Ref: 'EcsCluster97242B84', }, - // eslint-disable-next-line max-len + ' >> /etc/ecs/ecs.config', ], ], From 13e42d12bf2dbe770d476fc8228ad3bef8b7977f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 6 Jan 2026 15:49:26 +0100 Subject: [PATCH 2/3] Extract and refactor some helpers --- .../aws-appsync/lib/data-source-common.ts | 15 +--- .../aws-appsync/lib/data-source.ts | 22 +++--- .../aws-appsync/lib/private/ref-utils.ts | 69 ++++++++++++++++--- .../aws-cdk-lib/aws-appsync/lib/resolver.ts | 14 +--- .../aws-cdk-lib/aws-appsync/lib/schema.ts | 13 +--- 5 files changed, 74 insertions(+), 59 deletions(-) diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts index ef53546da7632..103d9a447ce9b 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts @@ -15,21 +15,10 @@ import { IFunction } from '../../aws-lambda'; import { IDomain } from '../../aws-opensearchservice'; import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; -import { Fn, IResolvable, Token, Lazy, Stack } from '../../core'; -import { toIApi } from './private/ref-utils'; +import { IResolvable, Token, Lazy, Stack } from '../../core'; +import { extractApiIdFromApiRef, toIApi } from './private/ref-utils'; import { IApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; -function extractApiIdFromApiRef(apiRef: IApiRef): string { - // Check if this is actually an IApi (which has apiId directly) - const api = apiRef as any; - if (api.apiId !== undefined) { - return api.apiId; - } - // Otherwise, extract from the ARN - // ARN format: arn:aws:appsync:region:account:apis/apiId - return Fn.select(1, Fn.split('/', apiRef.apiRef.apiArn)); -} - /** * Valid data source types for AppSync */ diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts index 5b3e0c750263d..62583febb0ce0 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { BaseAppsyncFunctionProps, AppsyncFunction } from './appsync-function'; import { CfnDataSource } from './appsync.generated'; import { IGraphqlApi } from './graphqlapi-base'; -import { toIGraphqlApi } from './private/ref-utils'; +import { extractApiIdFromGraphQLApiRef, toIGraphqlApi } from './private/ref-utils'; import { BaseResolverProps, Resolver } from './resolver'; import { ITable } from '../../aws-dynamodb'; import { IDomain as IElasticsearchDomain } from '../../aws-elasticsearch'; @@ -12,7 +12,7 @@ import { IFunction } from '../../aws-lambda'; import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice'; import { IServerlessCluster, IDatabaseCluster } from '../../aws-rds'; import { ISecret } from '../../aws-secretsmanager'; -import { Fn, IResolvable, Lazy, Stack, Token } from '../../core'; +import { IResolvable, Lazy, Stack, Token } from '../../core'; import { propertyInjectable } from '../../core/lib/prop-injectable'; import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; @@ -105,17 +105,6 @@ export interface ExtendedDataSourceProps { readonly relationalDatabaseConfig?: CfnDataSource.RelationalDatabaseConfigProperty | IResolvable; } -function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string { - // Check if this is actually an IGraphqlApi (which has apiId directly) - const api = apiRef as any; - if (api.apiId !== undefined) { - return api.apiId; - } - // Otherwise, extract from the ARN - // ARN format: arn:aws:appsync:region:account:apis/apiId - return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn)); -} - /** * Abstract AppSync datasource implementation. Do not use directly but use subclasses for concrete datasources */ @@ -160,6 +149,13 @@ export abstract class BaseDataSource extends Construct { return toIGraphqlApi(this._api); } + /** + * Set the API this data source is attached to + */ + protected set api(api: IGraphqlApi) { + this._api = api; + } + /** * creates a new resolver for this datasource and API using the given properties */ diff --git a/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts b/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts index aceaece638e8b..362d9f47a6625 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts @@ -1,26 +1,77 @@ -import { UnscopedValidationError } from '../../../core'; -import { IGraphQLApiRef, IApiRef } from '../../../interfaces/generated/aws-appsync-interfaces.generated'; +import { Fn, UnscopedValidationError } from '../../../core'; +import { IGraphQLApiRef, IApiRef, IFunctionConfigurationRef } from '../../../interfaces/generated/aws-appsync-interfaces.generated'; import { IApi } from '../api-base'; +import { IAppsyncFunction } from '../appsync-function'; import { IGraphqlApi } from '../graphqlapi-base'; /** * Converts an IGraphQLApiRef to IGraphqlApi, validating that it implements the full interface */ -export function toIGraphqlApi(apiRef: IGraphQLApiRef): IGraphqlApi { +export function toIGraphqlApi(api: IGraphQLApiRef): IGraphqlApi { + if (!isGraphQlApi(api)) { + throw new UnscopedValidationError(`'api' instance should implement IGraphqlApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`); + } + return api; +} + +function isGraphQlApi(apiRef: IGraphQLApiRef): apiRef is IGraphqlApi { const api = apiRef as any; if (typeof api.apiId !== 'string' || typeof api.arn !== 'string' || typeof api.addNoneDataSource !== 'function') { - throw new UnscopedValidationError(`'api' instance should implement IGraphqlApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`); + return false; + } + return true; +} + +function isIApi(apiRef: IApiRef): apiRef is IApi { + const api = apiRef as any; + if (typeof api.apiId !== 'string' || typeof api.apiArn !== 'string' || typeof api.addDynamoDbDataSource !== 'function') { + return false; } - return apiRef as IGraphqlApi; + return true; } /** * Converts an IApiRef to IApi, validating that it implements the full interface */ -export function toIApi(apiRef: IApiRef): IApi { - const api = apiRef as any; - if (typeof api.apiId !== 'string' || typeof api.apiArn !== 'string' || typeof api.addDynamoDbDataSource !== 'function') { +export function toIApi(api: IApiRef): IApi { + if (!isIApi(api)) { throw new UnscopedValidationError(`'api' instance should implement IApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`); } - return apiRef as IApi; + return api; +} + +export function extractApiIdFromApiRef(apiRef: IApiRef): string { + // Check if this is actually an IApi (which has apiId directly) + if (isIApi(apiRef)) { + return apiRef.apiId; + } + + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/ + return Fn.select(1, Fn.split('/', apiRef.apiRef.apiArn)); +} + +export function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string { + // Check if this is actually an IGraphqlApi (which has apiId directly) + if (isGraphQlApi(apiRef)) { + return apiRef.apiId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis/ + return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn)); +} + +function isIFunctionConfiguration(funcRef: IFunctionConfigurationRef): funcRef is IAppsyncFunction { + const fr = funcRef as unknown as IAppsyncFunction; + return !!fr.functionId; +} + +export function extractFunctionIdFromFunctionRef(funcRef: IFunctionConfigurationRef): string { + // Check if this is actually an IAppsyncFunction (which has functionId directly) + if (isIFunctionConfiguration(funcRef)) { + return funcRef.functionId; + } + // Otherwise, extract from the ARN + // ARN format: arn:aws:appsync:region:account:apis//functions/ + return Fn.select(3, Fn.split('/', funcRef.functionConfigurationRef.functionArn)); } diff --git a/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts b/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts index 157cd5f58d570..4246108fc12ff 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/resolver.ts @@ -7,20 +7,10 @@ import { BaseDataSource } from './data-source'; import { IGraphqlApi } from './graphqlapi-base'; import { MappingTemplate } from './mapping-template'; import { FunctionRuntime } from './runtime'; -import { Fn, Token, ValidationError } from '../../core'; +import { Token, ValidationError } from '../../core'; +import { extractFunctionIdFromFunctionRef } from './private/ref-utils'; import { IFunctionConfigurationRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; -function extractFunctionIdFromFunctionRef(funcRef: IFunctionConfigurationRef): string { - // Check if this is actually an IAppsyncFunction (which has functionId directly) - const func = funcRef as any; - if (func.functionId !== undefined) { - return func.functionId; - } - // Otherwise, extract from the ARN - // ARN format: arn:aws:appsync:region:account:apis/apiId/functions/functionId - return Fn.select(3, Fn.split('/', funcRef.functionConfigurationRef.functionArn)); -} - /** * Basic properties for an AppSync resolver */ diff --git a/packages/aws-cdk-lib/aws-appsync/lib/schema.ts b/packages/aws-cdk-lib/aws-appsync/lib/schema.ts index ad5921c01b028..888fd5efab6b1 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/schema.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/schema.ts @@ -1,18 +1,7 @@ import { readFileSync } from 'fs'; -import { Fn } from '../../core'; +import { extractApiIdFromGraphQLApiRef } from './private/ref-utils'; import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated'; -function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string { - // Check if this is actually an IGraphqlApi (which has apiId directly) - const api = apiRef as any; - if (api.apiId !== undefined) { - return api.apiId; - } - // Otherwise, extract from the ARN - // ARN format: arn:aws:appsync:region:account:apis/apiId - return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn)); -} - /** * Configuration for bound graphql schema * From bb9ce384c9e53ee759fb9c872babecdbb8381751 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 7 Jan 2026 10:51:49 +0100 Subject: [PATCH 3/3] Adding another setter --- packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts index 103d9a447ce9b..891bf6c049d1e 100644 --- a/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts +++ b/packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts @@ -202,6 +202,13 @@ export abstract class AppSyncBaseDataSource extends Construct { protected get api(): IApi { return toIApi(this._api); } + + /** + * Set the API this data source is attached to + */ + protected set api(api: IApi) { + this._api = api; + } } /**