diff --git a/.changeset/yellow-facts-turn.md b/.changeset/yellow-facts-turn.md new file mode 100644 index 00000000..71e2d1e6 --- /dev/null +++ b/.changeset/yellow-facts-turn.md @@ -0,0 +1,5 @@ +--- +"@aligent/cdk-aspects": patch +--- + +fix: use addPropertyDeletionOverride to properly clear CDK L2 lazy token producers for DynamoDB throughput properties at synth time diff --git a/packages/cdk-aspects/lib/defaults/dynamodb.test.ts b/packages/cdk-aspects/lib/defaults/dynamodb.test.ts index 3fdddc6e..bc51ae47 100644 --- a/packages/cdk-aspects/lib/defaults/dynamodb.test.ts +++ b/packages/cdk-aspects/lib/defaults/dynamodb.test.ts @@ -171,6 +171,27 @@ describe("DynamoDbDefaultsAspect", () => { expect(props["ProvisionedThroughput"]).toBeUndefined(); }); + it("clears GSI-level ProvisionedThroughput set by CDK L2 defaults", () => { + const table = new Table(stack, "TableWithGSI", { + partitionKey: { name: "pk", type: AttributeType.STRING }, + }); + table.addGlobalSecondaryIndex({ + indexName: "gsi1", + partitionKey: { name: "gsi1pk", type: AttributeType.STRING }, + }); + app.synth(); + + const resources = Template.fromStack(stack).findResources( + "AWS::DynamoDB::Table" + ); + const props = Object.values(resources)[0].Properties; + const gsis = props["GlobalSecondaryIndexes"]; + expect(gsis).toBeDefined(); + gsis.forEach((gsi: Record) => { + expect(gsi["ProvisionedThroughput"]).toBeUndefined(); + }); + }); + it("applies DESTROY removal policy", () => { makeTable(stack, "MyTable"); app.synth(); diff --git a/packages/cdk-aspects/lib/defaults/dynamodb.ts b/packages/cdk-aspects/lib/defaults/dynamodb.ts index ba41e060..b5ff51d7 100644 --- a/packages/cdk-aspects/lib/defaults/dynamodb.ts +++ b/packages/cdk-aspects/lib/defaults/dynamodb.ts @@ -1,4 +1,4 @@ -import { RemovalPolicy, type IAspect } from "aws-cdk-lib"; +import { RemovalPolicy, Stack, type IAspect } from "aws-cdk-lib"; import { BillingMode, CfnTable, @@ -126,11 +126,21 @@ export class DynamoDbDefaultsAspect implements IAspect { cfnTable.billingMode = cfnTable.billingMode ?? billingMode; - // Clear conflicting throughput config to prevent CloudFormation errors + // Use addPropertyDeletionOverride instead of direct assignment to bypass + // CDK L2 lazy token producers that re-resolve at synth time. if (cfnTable.billingMode === BillingMode.PAY_PER_REQUEST) { - cfnTable.provisionedThroughput = undefined; + cfnTable.addPropertyDeletionOverride("ProvisionedThroughput"); + + const gsis = Stack.of(node).resolve(cfnTable.globalSecondaryIndexes); + if (Array.isArray(gsis)) { + gsis.forEach((_, i) => { + cfnTable.addPropertyDeletionOverride( + `GlobalSecondaryIndexes.${i}.ProvisionedThroughput` + ); + }); + } } else if (cfnTable.billingMode === BillingMode.PROVISIONED) { - cfnTable.onDemandThroughput = undefined; + cfnTable.addPropertyDeletionOverride("OnDemandThroughput"); } // Stamp throughput as a default, not an override: the first predicate asks