@composurecdk/budgets 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ import { CfnBudget } from "aws-cdk-lib/aws-budgets";
2
+ import { type Alarm } from "aws-cdk-lib/aws-cloudwatch";
3
+ import type { TopicPolicy } from "aws-cdk-lib/aws-sns";
4
+ import type { IConstruct } from "constructs";
5
+ import { type IBuilder, type Lifecycle } from "@composurecdk/core";
6
+ import { AlarmDefinitionBuilder } from "@composurecdk/cloudwatch";
7
+ import type { BudgetAlarmConfig } from "./alarm-config.js";
8
+ import { type BudgetSubscriber, type NotificationEntry } from "./notifications.js";
9
+ /**
10
+ * Spend limit for a cost or usage budget.
11
+ */
12
+ export interface BudgetLimit {
13
+ /** Numeric limit. */
14
+ amount: number;
15
+ /**
16
+ * Currency or usage unit. Defaults to
17
+ * {@link BUDGET_DEFAULTS.limitUnit} when omitted.
18
+ */
19
+ unit?: string;
20
+ }
21
+ /**
22
+ * Configuration properties for the budget builder.
23
+ */
24
+ export interface BudgetBuilderProps {
25
+ /** Name used for `BudgetName` in the `BudgetData` property. */
26
+ budgetName?: string;
27
+ /**
28
+ * One of `COST`, `USAGE`, `RI_UTILIZATION`, `RI_COVERAGE`,
29
+ * `SAVINGS_PLANS_UTILIZATION`, `SAVINGS_PLANS_COVERAGE`.
30
+ *
31
+ * @default "COST"
32
+ */
33
+ budgetType?: string;
34
+ /** @default "MONTHLY" */
35
+ timeUnit?: string;
36
+ /** Spend limit (required for COST and USAGE budgets). */
37
+ limit?: BudgetLimit;
38
+ /**
39
+ * CloudFormation `CostFilters` map. Keys are filter dimensions
40
+ * (`Service`, `Region`, `LinkedAccount`, `TagKeyValue`, …) and values
41
+ * are arrays of filter values.
42
+ */
43
+ costFilters?: Record<string, string[]>;
44
+ /** CloudFormation `CostTypes` passthrough. */
45
+ costTypes?: CfnBudget.CostTypesProperty;
46
+ /**
47
+ * Configuration for the AWS-recommended billing alarm.
48
+ *
49
+ * Off by default — pass an
50
+ * {@link BudgetAlarmConfig.estimatedCharges} entry to opt in. Set to
51
+ * `false` to suppress recommended alarms entirely; custom alarms added
52
+ * via {@link IBudgetBuilder.addAlarm} are unaffected.
53
+ *
54
+ * Note: `AWS/Billing EstimatedCharges` is emitted in `us-east-1` only.
55
+ * If this builder is used outside `us-east-1`, the synthesised alarm
56
+ * will never receive data — the builder emits a synth-time warning.
57
+ * For non-`us-east-1` stacks, suppress this builder's alarms with
58
+ * `recommendedAlarms: false` and create alarms in a `us-east-1` stack
59
+ * via {@link createBudgetAlarmBuilder}.
60
+ *
61
+ * @see BudgetAlarmConfig
62
+ * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/monitor_estimated_charges_with_cloudwatch.html
63
+ */
64
+ recommendedAlarms?: BudgetAlarmConfig | false;
65
+ }
66
+ /**
67
+ * The build output of an {@link IBudgetBuilder}.
68
+ */
69
+ export interface BudgetBuilderResult {
70
+ /** The `AWS::Budgets::Budget` construct. */
71
+ budget: CfnBudget;
72
+ /**
73
+ * `AWS::SNS::TopicPolicy` constructs created automatically for any SNS
74
+ * topic referenced as a notification subscriber, keyed by the topic's
75
+ * fully-qualified node path. Grants `budgets.amazonaws.com` permission
76
+ * to publish.
77
+ *
78
+ * `{}` when no SNS subscribers were configured.
79
+ */
80
+ topicPolicies: Record<string, TopicPolicy>;
81
+ /**
82
+ * CloudWatch alarms created for the budget.
83
+ *
84
+ * Includes both AWS-recommended alarms (`estimatedCharges`, off by
85
+ * default) and any custom alarms added via
86
+ * {@link IBudgetBuilder.addAlarm}. Empty unless the caller opts in via
87
+ * `recommendedAlarms` or `addAlarm`.
88
+ *
89
+ * No alarm actions are configured — apply them via the result or an
90
+ * `afterBuild` hook.
91
+ */
92
+ alarms: Record<string, Alarm>;
93
+ }
94
+ /**
95
+ * A fluent builder for configuring and creating an AWS Budget.
96
+ *
97
+ * Wraps the {@link CfnBudget} L1 construct (the CDK does not ship an L2
98
+ * for Budgets) with well-architected defaults, helpers for the
99
+ * percentage-threshold notification shape, and automatic
100
+ * `AWS::SNS::TopicPolicy` wiring for SNS subscribers.
101
+ *
102
+ * The builder can also create the AWS-recommended `EstimatedCharges`
103
+ * billing alarm; opt in via `recommendedAlarms`. For non-`us-east-1`
104
+ * stacks, route the alarms separately via
105
+ * {@link createBudgetAlarmBuilder}.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * createBudgetBuilder()
110
+ * .budgetName("AgentBudget")
111
+ * .limit({ amount: 50, unit: "GBP" })
112
+ * .notifyOnActual(100, ref("alerts", r => r.topic))
113
+ * .withRecommendedThresholds()
114
+ * .build(stack, "AgentBudget");
115
+ * ```
116
+ */
117
+ export type IBudgetBuilder = IBuilder<BudgetBuilderProps, BudgetBuilder>;
118
+ declare class BudgetBuilder implements Lifecycle<BudgetBuilderResult> {
119
+ #private;
120
+ props: Partial<BudgetBuilderProps>;
121
+ /**
122
+ * Add a notification that fires when ACTUAL spend crosses the given
123
+ * percentage of the budget limit.
124
+ *
125
+ * @param thresholdPercent - Percentage of the budget limit (e.g. `80`).
126
+ * For absolute-value thresholds, use {@link addNotification} directly.
127
+ * @param subscribers - One or more email addresses / SNS topics (or
128
+ * {@link Resolvable} refs to topics).
129
+ */
130
+ notifyOnActual(thresholdPercent: number, ...subscribers: BudgetSubscriber[]): this;
131
+ /**
132
+ * Add a notification that fires when FORECASTED spend crosses the
133
+ * given percentage of the budget limit.
134
+ *
135
+ * @param thresholdPercent - Percentage of the budget limit (e.g. `100`).
136
+ * For absolute-value thresholds, use {@link addNotification} directly.
137
+ */
138
+ notifyOnForecasted(thresholdPercent: number, ...subscribers: BudgetSubscriber[]): this;
139
+ /**
140
+ * Raw notification passthrough for callers that need the full
141
+ * CloudFormation surface (e.g. absolute-value thresholds).
142
+ */
143
+ addNotification(entry: NotificationEntry): this;
144
+ /**
145
+ * Apply the well-architected recommended notification thresholds:
146
+ *
147
+ * - `ACTUAL` at 80% — early warning before breach.
148
+ * - `FORECASTED` at 100% — trending-over-budget alert.
149
+ *
150
+ * Must be called with at least one subscriber; the same subscriber
151
+ * list is used for both thresholds.
152
+ *
153
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-best-practices.html
154
+ */
155
+ withRecommendedThresholds(...subscribers: BudgetSubscriber[]): this;
156
+ /**
157
+ * Adds a custom CloudWatch alarm against the budget. The configure
158
+ * callback receives a fresh {@link AlarmDefinitionBuilder} pre-set with
159
+ * the alarm's key; configure metric, threshold, comparison and any
160
+ * other options.
161
+ *
162
+ * Custom alarms are materialised in this builder's stack alongside any
163
+ * recommended alarms. Like the recommended `EstimatedCharges` alarm,
164
+ * custom alarms on `AWS/Billing` metrics will only fire when this
165
+ * stack is in `us-east-1` — the builder emits the same synth-time
166
+ * warning (`@composurecdk/budgets:alarm-region`) when used elsewhere.
167
+ * For non-`us-east-1` stacks, route alarms via
168
+ * {@link createBudgetAlarmBuilder} into a `us-east-1` stack.
169
+ */
170
+ addAlarm(key: string, configure: (alarm: AlarmDefinitionBuilder<CfnBudget>) => AlarmDefinitionBuilder<CfnBudget>): this;
171
+ build(scope: IConstruct, id: string, context?: Record<string, object>): BudgetBuilderResult;
172
+ }
173
+ /**
174
+ * Creates a new {@link IBudgetBuilder} for configuring an AWS Budget.
175
+ */
176
+ export declare function createBudgetBuilder(): IBudgetBuilder;
177
+ export {};
178
+ //# sourceMappingURL=budget-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget-builder.d.ts","sourceRoot":"","sources":["../src/budget-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAuB,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAW,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAIvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,SAAS,CAAC,iBAAiB,CAAC;IACxC;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,KAAK,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,MAAM,EAAE,SAAS,CAAC;IAClB;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;AAEzE,cAAM,aAAc,YAAW,SAAS,CAAC,mBAAmB,CAAC;;IAC3D,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAM;IAIxC;;;;;;;;OAQG;IACH,cAAc,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAIlF;;;;;;OAMG;IACH,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAItF;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAK/C;;;;;;;;;;OAUG;IACH,yBAAyB,CAAC,GAAG,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAcnE;;;;;;;;;;;;;OAaG;IACH,QAAQ,CACN,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,CAAC,KAAK,EAAE,sBAAsB,CAAC,SAAS,CAAC,KAAK,sBAAsB,CAAC,SAAS,CAAC,GACzF,IAAI;IAKP,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,mBAAmB;CAiFhG;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD"}
@@ -0,0 +1,139 @@
1
+ import { CfnBudget } from "aws-cdk-lib/aws-budgets";
2
+ import { Builder } from "@composurecdk/core";
3
+ import { AlarmDefinitionBuilder } from "@composurecdk/cloudwatch";
4
+ import { buildBudgetAlarms } from "./budget-alarm-builder.js";
5
+ import { BUDGET_DEFAULTS } from "./defaults.js";
6
+ import { resolveSubscribers, toCfnNotificationWithSubscribers, } from "./notifications.js";
7
+ import { createBudgetsTopicPolicies } from "./topic-policy.js";
8
+ class BudgetBuilder {
9
+ props = {};
10
+ #notifications = [];
11
+ #customAlarms = [];
12
+ /**
13
+ * Add a notification that fires when ACTUAL spend crosses the given
14
+ * percentage of the budget limit.
15
+ *
16
+ * @param thresholdPercent - Percentage of the budget limit (e.g. `80`).
17
+ * For absolute-value thresholds, use {@link addNotification} directly.
18
+ * @param subscribers - One or more email addresses / SNS topics (or
19
+ * {@link Resolvable} refs to topics).
20
+ */
21
+ notifyOnActual(thresholdPercent, ...subscribers) {
22
+ return this.#addPercentageNotification("ACTUAL", thresholdPercent, subscribers);
23
+ }
24
+ /**
25
+ * Add a notification that fires when FORECASTED spend crosses the
26
+ * given percentage of the budget limit.
27
+ *
28
+ * @param thresholdPercent - Percentage of the budget limit (e.g. `100`).
29
+ * For absolute-value thresholds, use {@link addNotification} directly.
30
+ */
31
+ notifyOnForecasted(thresholdPercent, ...subscribers) {
32
+ return this.#addPercentageNotification("FORECASTED", thresholdPercent, subscribers);
33
+ }
34
+ /**
35
+ * Raw notification passthrough for callers that need the full
36
+ * CloudFormation surface (e.g. absolute-value thresholds).
37
+ */
38
+ addNotification(entry) {
39
+ this.#notifications.push(entry);
40
+ return this;
41
+ }
42
+ /**
43
+ * Apply the well-architected recommended notification thresholds:
44
+ *
45
+ * - `ACTUAL` at 80% — early warning before breach.
46
+ * - `FORECASTED` at 100% — trending-over-budget alert.
47
+ *
48
+ * Must be called with at least one subscriber; the same subscriber
49
+ * list is used for both thresholds.
50
+ *
51
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-best-practices.html
52
+ */
53
+ withRecommendedThresholds(...subscribers) {
54
+ if (subscribers.length === 0) {
55
+ throw new Error(`BudgetBuilder: withRecommendedThresholds(...) must be called with at least one subscriber.`);
56
+ }
57
+ const { actualPercent, forecastedPercent } = BUDGET_DEFAULTS.recommendedThresholds;
58
+ this.#notifications.push({ notificationType: "ACTUAL", threshold: actualPercent, subscribers }, { notificationType: "FORECASTED", threshold: forecastedPercent, subscribers });
59
+ return this;
60
+ }
61
+ /**
62
+ * Adds a custom CloudWatch alarm against the budget. The configure
63
+ * callback receives a fresh {@link AlarmDefinitionBuilder} pre-set with
64
+ * the alarm's key; configure metric, threshold, comparison and any
65
+ * other options.
66
+ *
67
+ * Custom alarms are materialised in this builder's stack alongside any
68
+ * recommended alarms. Like the recommended `EstimatedCharges` alarm,
69
+ * custom alarms on `AWS/Billing` metrics will only fire when this
70
+ * stack is in `us-east-1` — the builder emits the same synth-time
71
+ * warning (`@composurecdk/budgets:alarm-region`) when used elsewhere.
72
+ * For non-`us-east-1` stacks, route alarms via
73
+ * {@link createBudgetAlarmBuilder} into a `us-east-1` stack.
74
+ */
75
+ addAlarm(key, configure) {
76
+ this.#customAlarms.push(configure(new AlarmDefinitionBuilder(key)));
77
+ return this;
78
+ }
79
+ build(scope, id, context = {}) {
80
+ const { recommendedAlarms: alarmConfig, ...budgetProps } = this.props;
81
+ const budgetType = budgetProps.budgetType ?? BUDGET_DEFAULTS.budgetType;
82
+ const timeUnit = budgetProps.timeUnit ?? BUDGET_DEFAULTS.timeUnit;
83
+ const requiresLimit = budgetType === "COST" || budgetType === "USAGE";
84
+ if (requiresLimit && !budgetProps.limit) {
85
+ throw new Error(`BudgetBuilder "${id}": limit({ amount, unit }) must be set for ${budgetType} budgets.`);
86
+ }
87
+ const { notificationsWithSubscribers, snsTopics } = this.#buildNotifications(context);
88
+ const budgetData = {
89
+ budgetName: budgetProps.budgetName,
90
+ budgetType,
91
+ timeUnit,
92
+ ...(budgetProps.limit
93
+ ? {
94
+ budgetLimit: {
95
+ amount: budgetProps.limit.amount,
96
+ unit: budgetProps.limit.unit ?? BUDGET_DEFAULTS.limitUnit,
97
+ },
98
+ }
99
+ : {}),
100
+ ...(budgetProps.costFilters ? { costFilters: budgetProps.costFilters } : {}),
101
+ ...(budgetProps.costTypes ? { costTypes: budgetProps.costTypes } : {}),
102
+ };
103
+ const cfnProps = {
104
+ budget: budgetData,
105
+ ...(notificationsWithSubscribers.length > 0 ? { notificationsWithSubscribers } : {}),
106
+ };
107
+ const budget = new CfnBudget(scope, id, cfnProps);
108
+ const topicPolicies = createBudgetsTopicPolicies(scope, id, snsTopics);
109
+ const alarms = buildBudgetAlarms(scope, id, { budget }, {
110
+ recommendedAlarms: alarmConfig,
111
+ customAlarms: this.#customAlarms,
112
+ });
113
+ return { budget, topicPolicies, alarms };
114
+ }
115
+ #addPercentageNotification(notificationType, thresholdPercent, subscribers) {
116
+ if (subscribers.length === 0) {
117
+ throw new Error(`BudgetBuilder: ${notificationType} notification at ${String(thresholdPercent)}% requires at least one subscriber.`);
118
+ }
119
+ this.#notifications.push({ notificationType, threshold: thresholdPercent, subscribers });
120
+ return this;
121
+ }
122
+ #buildNotifications(context) {
123
+ const notificationsWithSubscribers = [];
124
+ const allSnsTopics = [];
125
+ for (const entry of this.#notifications) {
126
+ const resolved = resolveSubscribers(entry.subscribers, context);
127
+ notificationsWithSubscribers.push(toCfnNotificationWithSubscribers(entry, resolved.cfn));
128
+ allSnsTopics.push(...resolved.snsTopics);
129
+ }
130
+ return { notificationsWithSubscribers, snsTopics: allSnsTopics };
131
+ }
132
+ }
133
+ /**
134
+ * Creates a new {@link IBudgetBuilder} for configuring an AWS Budget.
135
+ */
136
+ export function createBudgetBuilder() {
137
+ return Builder(BudgetBuilder);
138
+ }
139
+ //# sourceMappingURL=budget-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget-builder.js","sourceRoot":"","sources":["../src/budget-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Well-architected defaults for {@link createBudgetBuilder}. Each can be
3
+ * overridden via the builder's fluent API.
4
+ *
5
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/welcome.html
6
+ */
7
+ export declare const BUDGET_DEFAULTS: {
8
+ /**
9
+ * Default budget type — most budgets are cost budgets.
10
+ *
11
+ * @see https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_Budget.html
12
+ */
13
+ budgetType: "COST";
14
+ /**
15
+ * Default tracking period — monthly is the most common granularity and
16
+ * aligns with AWS billing cycles.
17
+ *
18
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html
19
+ */
20
+ timeUnit: "MONTHLY";
21
+ /**
22
+ * Default spend currency — most customers need to set this; the builder
23
+ * defaults to USD to align with the AWS Billing reporting default.
24
+ *
25
+ * @see https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_Spend.html
26
+ */
27
+ limitUnit: string;
28
+ /**
29
+ * Recommended percentage thresholds applied by
30
+ * {@link IBudgetBuilder.withRecommendedThresholds}.
31
+ *
32
+ * - `ACTUAL` at 80% — early warning before you breach the budget.
33
+ * - `FORECASTED` at 100% — notifies when forecasted spend trends over
34
+ * budget for the period.
35
+ *
36
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-best-practices.html
37
+ */
38
+ recommendedThresholds: {
39
+ actualPercent: number;
40
+ forecastedPercent: number;
41
+ };
42
+ };
43
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;;IAGH;;;;;OAKG;;IAGH;;;;;OAKG;;IAGH;;;;;;;;;OASG;;;;;CAKJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Well-architected defaults for {@link createBudgetBuilder}. Each can be
3
+ * overridden via the builder's fluent API.
4
+ *
5
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/welcome.html
6
+ */
7
+ export const BUDGET_DEFAULTS = {
8
+ /**
9
+ * Default budget type — most budgets are cost budgets.
10
+ *
11
+ * @see https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_Budget.html
12
+ */
13
+ budgetType: "COST",
14
+ /**
15
+ * Default tracking period — monthly is the most common granularity and
16
+ * aligns with AWS billing cycles.
17
+ *
18
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html
19
+ */
20
+ timeUnit: "MONTHLY",
21
+ /**
22
+ * Default spend currency — most customers need to set this; the builder
23
+ * defaults to USD to align with the AWS Billing reporting default.
24
+ *
25
+ * @see https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_Spend.html
26
+ */
27
+ limitUnit: "USD",
28
+ /**
29
+ * Recommended percentage thresholds applied by
30
+ * {@link IBudgetBuilder.withRecommendedThresholds}.
31
+ *
32
+ * - `ACTUAL` at 80% — early warning before you breach the budget.
33
+ * - `FORECASTED` at 100% — notifies when forecasted spend trends over
34
+ * budget for the period.
35
+ *
36
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-best-practices.html
37
+ */
38
+ recommendedThresholds: {
39
+ actualPercent: 80,
40
+ forecastedPercent: 100,
41
+ },
42
+ };
43
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B;;;;OAIG;IACH,UAAU,EAAE,MAAe;IAE3B;;;;;OAKG;IACH,QAAQ,EAAE,SAAkB;IAE5B;;;;;OAKG;IACH,SAAS,EAAE,KAAK;IAEhB;;;;;;;;;OASG;IACH,qBAAqB,EAAE;QACrB,aAAa,EAAE,EAAE;QACjB,iBAAiB,EAAE,GAAG;KACvB;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { createBudgetBuilder, type IBudgetBuilder, type BudgetBuilderProps, type BudgetBuilderResult, type BudgetLimit, } from "./budget-builder.js";
2
+ export { createBudgetAlarmBuilder, type IBudgetAlarmBuilder, type BudgetAlarmBuilderProps, type BudgetAlarmBuilderResult, } from "./budget-alarm-builder.js";
3
+ export { BUDGET_DEFAULTS } from "./defaults.js";
4
+ export { type BudgetAlarmConfig, type EstimatedChargesAlarmConfig } from "./alarm-config.js";
5
+ export { createBudgetsTopicPolicies } from "./topic-policy.js";
6
+ export { resolveSubscribers, toCfnNotificationWithSubscribers, type BudgetSubscriber, type NotificationEntry, type NotificationType, type ResolvedSubscribers, } from "./notifications.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EAChC,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { createBudgetBuilder, } from "./budget-builder.js";
2
+ export { createBudgetAlarmBuilder, } from "./budget-alarm-builder.js";
3
+ export { BUDGET_DEFAULTS } from "./defaults.js";
4
+ export { createBudgetsTopicPolicies } from "./topic-policy.js";
5
+ export { resolveSubscribers, toCfnNotificationWithSubscribers, } from "./notifications.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAKpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,GAIzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EACL,kBAAkB,EAClB,gCAAgC,GAKjC,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { CfnBudget } from "aws-cdk-lib/aws-budgets";
2
+ import type { ITopic } from "aws-cdk-lib/aws-sns";
3
+ import { type Resolvable } from "@composurecdk/core";
4
+ /**
5
+ * A subscriber that receives budget notifications. Either an email
6
+ * address (string) or an SNS topic (concrete or resolvable reference).
7
+ */
8
+ export type BudgetSubscriber = string | ITopic | Resolvable<ITopic>;
9
+ /**
10
+ * Which side of spend a notification triggers on.
11
+ *
12
+ * - `ACTUAL` — fires when real, posted spend crosses the threshold.
13
+ * - `FORECASTED` — fires when AWS projects you will cross the threshold
14
+ * during the current budget period.
15
+ */
16
+ export type NotificationType = "ACTUAL" | "FORECASTED";
17
+ /**
18
+ * Raw notification entry accepted by {@link IBudgetBuilder.addNotification}.
19
+ *
20
+ * Callers that need the full CloudFormation surface (custom comparison
21
+ * operators, absolute-value thresholds) should use this shape; for the
22
+ * common percentage case, prefer
23
+ * {@link IBudgetBuilder.notifyOnActual} /
24
+ * {@link IBudgetBuilder.notifyOnForecasted}.
25
+ */
26
+ export interface NotificationEntry {
27
+ notificationType: NotificationType;
28
+ /**
29
+ * Threshold value. Interpreted as a percentage of the budget limit
30
+ * when `thresholdType` is `PERCENTAGE` (the default), or as an
31
+ * absolute amount when `thresholdType` is `ABSOLUTE_VALUE`.
32
+ */
33
+ threshold: number;
34
+ subscribers: BudgetSubscriber[];
35
+ comparisonOperator?: "GREATER_THAN" | "LESS_THAN" | "EQUAL_TO";
36
+ thresholdType?: "PERCENTAGE" | "ABSOLUTE_VALUE";
37
+ }
38
+ /**
39
+ * Resolved subscriber after any {@link Resolvable} has been resolved.
40
+ *
41
+ * `snsTopics` is surfaced separately so the builder can create
42
+ * `AWS::SNS::TopicPolicy` entries granting `budgets.amazonaws.com`
43
+ * permission to publish.
44
+ */
45
+ export interface ResolvedSubscribers {
46
+ cfn: CfnBudget.SubscriberProperty[];
47
+ snsTopics: ITopic[];
48
+ }
49
+ /**
50
+ * Resolve a list of {@link BudgetSubscriber}s into the CloudFormation
51
+ * shape required by `AWS::Budgets::Budget` and a flat list of any SNS
52
+ * topics referenced (so the caller can create topic policies).
53
+ */
54
+ export declare function resolveSubscribers(subscribers: BudgetSubscriber[], context: Record<string, object>): ResolvedSubscribers;
55
+ /**
56
+ * Convert a {@link NotificationEntry} plus resolved subscribers into the
57
+ * CloudFormation `NotificationWithSubscribersProperty` shape.
58
+ */
59
+ export declare function toCfnNotificationWithSubscribers(entry: NotificationEntry, resolvedSubscribers: CfnBudget.SubscriberProperty[]): CfnBudget.NotificationWithSubscribersProperty;
60
+ //# sourceMappingURL=notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,kBAAkB,CAAC,EAAE,cAAc,GAAG,WAAW,GAAG,UAAU,CAAC;IAC/D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,CAAC;CACjD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,SAAS,CAAC,kBAAkB,EAAE,CAAC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,mBAAmB,CAgBrB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,iBAAiB,EACxB,mBAAmB,EAAE,SAAS,CAAC,kBAAkB,EAAE,GAClD,SAAS,CAAC,mCAAmC,CAU/C"}
@@ -0,0 +1,36 @@
1
+ import { resolve } from "@composurecdk/core";
2
+ /**
3
+ * Resolve a list of {@link BudgetSubscriber}s into the CloudFormation
4
+ * shape required by `AWS::Budgets::Budget` and a flat list of any SNS
5
+ * topics referenced (so the caller can create topic policies).
6
+ */
7
+ export function resolveSubscribers(subscribers, context) {
8
+ const cfn = [];
9
+ const snsTopics = [];
10
+ for (const subscriber of subscribers) {
11
+ if (typeof subscriber === "string") {
12
+ cfn.push({ address: subscriber, subscriptionType: "EMAIL" });
13
+ continue;
14
+ }
15
+ const topic = resolve(subscriber, context);
16
+ cfn.push({ address: topic.topicArn, subscriptionType: "SNS" });
17
+ snsTopics.push(topic);
18
+ }
19
+ return { cfn, snsTopics };
20
+ }
21
+ /**
22
+ * Convert a {@link NotificationEntry} plus resolved subscribers into the
23
+ * CloudFormation `NotificationWithSubscribersProperty` shape.
24
+ */
25
+ export function toCfnNotificationWithSubscribers(entry, resolvedSubscribers) {
26
+ return {
27
+ notification: {
28
+ notificationType: entry.notificationType,
29
+ threshold: entry.threshold,
30
+ comparisonOperator: entry.comparisonOperator ?? "GREATER_THAN",
31
+ thresholdType: entry.thresholdType ?? "PERCENTAGE",
32
+ },
33
+ subscribers: resolvedSubscribers,
34
+ };
35
+ }
36
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAmB,MAAM,oBAAoB,CAAC;AAmD9D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAA+B,EAC/B,OAA+B;IAE/B,MAAM,GAAG,GAAmC,EAAE,CAAC;IAC/C,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;YAC7D,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC3C,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gCAAgC,CAC9C,KAAwB,EACxB,mBAAmD;IAEnD,OAAO;QACL,YAAY,EAAE;YACZ,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,IAAI,cAAc;YAC9D,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,YAAY;SACnD;QACD,WAAW,EAAE,mBAAmB;KACjC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type ITopic, TopicPolicy } from "aws-cdk-lib/aws-sns";
2
+ import type { IConstruct } from "constructs";
3
+ /**
4
+ * Create an `AWS::SNS::TopicPolicy` granting the AWS Budgets service
5
+ * principal (`budgets.amazonaws.com`) permission to publish to each of
6
+ * the supplied topics.
7
+ *
8
+ * Without this policy, a budget notification configured with an SNS
9
+ * subscriber will silently fail to deliver — one of the most common
10
+ * footguns when wiring Budgets to SNS by hand. The builder wires it up
11
+ * automatically whenever at least one `SNS` subscriber is configured.
12
+ *
13
+ * Each topic gets its own `TopicPolicy` construct, keyed by the topic's
14
+ * fully-qualified CDK node path, so callers can inspect or extend them
15
+ * via the build result.
16
+ *
17
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-sns-policy.html
18
+ */
19
+ export declare function createBudgetsTopicPolicies(scope: IConstruct, id: string, topics: ITopic[]): Record<string, TopicPolicy>;
20
+ //# sourceMappingURL=topic-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-policy.d.ts","sourceRoot":"","sources":["../src/topic-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EAAE,GACf,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA2B7B"}
@@ -0,0 +1,42 @@
1
+ import { Effect, PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam";
2
+ import { TopicPolicy } from "aws-cdk-lib/aws-sns";
3
+ /**
4
+ * Create an `AWS::SNS::TopicPolicy` granting the AWS Budgets service
5
+ * principal (`budgets.amazonaws.com`) permission to publish to each of
6
+ * the supplied topics.
7
+ *
8
+ * Without this policy, a budget notification configured with an SNS
9
+ * subscriber will silently fail to deliver — one of the most common
10
+ * footguns when wiring Budgets to SNS by hand. The builder wires it up
11
+ * automatically whenever at least one `SNS` subscriber is configured.
12
+ *
13
+ * Each topic gets its own `TopicPolicy` construct, keyed by the topic's
14
+ * fully-qualified CDK node path, so callers can inspect or extend them
15
+ * via the build result.
16
+ *
17
+ * @see https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-sns-policy.html
18
+ */
19
+ export function createBudgetsTopicPolicies(scope, id, topics) {
20
+ const policies = {};
21
+ const seen = new Set();
22
+ for (const topic of topics) {
23
+ const key = topic.node.path;
24
+ if (seen.has(key))
25
+ continue;
26
+ seen.add(key);
27
+ const policy = new TopicPolicy(scope, `${id}TopicPolicy${topic.node.addr}`, {
28
+ topics: [topic],
29
+ policyDocument: undefined,
30
+ });
31
+ policy.document.addStatements(new PolicyStatement({
32
+ sid: "AllowBudgetsPublish",
33
+ effect: Effect.ALLOW,
34
+ principals: [new ServicePrincipal("budgets.amazonaws.com")],
35
+ actions: ["SNS:Publish"],
36
+ resources: [topic.topicArn],
37
+ }));
38
+ policies[key] = policy;
39
+ }
40
+ return policies;
41
+ }
42
+ //# sourceMappingURL=topic-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-policy.js","sourceRoot":"","sources":["../src/topic-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAe,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAiB,EACjB,EAAU,EACV,MAAgB;IAEhB,MAAM,QAAQ,GAAgC,EAAE,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE;YAC1E,MAAM,EAAE,CAAC,KAAK,CAAC;YACf,cAAc,EAAE,SAAS;SAC1B,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,aAAa,CAC3B,IAAI,eAAe,CAAC;YAClB,GAAG,EAAE,qBAAqB;YAC1B,MAAM,EAAE,MAAM,CAAC,KAAK;YACpB,UAAU,EAAE,CAAC,IAAI,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;YAC3D,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,SAAS,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;SAC5B,CAAC,CACH,CAAC;QAEF,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}