# @othree.io/cdk

Functional, composition-based AWS CDK utilities. Thin wrappers around common CDK constructs with a consistent curried API and dependency injection.

## Install

```bash
npm install @othree.io/cdk
```

### Peer dependencies

```bash
npm install aws-cdk-lib constructs @othree.io/optional @aws-sdk/util-dynamodb
```

## API pattern

Every factory follows the same curried shape:

```
withX(deps)(input, configuration?)
```

Dependencies (stack, defaults) are injected first, returning a reusable builder. Optional per-call `configuration` overrides are merged on top of the defaults.

```ts
const createQueue = sqs.withQueue({
  stack: myStack,
  defaultConfiguration: { retentionPeriod: Duration.days(14) },
})

const ordersQueue = createQueue({ id: 'OrdersQueue' })
const eventsQueue = createQueue({ id: 'EventsQueue' })
```

## Modules

- [stack](#stack) -- Stack creation with default tags
- [core](#core) -- Resource ID generation and account helpers
- [lambda](#lambda) -- Lambda functions with aliases, auto-scaling, and ARN builders
- [dynamo](#dynamo) -- DynamoDB tables
- [sqs](#sqs) -- SQS queues and ARN builders
- [sns](#sns) -- SNS topics, SQS subscriptions, and Chisel filter policies
- [restApi](#restapi) -- API Gateway REST APIs with functional route composition
- [httpApi](#httpapi) -- API Gateway V2 HTTP APIs with JWT auth and route composition
- [s3](#s3) -- S3 buckets and local asset deployments
- [fargate](#fargate) -- ECS Fargate services
- [rds](#rds) -- Serverless Aurora RDS clusters
- [vpc](#vpc) -- VPC peering with bidirectional routes
- [customResource](#customresource) -- CloudFormation custom resources for SDK calls
- [usagePlan](#usageplan) -- API Gateway usage plan stage attachment
- [utils](#utils) -- Generic functional `compose` utility

---

### stack

Create a CloudFormation stack with default tags (Project, ApplicationId, Environment, Owner, Version).

```ts
import { stack } from '@othree.io/cdk'
import { App } from 'aws-cdk-lib'

const app = new App()

const defaultConfig = stack.withDefaultConfiguration(
  { project: 'Acme', environment: 'dev', owner: 'platform-team' },
  'order-service'
)

const myStack = stack.withStack({ defaultConfiguration: defaultConfig, app })({
  id: 'OrderServiceStack',
})

// add extra tags
stack.withTag({ scope: myStack })({ key: 'CostCenter', value: '1234' })
```

---

### core

Resource ID generation with a 65-character limit and stack account helpers.

```ts
import { core } from '@othree.io/cdk'

const buildId = core.id({
  version: 'v1',
  input: { prefix: 'acme', suffix: 'dev' },
})

buildId('order-service') // 'acme-order-service-dev-V1'

const account = core.getStackAccountFromEnv()
// { accountId: '123456789012', region: 'us-east-1' }
```

---

### lambda

Lambda functions with versioned aliases, optional auto-scaling, and ARN builders.

```ts
import { lambda } from '@othree.io/cdk'
import { Duration } from 'aws-cdk-lib'
import { Architecture, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda'

// create
const fn = lambda.withLambda({ stack: myStack, aliasName: 'live' })({
  functionName: 'ProcessOrder',
  codePath: './dist',
  handler: 'index.handler',
  lambdaConfiguration: {
    runtime: Runtime.NODEJS_LATEST,
    tracing: Tracing.ACTIVE,
    timeout: Duration.seconds(30),
    memorySize: 256,
    architecture: Architecture.ARM_64,
  },
  scalingConfiguration: {
    minCapacity: 1,
    maxCapacity: 20,
    utilizationTarget: 0.5,
  },
})

// build ARN
const arn = lambda.functionArnFromName({ account })('ProcessOrder', 'live')

// import existing
const imported = lambda.importFunction({ stack: myStack })({
  arn: 'arn:aws:lambda:us-east-1:123456789012:function:ExistingFn',
  id: 'ImportedFn',
})
```

---

### dynamo

DynamoDB tables with default configuration overrides.

```ts
import { dynamo } from '@othree.io/cdk'
import { AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb'
import { RemovalPolicy } from 'aws-cdk-lib'

const createTable = dynamo.withTable({
  defaultConfiguration: {
    billingMode: BillingMode.PAY_PER_REQUEST,
    removalPolicy: RemovalPolicy.DESTROY,
  },
  stack: myStack,
})

const table = createTable({
  id: 'OrdersTable',
  partitionKey: { name: 'pk', type: AttributeType.STRING },
  sortKey: { name: 'sk', type: AttributeType.STRING },
})
```

---

### sqs

SQS queues with dead-letter queue support and ARN builders.

```ts
import { sqs } from '@othree.io/cdk'
import { Duration, RemovalPolicy } from 'aws-cdk-lib'

const createQueue = sqs.withQueue({
  defaultConfiguration: {
    retentionPeriod: Duration.days(14),
    removalPolicy: RemovalPolicy.DESTROY,
  },
  stack: myStack,
})

const dlq = createQueue({ id: 'OrdersDLQ' })
const queue = createQueue({ id: 'OrdersQueue', dlq: { queue: dlq, maxReceiveCount: 5 } })

const arn = sqs.queueArnFromName({ account })('OrdersQueue')
```

---

### sns

SNS topics, SQS subscriptions, and Chisel event-sourcing filter policies.

```ts
import { sns } from '@othree.io/cdk'

// topic
const topic = sns.withTopic({ stack: myStack })({ id: 'OrderEvents' })

// Chisel filter policy (auto-appends health-check sentinels)
const filterPolicy = sns.withChiselFilterPolicy({
  bcs: ['order-bc', 'payment-bc'],
  eventTypes: ['OrderCreated', 'PaymentProcessed'],
})

// subscribe SQS to SNS
sns.withSqsSubscription({ filterPolicy })({
  topic,
  queue,
  dlq,
})

const arn = sns.topicArnFromName({ account })('OrderEvents')
```

---

### restApi

API Gateway REST APIs with functional route composition.

```ts
import { restApi } from '@othree.io/cdk'

const api = restApi.withRestApi({
  defaultConfiguration: restApi.withDefaultApiConfiguration(),
  stack: myStack,
})({ apiName: 'OrdersAPI' })

restApi.composeRestApi(
  api,
  restApi.withResource('/orders', { verb: 'GET', fn: listOrdersFn }),
  restApi.withResource('/orders', { verb: 'POST', fn: createOrderFn }),
  restApi.withResource('/orders/{orderId}', { verb: 'GET', fn: getOrderFn }),
  restApi.withResource('/orders/{orderId}', {
    verb: 'PUT',
    fn: updateOrderFn,
    props: { apiKeyRequired: true },
  }),
)
```

---

### httpApi

API Gateway V2 HTTP APIs with JWT authorizer and route composition.

```ts
import { httpApi } from '@othree.io/cdk'
import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2'

const authConfig = httpApi.withAuthorizerConfiguration(
  'CognitoAuthorizer',
  'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123',
  ['my-app-client-id']
)

const api = httpApi.withHttpApi({
  defaultConfiguration: {
    ...httpApi.withDefaultApiConfiguration(),
    ...authConfig,
  },
  stack: myStack,
})({ apiName: 'OrdersAPI' })

httpApi.composedHttpApi({ defaultOptions: {} })(
  api,
  httpApi.withRoute('/orders', { method: HttpMethod.GET, fn: listOrdersFn }),
  httpApi.withRoute('/orders/{orderId}', { method: HttpMethod.PUT, fn: updateOrderFn }),
)
```

---

### s3

S3 buckets and local asset deployments.

```ts
import { s3 } from '@othree.io/cdk'

const bucket = s3.withBucket({ stack: myStack })({ name: 'OrderAssets' })

s3.withLocalAssetsBucketDeployment({ stack: myStack })({
  name: 'DeployAssets',
  assetsPath: './public',
  bucket,
})
```

---

### fargate

ECS Fargate services with task roles and CloudWatch logging.

```ts
import { fargate } from '@othree.io/cdk'

const service = fargate.withFargateService({
  stack: myStack,
  defaultConfiguration: { cpu: 256, memoryLimitMiB: 512, publicIp: true },
})({
  name: 'OrderProcessor',
  image: dockerImage,
  environment: { NODE_ENV: 'production' },
  policies: [policyStatement],
  taskCount: 2,
})
```

---

### rds

Serverless Aurora RDS clusters.

```ts
import { rds } from '@othree.io/cdk'
import { AuroraCapacityUnit } from 'aws-cdk-lib/aws-rds'

const serverlessConfig = rds.withServerlessRdsConfiguration({
  serverlessScalingOptions: {
    minCapacity: AuroraCapacityUnit.ACU_2,
    maxCapacity: AuroraCapacityUnit.ACU_4,
  },
})('orders', 'admin')

const engine = rds.withAuroraPostgresSql10EngineConfiguration({
  stack: myStack,
  parameterGroupId: 'pg-id',
})

const credentials = rds.withDatabaseCredentials({ stack: myStack })('OrdersSecret', 'admin')

const cluster = rds.withServerlessRds({
  stack: myStack,
  serverlessRdsConfiguration: serverlessConfig,
  engineConfiguration: engine,
})({
  clusterName: 'OrdersCluster',
  selectedSubnets: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_ISOLATED }),
  vpc,
  credentials: Credentials.fromSecret(credentials),
})
```

---

### vpc

VPC peering connections with bidirectional routes.

```ts
import { vpc } from '@othree.io/cdk'
import { SubnetType } from 'aws-cdk-lib/aws-ec2'

vpc.withVpcPeering({ stack: myStack, withId: buildId })({
  fromVpcPeering: {
    vpc: vpcA,
    subnetSelection: { subnetType: SubnetType.PUBLIC },
  },
  toVpcPeering: {
    vpc: vpcB,
    subnetSelection: { subnetType: SubnetType.PRIVATE_ISOLATED },
  },
})
```

---

### customResource

CloudFormation custom resources backed by AWS SDK calls (Lambda invoke, DynamoDB batch write).

```ts
import { customResource } from '@othree.io/cdk'

const config = customResource.withDefaultCustomResourceConfiguration()

const onCreate = customResource.withLambdaSdkCall({
  payload: { action: 'seed' },
  functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:SeedFn',
  hash: 'seed-v1',
})

customResource.withCustomResource({
  stack: myStack,
  customResourceConfiguration: config,
  onCreateSdkCall: onCreate,
})({ name: 'SeedDatabase' })
```

`withDynamoBatchWriteCall` is also available for DynamoDB BatchWriteItem operations (max 25 items).

---

### usagePlan

Attach an API stage to an existing usage plan via custom resource.

```ts
import { usagePlan } from '@othree.io/cdk'

usagePlan.addApiStageToUsagePlan(myStack, 'AttachStage', {
  UsagePlanId: 'abc123',
  ApiId: api.restApiId,
  Stage: 'release',
})
```

---

### utils

Generic functional `compose` utility used internally by the API modules.

```ts
import { utils } from '@othree.io/cdk'

const result = utils.compose(
  initialValue,
  transformA,
  transformB,
  transformC,
)
```

## Module support

This package ships both ESM and CJS builds. It works with `import` and `require()` out of the box.

## Commit message format

This project uses [semantic-release](https://semantic-release.gitbook.io/semantic-release/) with [Angular Commit Message Conventions](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format).

| Commit message | Release type |
|----------------|--------------|
| `fix(lambda): correct ARN format` | Patch |
| `feat(sqs): add dead-letter queue support` | Minor |
| `feat(core): remove deprecated id format`<br>`BREAKING CHANGE: ...` | Major |

## License

ISC
