creating-subpackages
About
This skill provides standardized patterns for creating new AWS service subpackages in a monorepo structure. It includes templates for package organization, factory functions, and testing conventions when adding services like Lambda or DynamoDB. Developers should use it to ensure consistency across all new service packages.
Quick Install
Claude Code
Recommended/plugin add https://github.com/majiayu000/claude-skill-registrygit clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/creating-subpackagesCopy and paste this command in Claude Code to install this skill
Documentation
Creating Subpackages Guide
This guide documents the patterns established when creating the Aurora package, which should be followed for all new service subpackages.
Package Structure
packages/
└── <service-name>/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ ├── index.ts # Package exports
│ ├── constructs/
│ │ └── <resource>-<variant>.ts # Factory functions
│ ├── types/
│ │ ├── <resource>-base.ts # Base/shared types
│ │ └── <resource>-<variant>.ts # Variant-specific types
│ └── util/
│ └── <resource>-helpers.ts # Shared utilities
├── test/
│ └── <resource>-<variant>.test.ts
└── docs/
└── <resource>-guide.md
Factory Function Pattern
No ID Parameter
Factory functions should NOT take an id parameter. Use the resource name from props as the construct ID.
// ❌ Old pattern - DO NOT USE
export const createFunction = (scope: Construct, id: string, props: FunctionProps) => {
return new Function(scope, id, { ... });
};
// ✅ New pattern - USE THIS
export const createFunction = (scope: Construct, props: FunctionProps): FunctionResources => {
return new Function(scope, props.functionName, { ... });
};
Resource ID Naming
Use the resource name from props for ALL resource IDs to prevent collisions:
export const createFunction = (scope: Construct, props: FunctionProps): FunctionResources => {
const role = new Role(scope, `${props.functionName}-role`, { ... });
const logGroup = new LogGroup(scope, `${props.functionName}-logs`, { ... });
const func = new Function(scope, props.functionName, {
functionName: props.functionName,
role,
logGroup,
// ... other config
});
return { function: func, role, logGroup };
};
Type Definitions
Base Configuration Type
Create a base type for shared configuration across variants:
// types/<resource>-base.ts
export type ResourceBaseConfig = {
/** Resource name - used as construct ID and for resource naming */
resourceName: string;
/** Common config properties */
environment?: string;
tags?: Record<string, string>;
// ... other common properties
};
export type ResourceResources = {
/** The primary resource created */
resource: PrimaryResource;
/** Related resources that may be useful to consumers */
role?: IRole;
logGroup?: ILogGroup;
};
Variant-Specific Types
Extend the base for specific variants:
// types/<resource>-<variant>.ts
import {ResourceBaseConfig} from './resource-base';
export type VariantResourceProps = ResourceBaseConfig & {
/** Variant-specific properties */
variantOption: string;
};
Avoid Unnecessary Type Wrappers
Don't use Omit<> for properties that don't exist:
// ❌ Don't do this
export const CONFIG: Omit<ResourceProps, 'nonExistentProp'> = { ... };
// ✅ Do this
export const CONFIG: ResourceProps = { ... };
Automatic Resource Creation
Constructs should create necessary supporting resources automatically:
export const createFunction = (scope: Construct, props: FunctionProps): FunctionResources => {
// Create role automatically if not provided
const role =
props.existingRole ||
new Role(scope, `${props.functionName}-role`, {
roleName: props.roleName || `${props.functionName}-execution-role`,
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
});
// Create log group automatically
const logGroup = new LogGroup(scope, `${props.functionName}-logs`, {
logGroupName: `/aws/lambda/${props.functionName}`,
retention: props.logRetention || RetentionDays.ONE_WEEK,
removalPolicy: props.removalPolicy || RemovalPolicy.DESTROY,
});
const func = new Function(scope, props.functionName, {
functionName: props.functionName,
role,
logGroup,
// ... other config
});
return {function: func, role, logGroup};
};
Default Values and Validation
Provide Sensible Defaults
const commonConfig = {
timeout: props.timeout || Duration.seconds(30),
memorySize: props.memorySize || 256,
retries: props.retries ?? 2, // Use ?? for number defaults
// Conditional defaults based on other props
logRetention: props.enableDetailedLogging ? props.logRetention || RetentionDays.ONE_MONTH : RetentionDays.ONE_WEEK,
};
Validate Required Dependencies
if (props.enableFeature && !props.requiredConfig) {
throw new Error(`requiredConfig must be set when enableFeature is true`);
}
Example Stack Pattern
Config Resolver for Local Overrides
// examples/<service>/config/config-resolver.ts
export interface LocalConfig {
vpcId?: string;
subnetIds?: string[];
// ... other overridable values
}
export class ConfigResolver {
private static localConfig: LocalConfig | undefined;
private static localConfigLoaded = false;
private static loadLocalConfig(): LocalConfig | undefined {
if (!this.localConfigLoaded) {
try {
const {LOCAL_CONFIG} = require('../../environments.local');
this.localConfig = LOCAL_CONFIG;
} catch {
this.localConfig = undefined;
}
this.localConfigLoaded = true;
}
return this.localConfig;
}
private static resolve<T extends LocalConfig>(baseConfig: T): T {
const localConfig = this.loadLocalConfig();
if (localConfig) {
return {...baseConfig, ...localConfig};
}
return baseConfig;
}
public static getDevConfig(): ResourceProps {
return this.resolve(DEV_CONFIG);
}
}
Config Files with Placeholders
// examples/<service>/config/<resource>-dev.ts
export const DEV_CONFIG: ResourceProps = {
resourceName: 'my-resource-dev',
// Use placeholder values for sensitive/environment-specific config
vpcId: 'vpc-xxxxxxxxxxxxxxxxx',
subnetIds: ['subnet-xxxxxxxxxxxxxxxxx', 'subnet-yyyyyyyyyyyyyyyyy'],
// Dev-appropriate settings
enableDetailedLogging: true,
retentionPolicy: RemovalPolicy.DESTROY,
deletionProtection: false,
};
Example Stack
// examples/<service>/stacks/<resource>-dev-stack.ts
export class ResourceDevStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const config = ConfigResolver.getDevConfig();
const {resource, role} = createResource(this, {
...config,
});
}
}
Package Exports
Structure the index.ts to export everything consumers need:
// src/index.ts
// Construct functions
export {createResourceVariantA} from './constructs/resource-variant-a';
export {createResourceVariantB} from './constructs/resource-variant-b';
// Types - organize by category
export type {
// Base types
ResourceBaseConfig,
ResourceResources,
// Variant types
VariantAResourceProps,
VariantBResourceProps,
// Helper types
ParameterGroupConfig,
MonitoringConfig,
} from './types';
// Utilities - only export what's useful to consumers
export {createParameterGroup, createMonitoringAlarm} from './util/resource-helpers';
Testing Requirements
Test Structure
describe('Resource Variant A', () => {
let app: App;
let stack: Stack;
beforeEach(() => {
app = new App({
context: {
/* mock context */
},
});
stack = new Stack(app, 'TestStack', {
env: {account: '123456789012', region: 'us-east-1'},
});
});
test('creates resource with basic configuration', () => {
const {resource} = createResourceVariantA(stack, {
resourceName: 'test-resource',
// ... required props
});
const template = Template.fromStack(stack);
template.resourceCountIs('AWS::Service::Resource', 1);
template.hasResourceProperties('AWS::Service::Resource', {
Property: 'ExpectedValue',
});
});
test('creates supporting resources automatically', () => {
createResourceVariantA(stack, {
resourceName: 'test-resource',
});
const template = Template.fromStack(stack);
template.resourceCountIs('AWS::IAM::Role', 1);
template.resourceCountIs('AWS::Logs::LogGroup', 1);
});
});
Documentation Standards
TSDoc Requirements
/**
* Creates a [Service] [Resource] with [key features].
*
* @remarks
* This construct creates a production-ready resource with:
* - Feature 1
* - Feature 2
* - Feature 3
*
* @param scope - The construct scope
* @param props - Configuration properties
* @returns Resources including the primary resource and supporting resources
*
* @example
* ```typescript
* import { createResource } from '@cdk-constructs/<service>';
* import { Duration } from 'aws-cdk-lib';
*
* const { resource, role } = createResource(this, {
* resourceName: 'my-resource',
* timeout: Duration.seconds(30),
* memorySize: 512,
* });
* ```
*
* @see {@link ResourceProps} for configuration options
* @see https://docs.aws.amazon.com/service/resource.html
* @public
*/
Common Patterns
Environment-Based Configuration
const isProd = props.environment === 'prod';
const config = {
// Expensive features gated to prod
enableBackups: isProd,
multiAZ: isProd,
// Safe defaults for non-prod
deletionProtection: isProd,
removalPolicy: isProd ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY,
};
Conditional Resource Creation
// Create optional resources based on configuration
const kmsKey =
props.createKmsKey && !props.existingKmsKey
? new Key(scope, `${props.resourceName}-kms`, {
enableKeyRotation: true,
removalPolicy: RemovalPolicy.RETAIN,
})
: props.existingKmsKey;
Helper Functions
// util/resource-helpers.ts
export const createParameterGroup = (scope: Construct, props: ParameterGroupConfig): ParameterGroup => {
return new ParameterGroup(scope, `${props.name}-params`, {
description: props.description,
parameters: props.parameters || {},
});
};
AWS Service-Specific Patterns
Valid Values Documentation
Document valid values for service-specific properties:
/**
* Instance type for the resource.
*
* @remarks
* Valid instance classes:
* - T3: Burstable (MICRO, SMALL, MEDIUM, LARGE, XLARGE, XLARGE2)
* - M6G: General purpose Graviton2 (LARGE and up)
* - R6G: Memory optimized Graviton2 (LARGE and up, no MEDIUM)
*/
instanceType: InstanceType;
Service Limits and Defaults
// Validate against service limits
if (props.timeout && props.timeout.toSeconds() > 900) {
throw new Error('Lambda timeout cannot exceed 900 seconds');
}
// Use service-specific defaults
const monitoringInterval = props.enableEnhancedMonitoring
? props.monitoringInterval || Duration.seconds(60) // RDS default
: undefined;
Integration with Root Project
Add to bin/environment.ts
export type ProjectEnvironment = EnvironmentConfig & {
// ... existing configs
myResource?: Partial<MyResourceProps>;
};
export const integrationEnvironments: ProjectEnvironment[] = [
{
...devEnv,
myResource: {
resourceName: 'my-resource-dev',
},
},
];
Add to bin/app.ts
import {MyResourceStack} from '../examples/<service>/stacks/my-resource-stack';
integrationEnvironments.forEach(env => {
// ... existing stacks
if (env.myResource) {
new MyResourceStack(app, `my-resource-${env.name}`, envProps);
}
});
Checklist for New Subpackages
- Package structure follows standard layout
- Factory functions use resource name from props (no id parameter)
- Resource IDs use resource name for specificity
- Supporting resources created automatically
- Types properly structured (base + variants)
- No unnecessary
Omit<>type wrappers - Sensible defaults provided
- Service-specific validations implemented
- Config resolver pattern for examples
- Placeholder values in base configs
- Example stacks for dev and prod
- Comprehensive TSDoc documentation
- Test coverage for all variants
- Package exports properly organized
- Package README with usage examples
- Root README.md updated with new package (compatibility matrix, dependency resolution, installation, build commands, publish commands, workspace structure)
- Integration with root project complete
GitHub Repository
Related Skills
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
