@composurecdk/core
v0.11.1
Published
Composable CDK component system — lifecycle, dependency resolution, and builder pattern
Maintainers
Readme
@composurecdk/core
Core primitives for ComposureCDK.
This package provides the foundational types and utilities that all ComposureCDK resource packages build on: the fluent Builder proxy, the compose function for assembling component systems with dependency resolution, lazy Ref cross-references, and stack strategies for routing components to scopes.
Most users interact with these primitives indirectly through resource packages (e.g., @composurecdk/lambda, @composurecdk/s3). Use this package directly when building a custom ComposureCDK resource package or when composing systems.
Builder
Generic fluent-builder factory that wraps a class, exposing every property in Props as an overloaded getter/setter method. Resource packages use this internally to create their builders.
import { Builder, type IBuilder } from "@composurecdk/core";
type IMyBuilder = IBuilder<MyProps, MyLifecycle>;
export function createMyBuilder(): IMyBuilder {
return Builder<MyProps, MyLifecycle>(MyLifecycle);
}compose
Composes a set of Lifecycle components into a single system with automatic dependency resolution. Dependencies are declared as data; the system builds components in topological order and passes resolved outputs as context.
import { compose } from "@composurecdk/core";
const system = compose(
{ handler: createFunctionBuilder(), api: createRestApiBuilder() },
{ handler: [], api: ["handler"] },
);
system.build(stack, "MySystem");Cyclic dependencies are detected at composition time and throw a CyclicDependencyError.
Stack routing
Route components to different scopes (typically Stacks) using withStacks or withStackStrategy:
// Explicit scope mapping
system.withStacks({ handler: serviceStack, api: apiStack }).build(app, "MySystem");
// Strategy-based routing
import { singleStack, groupedStacks } from "@composurecdk/core";
system.withStackStrategy(singleStack(myFactory)).build(app, "MySystem");
system
.withStackStrategy(groupedStacks((key) => (key === "handler" ? "compute" : "api"), myFactory))
.build(app, "MySystem");Post-build hooks
Register callbacks that run after all components are built via afterBuild. Domain-specific packages provide helper functions that return hooks — for example, outputs() from @composurecdk/cloudformation.
system
.afterBuild((scope, id, results) => {
console.log("Built:", Object.keys(results));
})
.build(stack, "MySystem");Ref
Lazy cross-component references that are resolved at build time. Use ref to reference a dependency's output at configuration time — the value is resolved when the system is built.
import { ref } from "@composurecdk/core";
import type { FunctionBuilderResult } from "@composurecdk/lambda";
// Reference the full build result
ref<FunctionBuilderResult>("handler");
// Narrow to a specific property
ref<FunctionBuilderResult>("handler").get("function");
// Transform the referenced value
ref<FunctionBuilderResult>("handler")
.get("function")
.map((fn) => new LambdaIntegration(fn));
// Shorthand with inline transform
ref<FunctionBuilderResult>("handler", (r) => new LambdaIntegration(r.function));Ordering: addDependencies
compose() decides the order components build; CloudFormation decides the order resources deploy, and it only orders B after A when B's template references A. Where no such reference exists — an SDK call whose parameters are hardcoded strings, a Lambda invoked during deployment — the edge has to be declared.
addDependencies(target, sources, context) is that seam. Each source is a concrete construct, a ref to one construct inside a sibling's result, or a ref to the whole sibling:
import { addDependencies, ref } from "@composurecdk/core";
addDependencies(
trigger,
[
queue, // a construct you already hold
ref("api", (r: RestApiBuilderResult) => r.api), // one construct out of a sibling
ref<SeedBuilderResult>("seed"), // every construct that sibling built
],
context,
);A construct-level dependency already covers that construct's own children, so naming one is enough whenever what you need sits under it — ordering against a RestApi also orders against its deployment and stage. Naming the component instead also reaches its peers: an API's access log group, a queue's dead-letter queue.
It backs dependsOn in @composurecdk/custom-resources and after in @composurecdk/lambda's .invokeOnDeploy(), so both spell ordering the same way.
Examples
- MultiStackApp — System composed with
withStacksfor multi-stack routing, demonstrates cross-component wiring withref - StaticWebsiteStack — S3 + CloudFront composed system with
refandafterBuild
