npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@composurecdk/dynamodb

v0.9.0

Published

Composable DynamoDB table builder with well-architected defaults

Downloads

785

Readme

@composurecdk/dynamodb

DynamoDB table builders for ComposureCDK.

This package provides fluent builders for DynamoDB tables with secure, AWS-recommended defaults and built-in CloudWatch alarms. It ships two builders, one per CDK construct:

| Factory | CDK construct | CloudFormation resource | Use for | | ------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------- | | createTableV2Builder() | TableV2 | AWS::DynamoDB::GlobalTable | New tables (recommended). | | createTableBuilder() | Table | AWS::DynamoDB::Table | importSource (S3 bulk import) and parity with classic tables. |

Both share the same well-architected defaults intent, the same recommended alarms, and the same ref()-able result shape — they differ only in the underlying CDK prop surface.

Which builder should I use?

For new tables, prefer createTableV2Builder(). A single-region TableV2 bills the same as a classic table, but it future-proofs the one irreversible part of the decision: you can add cross-region replicas later without replacing the table. ITableV2 extends ITable, so a built TableV2 works everywhere an ITable is expected (IAM grants, DynamoEventSource) — composition wiring is identical for both builders.

Reach for the classic createTableBuilder() when you need an importSource (S3 bulk import is V1-only, with no TableV2 equivalent) or parity with existing classic tables.

⚠️ Table and TableV2 are different CloudFormation resources (AWS::DynamoDB::Table vs. AWS::DynamoDB::GlobalTable). CloudFormation cannot migrate between them in place — swapping the construct on an existing table is a resource replacement, which deletes and recreates the table. Choose deliberately up front.

TableV2 builder (recommended)

import { AttributeType, StreamViewType } from "aws-cdk-lib/aws-dynamodb";
import { createTableV2Builder } from "@composurecdk/dynamodb";

const orders = createTableV2Builder()
  .partitionKey({ name: "orderId", type: AttributeType.STRING })
  .sortKey({ name: "createdAt", type: AttributeType.NUMBER })
  .dynamoStream(StreamViewType.NEW_AND_OLD_IMAGES)
  .build(stack, "Orders");

Every TablePropsV2 property is available as a fluent setter. Note the V2 prop shape differs from the classic one — billing is a single .billing(Billing.onDemand()) helper, encryption is .encryption(TableEncryptionV2.awsManagedKey()), and the stream is .dynamoStream(StreamViewType…). partitionKey is the one property the builder does not default — the key schema is workload-specific, so it must be set before build().

Classic Table builder

import { AttributeType, StreamViewType } from "aws-cdk-lib/aws-dynamodb";
import { createTableBuilder } from "@composurecdk/dynamodb";

const orders = createTableBuilder()
  .partitionKey({ name: "orderId", type: AttributeType.STRING })
  .sortKey({ name: "createdAt", type: AttributeType.NUMBER })
  .stream(StreamViewType.NEW_AND_OLD_IMAGES)
  .build(stack, "Orders");

Every TableProps property is available as a fluent setter, including importSource for S3 bulk import — the main reason to choose the classic builder.

Secure Defaults

Both builders apply the same well-architected intent, encoded against each construct's prop shape. Each default can be overridden via the builder's fluent API.

| Intent | TableV2 (TABLE_V2_DEFAULTS) | Classic (TABLE_DEFAULTS) | Rationale | | ---------------------- | ----------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | On-demand billing | billing: Billing.onDemand() | billingMode: BillingMode.PAY_PER_REQUEST | No forecasting, scales with traffic, avoids throttling from under-provisioning. Switch to provisioned for steady, predictable workloads. | | Encryption at rest | encryption: TableEncryptionV2.awsManagedKey() | encryption: TableEncryption.AWS_MANAGED | Encrypts with the AWS-managed aws/dynamodb KMS key — visible in the account and logged in CloudTrail, unlike the free AWS-owned default key. Bring-your-own KMS is opt-in. (Security Pillar) | | Point-in-time recovery | pointInTimeRecoverySpecification: { … } | pointInTimeRecoverySpecification: { … } | Continuous backups — restore to any second in the preceding 35 days. (PITR) | | Deletion protection | deletionProtection: true | deletionProtection: true | Blocks table deletion (API, console, or cdk destroy) until explicitly disabled. Override with .deletionProtection(false) for ephemeral tables. |

The defaults are exported for visibility and testing:

import { TABLE_DEFAULTS, TABLE_V2_DEFAULTS } from "@composurecdk/dynamodb";

DynamoDB Streams

Enable a stream with .dynamoStream(StreamViewType…) (TableV2) or .stream(StreamViewType…) (classic). The build result surfaces the stream ARN so a downstream component can wire a consumer:

const result = createTableV2Builder()
  .partitionKey({ name: "pk", type: AttributeType.STRING })
  .dynamoStream(StreamViewType.NEW_AND_OLD_IMAGES)
  .build(stack, "Events");

result.tableStreamArn; // string — feed into a Lambda DynamoEventSource, etc.
result.table; // the table itself, which a DynamoEventSource consumes

Neither construct exposes a distinct stream construct — the stream is an attribute of the table — so the result exposes tableStreamArn directly. It is undefined when no stream is configured, on both builders, so a ref() consumer can branch on its presence.

Recommended Alarms

Both builders create the same AWS-recommended CloudWatch alarms by default. No alarm actions are configured — access alarms from the build result to add SNS topics or other actions.

| Alarm | Metric | Default threshold | Rationale | | --------------------- | ------------------------------------------- | ----------------- | ---------------------------------------------------------------- | | systemErrors | SystemErrors (Sum across core ops, 1 min) | > 0 | Server-side (HTTP 500) faults — a table availability signal. | | readThrottleEvents | ReadThrottleEvents (Sum, 1 min) | > 0 | Read throttling — hot partition or insufficient read capacity. | | writeThrottleEvents | WriteThrottleEvents (Sum, 1 min) | > 0 | Write throttling — hot partition or insufficient write capacity. |

The thresholds are deliberately strict — a healthy table should sit at zero. Tune them up for workloads where brief, self-correcting throttling under burst is acceptable.

SystemErrors is emitted per-operation, and a CloudWatch alarm on a metric-math expression can reference at most 10 metrics, so the systemErrors alarm sums the ten core data-plane operations (GetItem, BatchGetItem, Query, Scan, PutItem, UpdateItem, DeleteItem, BatchWriteItem, TransactGetItems, TransactWriteItems). The PartiQL statement operations and the stream GetRecords operation are excluded; add a custom alarm if your errors live there.

The defaults are exported as TABLE_ALARM_DEFAULTS for visibility and testing:

import { TABLE_ALARM_DEFAULTS } from "@composurecdk/dynamodb";

What is not alarmed by default

  • Account-level utilization alarms (AccountProvisionedReadCapacityUtilization, AccountProvisionedWriteCapacityUtilization) from the AWS recommended-alarms list are account-scoped, not table-scoped, so they do not belong to a per-table builder.
  • Provisioned-capacity utilization (consumed vs. provisioned) only applies to provisioned tables; the default billing mode here is on-demand. Add it via addAlarm on a provisioned table.
  • UserErrors / ConditionalCheckFailedRequests are caller- or application-level signals (HTTP 400) and too workload-dependent for a useful generic threshold.

Customizing thresholds

Override individual alarm properties via recommendedAlarms. Unspecified fields keep their defaults.

const table = createTableV2Builder()
  .partitionKey({ name: "pk", type: AttributeType.STRING })
  .recommendedAlarms({
    readThrottleEvents: { threshold: 10, evaluationPeriods: 3 },
  });

Disabling alarms

builder.recommendedAlarms(false);
// or
builder.recommendedAlarms({ enabled: false });
// or individually
builder.recommendedAlarms({ writeThrottleEvents: false });

Custom alarms

Add custom alarms alongside the recommended ones via addAlarm. The callback receives an AlarmDefinitionBuilder typed to ITable, so the metric factory has access to the table's metric helpers.

import { Duration } from "aws-cdk-lib";

const table = createTableV2Builder()
  .partitionKey({ name: "pk", type: AttributeType.STRING })
  .addAlarm("userErrors", (alarm) =>
    alarm
      .metric((table) => table.metricUserErrors({ period: Duration.minutes(5) }))
      .threshold(5)
      .greaterThan()
      .description("Table is returning client-side (HTTP 400) errors."),
  );

Applying alarm actions

Alarms are returned in the build result as Record<string, Alarm>:

const result = createTableV2Builder()
  .partitionKey({ name: "pk", type: AttributeType.STRING })
  .build(stack, "Orders");

const alertTopic = new Topic(stack, "AlertTopic");
for (const alarm of Object.values(result.alarms)) {
  alarm.addAlarmAction(new SnsAction(alertTopic));
}

For composing the alarm-actions wiring across multiple builders in a single compose system, see alarmActionsPolicy in @composurecdk/cloudwatch.

Composition

Both builders implement Lifecycle, so they slot into a compose system and expose a ref()-able stream for wiring a consumer (e.g. a Lambda DynamoEventSource):

import { compose } from "@composurecdk/core";
import { createTableV2Builder } from "@composurecdk/dynamodb";
import { createFunctionBuilder } from "@composurecdk/lambda";

const system = compose(
  {
    orders: createTableV2Builder()
      .partitionKey({ name: "pk", type: AttributeType.STRING })
      .dynamoStream(StreamViewType.NEW_AND_OLD_IMAGES),
    processor: createFunctionBuilder(),
  },
  { orders: [], processor: ["orders"] },
);