Claude-skill-registry creating-subpackages

Patterns and standards for creating new AWS service subpackages in the monorepo. Use when adding new service packages like Lambda, DynamoDB, Step Functions, etc.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/creating-subpackages" ~/.claude/skills/majiayu000-claude-skill-registry-creating-subpackages && rm -rf "$T"
manifest: skills/data/creating-subpackages/SKILL.md
source content

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