@composurecdk/lambda 0.10.0 → 0.10.1

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,151 @@
1
+ import { Annotations, Duration } from "aws-cdk-lib";
2
+ import { InvocationType, Trigger } from "aws-cdk-lib/triggers";
3
+ import { addDependencies } from "@composurecdk/core";
4
+ /**
5
+ * Suppression id for the deploy-time invocation timeout guard. Stable and part
6
+ * of the public surface — silence the warning with
7
+ * `Annotations.of(scope).acknowledgeWarning(INVOKE_ON_DEPLOY_TIMEOUT_WARNING_ID)`,
8
+ * so it must not be renamed casually.
9
+ */
10
+ export const INVOKE_ON_DEPLOY_TIMEOUT_WARNING_ID = "@composurecdk/lambda:invoke-on-deploy-timeout";
11
+ /**
12
+ * The deployment waits for the handler's response, so a handler that throws
13
+ * fails the stack. An invariant of the action rather than a default: the
14
+ * asynchronous alternative returns before the work happens, reporting success
15
+ * whatever the handler did, so there is nothing to override to.
16
+ *
17
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_tracking_change_management_deployment_management.html
18
+ */
19
+ const INVOCATION_TYPE = InvocationType.REQUEST_RESPONSE;
20
+ /**
21
+ * The trigger provider's own Lambda timeout. CDK builds that provider from
22
+ * `CustomResourceProviderBase`, which defaults to 15 minutes, and `Trigger`
23
+ * overrides nothing — so there is no seam to raise it through and no way to
24
+ * read it back. Asserted against a synthesised template in the test suite, so
25
+ * an `aws-cdk-lib` upgrade that changes the default fails loudly here rather
26
+ * than quietly reopening the hang this ceiling exists to prevent.
27
+ */
28
+ const PROVIDER_TIMEOUT = Duration.minutes(15);
29
+ /**
30
+ * Added to the function's own `timeout` to derive the wait, and subtracted
31
+ * from {@link PROVIDER_TIMEOUT} to derive {@link MAX_TIMEOUT} — the same slack
32
+ * at both ends, for the same reason: whoever is waiting must outlive whoever
33
+ * they are waiting for, or the error is never reported.
34
+ */
35
+ const TIMEOUT_MARGIN = Duration.seconds(30);
36
+ /**
37
+ * Ceiling for the wait. The provider must outlive the invocation it is waiting
38
+ * on, or it dies mid-wait and posts nothing back to CloudFormation. `Trigger`
39
+ * sets no `ServiceTimeout` on the custom resource, so that is not a failed
40
+ * deployment — it is a *hung* one, until CloudFormation abandons the stack
41
+ * operation of its own accord.
42
+ */
43
+ const MAX_TIMEOUT = PROVIDER_TIMEOUT.minus(TIMEOUT_MARGIN);
44
+ /**
45
+ * The wait applied when the function's own timeout is unset or a token, and so
46
+ * yields no concrete value to derive from. Matches the CDK `Trigger` default.
47
+ */
48
+ const FALLBACK_TIMEOUT = Duration.minutes(2);
49
+ /**
50
+ * How {@link IFunctionBuilder.invokeOnDeploy | `.invokeOnDeploy()`} derives the
51
+ * wait when the caller sets no {@link InvokeOnDeployOptions.timeout}. A public
52
+ * mirror of this module's constants, exported for visibility and testing — the
53
+ * rationale for each value is on the constant it mirrors.
54
+ */
55
+ export const INVOKE_ON_DEPLOY_DEFAULTS = {
56
+ /** @see {@link TIMEOUT_MARGIN} */
57
+ timeoutMargin: TIMEOUT_MARGIN,
58
+ /** @see {@link FALLBACK_TIMEOUT} */
59
+ fallbackTimeout: FALLBACK_TIMEOUT,
60
+ /** @see {@link MAX_TIMEOUT} */
61
+ maxTimeout: MAX_TIMEOUT,
62
+ };
63
+ /**
64
+ * Creates the custom resource that invokes `handler` during deployment and
65
+ * fails the stack if it errors.
66
+ *
67
+ * The invocation is ordered after the function's execution role, so the policies
68
+ * attached *to it* by `.grant()` / `.configureRole()` — which CloudFormation
69
+ * does not otherwise sequence ahead of a custom resource — exist by the time the
70
+ * handler runs.
71
+ *
72
+ * That reaches the identity half of a grant only: one that also writes a
73
+ * resource policy (a KMS key policy, a cross-account bucket policy) leaves that
74
+ * half in the granting resource's tree, and an imported role contributes no
75
+ * resources to sequence at all. Both are the consumer's to declare through
76
+ * {@link InvokeOnDeployOptions.after}, as is anything else the call needs.
77
+ *
78
+ * @internal
79
+ */
80
+ export function createDeploymentTrigger(scope, id, handler, executionRole, options, context) {
81
+ const timeout = resolveWait(options.timeout, handler.timeout, id);
82
+ warnOnShortWait(handler, id, timeout);
83
+ const trigger = new Trigger(scope, `${id}DeploymentTrigger`, {
84
+ handler,
85
+ invocationType: INVOCATION_TYPE,
86
+ timeout,
87
+ executeOnHandlerChange: options.executeOnHandlerChange,
88
+ });
89
+ addDependencies(trigger, [executionRole, ...(options.after ?? [])], context);
90
+ return trigger;
91
+ }
92
+ /**
93
+ * How long the deployment waits.
94
+ *
95
+ * {@link MAX_TIMEOUT} binds both branches, but differently. A derived wait is
96
+ * capped: the derivation is the library's, so quietly settling for less than it
97
+ * wanted is the library's business. An explicit wait over the ceiling throws —
98
+ * clamping would honour neither the duration the caller asked for nor the
99
+ * failing deployment they expected, and there is no working form to fall back
100
+ * to. Both are silent on a token, which has no value to compare.
101
+ */
102
+ function resolveWait(explicitWait, handlerTimeout, id) {
103
+ if (explicitWait !== undefined) {
104
+ const waitSeconds = concreteSeconds(explicitWait);
105
+ if (waitSeconds !== undefined && waitSeconds > MAX_TIMEOUT.toSeconds()) {
106
+ throw new Error(`FunctionBuilder "${id}": .invokeOnDeploy() was given a timeout of ${String(waitSeconds)}s, ` +
107
+ `but the deployment cannot wait longer than ${String(MAX_TIMEOUT.toSeconds())}s — CDK's ` +
108
+ `trigger provider runs with a ${String(PROVIDER_TIMEOUT.toSeconds())}s Lambda timeout ` +
109
+ `and needs the remainder to report the result. A longer wait does not fail the ` +
110
+ `deployment, it hangs it: the provider dies mid-invocation and CloudFormation is never ` +
111
+ `told. Lower the timeout, or move work that needs longer out of the deployment.`);
112
+ }
113
+ return explicitWait;
114
+ }
115
+ const handlerSeconds = concreteSeconds(handlerTimeout);
116
+ if (handlerSeconds === undefined)
117
+ return FALLBACK_TIMEOUT;
118
+ const derived = handlerSeconds + TIMEOUT_MARGIN.toSeconds();
119
+ return Duration.seconds(Math.min(derived, MAX_TIMEOUT.toSeconds()));
120
+ }
121
+ /** A `Duration` in seconds, or `undefined` when it is unset or a token. */
122
+ function concreteSeconds(duration) {
123
+ if (duration === undefined || duration.isUnresolved())
124
+ return undefined;
125
+ return duration.toSeconds();
126
+ }
127
+ /**
128
+ * Warns when the deployment would stop waiting before the handler's own timeout
129
+ * — a handler that runs long then fails the stack as an abandoned invocation
130
+ * rather than reporting the error it was about to produce.
131
+ *
132
+ * Checked against the final wait, whether the caller set it or it was derived,
133
+ * because the derived path can land here too: the cap leaves no margin once the
134
+ * function's own timeout reaches {@link MAX_TIMEOUT}.
135
+ * Silent whenever either value is a token, with nothing to compare.
136
+ */
137
+ function warnOnShortWait(handler, id, wait) {
138
+ const waitSeconds = concreteSeconds(wait);
139
+ const handlerSeconds = concreteSeconds(handler.timeout);
140
+ if (waitSeconds === undefined || handlerSeconds === undefined)
141
+ return;
142
+ if (waitSeconds > handlerSeconds)
143
+ return;
144
+ Annotations.of(handler).addWarningV2(INVOKE_ON_DEPLOY_TIMEOUT_WARNING_ID, `FunctionBuilder "${id}": .invokeOnDeploy() waits ${String(waitSeconds)}s but the function's ` +
145
+ `own timeout is ${String(handlerSeconds)}s — the deployment stops waiting while the handler ` +
146
+ `is still running, so a slow call fails the stack as an abandoned invocation instead of ` +
147
+ `reporting the handler's error. Give the deployment longer than the function's timeout (up ` +
148
+ `to ${String(MAX_TIMEOUT.toSeconds())}s), shorten the function's ` +
149
+ `timeout, or acknowledge "${INVOKE_ON_DEPLOY_TIMEOUT_WARNING_ID}".`);
150
+ }
151
+ //# sourceMappingURL=invoke-on-deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoke-on-deploy.js","sourceRoot":"","sources":["../../src/invoke-on-deploy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composurecdk/lambda",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Composable Lambda function builder with well-architected defaults",
5
5
  "repository": {
6
6
  "type": "git",
@@ -59,8 +59,8 @@
59
59
  "devDependencies": {
60
60
  "@composurecdk/cdk-testing": "*",
61
61
  "@composurecdk/iam": "^0.10.0",
62
- "@types/node": "^26.5.1",
63
- "aws-cdk-lib": "^2.268.0",
62
+ "@types/node": "^26.6.1",
63
+ "aws-cdk-lib": "^2.269.0",
64
64
  "constructs": "^10.8.1",
65
65
  "ts-fake": "^1.2.0",
66
66
  "typescript": "^6.0.3",