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/budgets

v0.8.4

Published

Composable AWS Budgets builder with well-architected defaults and automatic SNS topic policies

Readme

@composurecdk/budgets

AWS Budgets builder for ComposureCDK.

This package provides a fluent builder for AWS::Budgets::Budget with well-architected defaults, percentage-threshold notification helpers, and automatic AWS::SNS::TopicPolicy wiring for SNS subscribers. It wraps the CDK L1 CfnBudget construct — there is no L2 for Budgets.

Budget Builder

import { createBudgetBuilder, email } from "@composurecdk/budgets";

const budget = createBudgetBuilder()
  .budgetName("AgentBudget")
  .limit({ amount: 50 })
  .notifyOnActual(100, { emails: [email("[email protected]")] })
  .build(stack, "AgentBudget");

Properties

Every field on CfnBudget.BudgetDataProperty that tends to be set by hand is surfaced as a fluent setter:

| Setter | Purpose | | ------------------- | --------------------------------------------------------------------- | | budgetName(name) | BudgetName — stable identifier in the console and across regions. | | budgetType(type) | BudgetTypeCOST, USAGE, RI_UTILIZATION, RI_COVERAGE, etc. | | timeUnit(unit) | TimeUnitDAILY, MONTHLY, QUARTERLY, ANNUALLY. | | limit({ amount }) | BudgetLimit — required for COST and USAGE budgets. | | costFilters(map) | CostFilters — e.g. { Service: ["AmazonEC2"] }. | | costTypes(types) | CostTypes passthrough. |

Notifications

Each notification takes a NotifySubscribers object with at most one sns topic and a list of validated emails — AWS Budgets caps every notification at 1 SNS subscriber plus up to 10 EMAIL subscribers. The shape encodes that constraint in the type system: passing two SNS topics is unrepresentable.

import { email } from "@composurecdk/budgets";

createBudgetBuilder()
  .limit({ amount: 100 })
  .notifyOnActual(80, { emails: [email("[email protected]")] }) // 80% ACTUAL → email
  .notifyOnForecasted(100, { sns: ref("alerts", (r) => r.topic) }) // 100% FORECASTED → SNS topic
  .notifyOnActual(100, {
    sns: killSwitchTopic,
    emails: [email("[email protected]")],
  }) // hard breach → automation + human
  .addNotification({
    notificationType: "ACTUAL",
    threshold: 120,
    thresholdType: "ABSOLUTE_VALUE",
    subscribers: { emails: [email("[email protected]")] },
  });

Email addresses must be constructed via email(string), which validates and brands the value — bare strings are rejected at compile time. The sns slot accepts an ITopic instance or a Resolvable<ITopic> reference to a topic owned by a sibling component.

Recommended Thresholds

createBudgetBuilder()
  .limit({ amount: 50 })
  .withRecommendedThresholds({ emails: [email("[email protected]")] });

Applies the AWS Cost Optimization pillar defaults: ACTUAL at 80% and FORECASTED at 100%.

Currency

limit({ amount, unit }) validates unit against the AWS-Budgets-supported ISO 4217 set (DEFAULT_BUDGET_CURRENCIES). Typos like "ZZZ" throw at synth instead of mid-deploy. Because the synth context cannot see an account's billing currency, anything other than "USD" also emits a non-fatal warning (@composurecdk/budgets:limit-currency) — verify the configured unit matches your billing currency before deploying.

Defaults

| Property | Default | Rationale | | ----------------------------------------- | ----------- | ----------------------------------------------------------------------------- | | budgetType | "COST" | Cost budgets are the most common; usage/RI/SP budgets are explicit overrides. | | timeUnit | "MONTHLY" | Aligns with AWS billing cycles. | | limitUnit | "USD" | Matches the AWS Billing console default. | | recommendedThresholds.actualPercent | 80 | Early-warning threshold before breach. | | recommendedThresholds.forecastedPercent | 100 | Trending-over-budget alert for the period. |

Exported as BUDGET_DEFAULTS.

Automatic SNS Topic Policies

When at least one notification subscriber is an SNS topic, the builder creates a matching AWS::SNS::TopicPolicy granting SNS:Publish to the budgets.amazonaws.com service principal. Without that policy, budget notifications to SNS silently fail to deliver — one of the most common footguns when wiring Budgets by hand.

The created TopicPolicy constructs are returned on result.topicPolicies, keyed by the topic's fully-qualified node path (unique within the CDK app).

Recommended Alarms

AWS Budgets does not publish per-budget CloudWatch metrics, but the well-architected cost-monitoring pattern combines a budget with a CloudWatch alarm on AWS/Billing EstimatedCharges. The builder can create that alarm for you, but it is off by default — pass an estimatedCharges config to opt in.

| Alarm | Metric | Default behaviour | | ------------------ | ----------------------------------- | ----------------- | | estimatedCharges | EstimatedCharges (Maximum, 6 hours) | off |

treatMissingData defaults to notBreaching: missing datapoints from a quiet account are not treated as a breach.

const stack = new Stack(app, "BillingStack", { env: { region: "us-east-1" } });

createBudgetBuilder()
  .limit({ amount: 50 })
  .recommendedAlarms({
    estimatedCharges: { threshold: 50, currency: "USD" },
  })
  .build(stack, "AccountBudget");

The Budget itself is a global service and can be created from any region; only the alarm requires us-east-1 (see below).

Customising thresholds

createBudgetBuilder()
  .limit({ amount: 1000 })
  .recommendedAlarms({
    estimatedCharges: {
      threshold: 1000,
      currency: "USD",
      evaluationPeriods: 2,
      datapointsToAlarm: 2,
    },
  });

Disabling alarms

Disable the recommended alarm with recommendedAlarms({ estimatedCharges: false }), or disable all recommended alarms with recommendedAlarms(false). Custom alarms attached via addAlarm are unaffected by either form.

Custom alarms

import { Metric } from "aws-cdk-lib/aws-cloudwatch";

createBudgetBuilder()
  .limit({ amount: 1000 })
  .addAlarm("ec2EstimatedCharges", (a) =>
    a
      .metric(
        () =>
          new Metric({
            namespace: "AWS/Billing",
            metricName: "EstimatedCharges",
            dimensionsMap: { Currency: "USD", ServiceName: "AmazonEC2" },
            statistic: "Maximum",
          }),
      )
      .threshold(500)
      .greaterThan()
      .description("EC2 estimated charges exceeded $500."),
  );

Applying alarm actions

No alarm actions are configured by default. Wire SNS or other actions via alarmActionsPolicy (or an afterBuild hook) — for cross-region deployments, the policy applied to the us-east-1 monitoring stack covers both recommended and custom alarms.

Cross-region: AWS/Billing EstimatedCharges lives in us-east-1 only

The AWS/Billing EstimatedCharges metric is emitted in us-east-1 only, regardless of where your budgets and resources live. CloudWatch alarms are regional, so an alarm in any other region will never receive data. The combined builder emits a synth-time warning (@composurecdk/budgets:alarm-region) when used outside us-east-1, but the better approach is to route the alarm into a us-east-1 stack via createBudgetAlarmBuilder and compose().withStacks():

import { compose, ref } from "@composurecdk/core";
import {
  createBudgetBuilder,
  createBudgetAlarmBuilder,
  type BudgetBuilderResult,
} from "@composurecdk/budgets";

compose(
  {
    account: createBudgetBuilder()
      .budgetName("Account")
      .limit({ amount: 1000 })
      .recommendedAlarms(false), // suppress alarms in the budget's own stack

    accountAlarms: createBudgetAlarmBuilder()
      .budget(ref<BudgetBuilderResult>("account"))
      .recommendedAlarms({ estimatedCharges: { threshold: 1000, currency: "USD" } }),
  },
  { account: [], accountAlarms: ["account"] },
)
  .withStacks({
    account: appStack, //         any region — AWS::Budgets::Budget is global
    accountAlarms: monitoringStack, // us-east-1 — where AWS/Billing metrics live
  })
  .build(app, "App");

If your custom addAlarm definitions reference the budget construct, set crossRegionReferences: true on both stacks so CDK can export the budget's properties from the app stack and import them in the alarm stack. The same pattern is documented for CloudFront and Route 53 alarms, and codified in ADR-0004.

Build Result

interface BudgetBuilderResult {
  budget: CfnBudget;
  topicPolicies: Record<string, TopicPolicy>;
  alarms: Record<string, Alarm>;
}