Skip to content

Commit 0c6ee1d

Browse files
authored
feat(mixins-preview): developer preview of CDK Mixins (#36136)
### Reason for this change aws/aws-cdk-rfcs#824 CDK Mixins are composable, reusable abstractions that can be applied to any construct (L1, L2 or custom). They are breaking down the traditional barriers between construct levels, allowing customers to mix and match sophisticated features without being locked into specific implementations. ### Description of changes This PR makes the package public so it can be released. It also implements some small changes based on RFC feedback. Main functional changes are: - Removing `validate()` in favor of just throwing errors - Making `.with()`, `.apply()` and `.mustApply()` variadic ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Unit tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent fc2f717 commit 0c6ee1d

File tree

12 files changed

+604
-346
lines changed

12 files changed

+604
-346
lines changed

packages/@aws-cdk/mixins-preview/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ The `.with()` method is available after importing `@aws-cdk/mixins-preview/with`
5555
5656
## Creating Custom Mixins
5757

58-
Mixins are simple classes that implement the `IMixin` interface:
58+
Mixins are simple classes that implement the `IMixin` interface (usually by extending the abstract `Mixin` class:
5959

6060
```typescript
6161
// Simple mixin that enables versioning
62-
class CustomVersioningMixin implements IMixin {
62+
class CustomVersioningMixin extends Mixin implements IMixin {
6363
supports(construct: any): boolean {
6464
return construct instanceof s3.CfnBucket;
6565
}
@@ -131,7 +131,7 @@ For every CloudFormation resource, CDK Mixins automatically generates type-safe
131131

132132
```typescript
133133
import '@aws-cdk/mixins-preview/with';
134-
import { CfnBucketPropsMixin } from '@aws-cdk/mixins-preview/aws-s3/mixins';
134+
135135

136136
const bucket = new s3.Bucket(scope, "Bucket")
137137
.with(new CfnBucketPropsMixin({
@@ -146,6 +146,8 @@ const bucket = new s3.Bucket(scope, "Bucket")
146146
Property mixins support two merge strategies:
147147

148148
```typescript
149+
declare const bucket: s3.CfnBucket;
150+
149151
// MERGE (default): Deep merges properties with existing values
150152
Mixins.of(bucket).apply(new CfnBucketPropsMixin(
151153
{ versioningConfiguration: { status: "Enabled" } },
@@ -155,16 +157,16 @@ Mixins.of(bucket).apply(new CfnBucketPropsMixin(
155157
// OVERWRITE: Replaces existing property values
156158
Mixins.of(bucket).apply(new CfnBucketPropsMixin(
157159
{ versioningConfiguration: { status: "Enabled" } },
158-
{ strategy: PropertyMergeStrategy.OVERWRITE }
160+
{ strategy: PropertyMergeStrategy.OVERRIDE }
159161
));
160162
```
161163

162164
Property mixins are available for all AWS services:
163165

164166
```typescript
165-
import { CfnLogGroupMixin } from '@aws-cdk/mixins-preview/aws-logs/mixins';
166-
import { CfnFunctionMixin } from '@aws-cdk/mixins-preview/aws-lambda/mixins';
167-
import { CfnTableMixin } from '@aws-cdk/mixins-preview/aws-dynamodb/mixins';
167+
import { CfnLogGroupPropsMixin } from '@aws-cdk/mixins-preview/aws_logs/mixins';
168+
import { CfnFunctionPropsMixin } from '@aws-cdk/mixins-preview/aws_lambda/mixins';
169+
import { CfnTablePropsMixin } from '@aws-cdk/mixins-preview/aws_dynamodb/mixins';
168170
```
169171

170172
## Error Handling

packages/@aws-cdk/mixins-preview/jest.config.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config');
33
/** @type {import('ts-jest').JestConfigWithTsJest} */
44
module.exports = {
55
...baseConfig,
6-
transform: {
7-
'^.+\\.tsx?$': ['ts-jest'],
8-
},
6+
coveragePathIgnorePatterns: [
7+
'scripts',
8+
'\\.generated\\.[jt]s$',
9+
'.warnings.jsii.js$'
10+
],
911
};

packages/@aws-cdk/mixins-preview/lib/core/applicator.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,31 @@ export class MixinApplicator {
2121
/**
2222
* Applies a mixin to selected constructs.
2323
*/
24-
apply(mixin: IMixin): this {
24+
apply(...mixins: IMixin[]): this {
2525
const constructs = this.selector.select(this.scope);
2626
for (const construct of constructs) {
27-
if (mixin.supports(construct)) {
28-
const errors = mixin.validate?.(construct) ?? [];
29-
if (errors.length > 0) {
30-
throw new ValidationError(`Mixin validation failed: ${errors.join(', ')}`, this.scope);
27+
for (const mixin of mixins) {
28+
if (mixin.supports(construct)) {
29+
mixin.applyTo(construct);
3130
}
32-
mixin.applyTo(construct);
3331
}
3432
}
3533
return this;
3634
}
3735

3836
/**
39-
* Applies a mixin and requires that it be applied to at least one construct.
37+
* Applies a mixin and requires that it be applied to all constructs.
4038
*/
41-
mustApply(mixin: IMixin): this {
39+
mustApply(...mixins: IMixin[]): this {
4240
const constructs = this.selector.select(this.scope);
43-
let applied = false;
4441
for (const construct of constructs) {
45-
if (mixin.supports(construct)) {
46-
const errors = mixin.validate?.(construct) ?? [];
47-
if (errors.length > 0) {
48-
throw new ValidationError(`Mixin validation failed: ${errors.join(', ')}`, construct);
42+
for (const mixin of mixins) {
43+
if (!mixin.supports(construct)) {
44+
throw new ValidationError(`Mixin ${mixin.constructor.name} could not be applied to ${construct.constructor.name} but was requested to.`, this.scope);
4945
}
5046
mixin.applyTo(construct);
51-
applied = true;
5247
}
5348
}
54-
if (!applied) {
55-
throw new ValidationError(`Mixin ${mixin.constructor.name} could not be applied to any constructs`, this.scope);
56-
}
5749
return this;
5850
}
5951
}

packages/@aws-cdk/mixins-preview/lib/core/mixins.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ export interface IMixin {
2727
*/
2828
supports(construct: IConstruct): boolean;
2929

30-
/**
31-
* Validates the construct before applying the mixin.
32-
*/
33-
validate?(construct: IConstruct): string[];
34-
3530
/**
3631
* Applies the mixin functionality to the target construct.
3732
*/
@@ -60,9 +55,5 @@ export abstract class Mixin implements IMixin {
6055
return true;
6156
}
6257

63-
public validate(_construct: IConstruct): string[] {
64-
return [];
65-
}
66-
6758
abstract applyTo(construct: IConstruct): IConstruct;
6859
}

packages/@aws-cdk/mixins-preview/lib/with.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import { Mixins, ConstructSelector } from './core';
55

66
declare module 'constructs' {
77
interface IConstruct {
8-
with(mixin: IMixin): this;
8+
with(...mixin: IMixin[]): this;
99
}
1010

1111
interface Construct {
12-
with(mixin: IMixin): this;
12+
with(...mixin: IMixin[]): this;
1313
}
1414
}
1515

1616
// Hack the prototype to add .with() method
17-
(Construct.prototype as any).with = function(this: IConstruct, mixin: IMixin): IConstruct {
18-
Mixins.of(this, ConstructSelector.cfnResource()).mustApply(mixin);
17+
(Construct.prototype as any).with = function(this: IConstruct, ...mixin: IMixin[]): IConstruct {
18+
Mixins.of(this, ConstructSelector.cfnResource()).mustApply(...mixin);
1919
return this;
2020
};

0 commit comments

Comments
 (0)