@composurecdk/core 0.1.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,46 @@
1
+ /**
2
+ * Constructs an instance of `T`.
3
+ */
4
+ type Constructor<T> = new () => T;
5
+ /**
6
+ * Constrains `T` to have a mutable `props` property of type `Props`.
7
+ * Classes used with {@link Builder} must expose their configuration this way.
8
+ */
9
+ interface ObjectWithProps<Props extends object> {
10
+ props: Partial<Props>;
11
+ }
12
+ /**
13
+ * A fluent builder interface generated from a props type and a target class.
14
+ *
15
+ * For each key in `Props`, the builder exposes an overloaded method:
16
+ * - Called with an argument: sets the prop value and returns the builder for chaining.
17
+ * - Called with no arguments: returns the current prop value.
18
+ *
19
+ * Methods from `T` that return `T` (chainable methods) have their return type
20
+ * replaced to return the builder. All other members of `T` pass through as-is,
21
+ * allowing methods like `build()` to be called directly on the builder.
22
+ *
23
+ * @typeParam Props - The configurable properties.
24
+ * @typeParam T - The target class the builder wraps.
25
+ */
26
+ export type IBuilder<Props extends object, T> = {
27
+ [K in keyof Props]-?: ((arg: Props[K]) => IBuilder<Props, T>) & (() => Props[K]);
28
+ } & {
29
+ [K in keyof T]: T[K] extends (...args: infer A) => T ? (...args: A) => IBuilder<Props, T> : T[K];
30
+ };
31
+ /**
32
+ * Creates a fluent builder wrapping an instance of `T`.
33
+ *
34
+ * The builder is backed by a {@link Proxy} that intercepts property access:
35
+ * - For keys in `Props`: returns a getter/setter function. When called with a
36
+ * value, it sets the prop and returns the builder. When called with no args,
37
+ * it returns the current value.
38
+ * - For methods on `T` that return `T`: wraps them to return the builder instead.
39
+ * - For all other members: delegates directly to the underlying instance.
40
+ *
41
+ * @param constructor - The class to instantiate and wrap.
42
+ * @returns A fluent {@link IBuilder} wrapping a new instance of `T`.
43
+ */
44
+ export declare function Builder<Props extends object, T extends ObjectWithProps<Props>>(constructor: Constructor<T>): IBuilder<Props, T>;
45
+ export {};
46
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,KAAK,WAAW,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AAElC;;;GAGG;AACH,UAAU,eAAe,CAAC,KAAK,SAAS,MAAM;IAC5C,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,IAAI;KAC7C,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;CACjF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACjG,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAC5E,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,GAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAoCpB"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Creates a fluent builder wrapping an instance of `T`.
3
+ *
4
+ * The builder is backed by a {@link Proxy} that intercepts property access:
5
+ * - For keys in `Props`: returns a getter/setter function. When called with a
6
+ * value, it sets the prop and returns the builder. When called with no args,
7
+ * it returns the current value.
8
+ * - For methods on `T` that return `T`: wraps them to return the builder instead.
9
+ * - For all other members: delegates directly to the underlying instance.
10
+ *
11
+ * @param constructor - The class to instantiate and wrap.
12
+ * @returns A fluent {@link IBuilder} wrapping a new instance of `T`.
13
+ */
14
+ export function Builder(constructor) {
15
+ const instance = new constructor();
16
+ const methods = new Set(Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).filter((key) => key !== "constructor" && typeof instance[key] === "function"));
17
+ const proxy = new Proxy(instance, {
18
+ get(target, prop) {
19
+ if (typeof prop === "symbol") {
20
+ return Reflect.get(target, prop);
21
+ }
22
+ // Props getter/setter
23
+ if (!methods.has(prop)) {
24
+ return (...args) => {
25
+ if (args.length === 0) {
26
+ return target.props[prop];
27
+ }
28
+ target.props[prop] = args[0];
29
+ return proxy;
30
+ };
31
+ }
32
+ // Method on target — wrap to return proxy for chainable methods
33
+ const method = target[prop];
34
+ return (...args) => {
35
+ const result = method.apply(target, args);
36
+ return result === target ? proxy : result;
37
+ };
38
+ },
39
+ });
40
+ return proxy;
41
+ }
42
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO,CACrB,WAA2B;IAE3B,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAChE,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,KAAK,aAAa,IAAI,OAAQ,QAAoC,CAAC,GAAG,CAAC,KAAK,UAAU,CAC5F,CACF,CAAC;IAEF,MAAM,KAAK,GAAuB,IAAI,KAAK,CAAC,QAAQ,EAAE;QACpD,GAAG,CAAC,MAAS,EAAE,IAAqB;YAClC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAY,CAAC;YAC9C,CAAC;YAED,sBAAsB;YACtB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;oBAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACtB,OAAO,MAAM,CAAC,KAAK,CAAC,IAAmB,CAAC,CAAC;oBAC3C,CAAC;oBACD,MAAM,CAAC,KAAK,CAAC,IAAmB,CAAC,GAAG,IAAI,CAAC,CAAC,CAAuB,CAAC;oBAClE,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC;YACJ,CAAC;YAED,gEAAgE;YAChE,MAAM,MAAM,GAAI,MAAkC,CAAC,IAAI,CAAiC,CAAC;YACzF,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,MAAM,GAAY,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACnD,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;YAC5C,CAAC,CAAC;QACJ,CAAC;KACF,CAAuB,CAAC;IAEzB,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { type IConstruct } from "constructs";
2
+ import { type Lifecycle } from "./lifecycle.js";
3
+ import { type StackStrategy } from "./stack-strategy.js";
4
+ /**
5
+ * Maps a record of {@link Lifecycle} components to a record of their build outputs.
6
+ * Each property's type is derived from the return type of the corresponding
7
+ * component's `build` method, preserving full type information through composition.
8
+ *
9
+ * @typeParam T - A record where every value implements {@link Lifecycle}.
10
+ */
11
+ type BuildResult<T extends {
12
+ [Property in keyof T]: Lifecycle;
13
+ }> = {
14
+ [Property in keyof T]: ReturnType<T[Property]["build"]>;
15
+ };
16
+ /**
17
+ * Declares which other components a component depends on within a system.
18
+ * Dependencies are expressed as an array of component keys. During build,
19
+ * the resolved outputs of these components are passed as the component's context.
20
+ *
21
+ * @typeParam Components - The full set of components in the system.
22
+ */
23
+ type Dependency<Components extends Record<string, Lifecycle>> = (keyof Components)[];
24
+ /**
25
+ * A {@link Lifecycle} produced by {@link compose}, extended with methods
26
+ * for controlling how components are routed to scopes during build.
27
+ *
28
+ * Because `ComposedSystem` extends `Lifecycle`, a composed system can be
29
+ * nested as a component inside another `compose` call — composition is
30
+ * recursive.
31
+ */
32
+ export interface ComposedSystem<Components extends Record<string, Lifecycle>> extends Lifecycle<BuildResult<Components>> {
33
+ /**
34
+ * Returns a new {@link Lifecycle} that routes components to specific scopes
35
+ * (typically Stacks) during build. Components not listed in the map use the
36
+ * default scope passed to `build`.
37
+ *
38
+ * Accepts any `IConstruct`, so `Stack`, `DeploymentStack`, or custom
39
+ * subclasses all work.
40
+ *
41
+ * @param stacks - A partial map from component key to scope.
42
+ * @returns A {@link Lifecycle} with stack routing applied.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * compose({ handler, api }, { handler: [], api: ["handler"] })
47
+ * .withStacks({ handler: serviceStack, api: apiStack })
48
+ * .build(app, "MySystem");
49
+ * ```
50
+ */
51
+ withStacks(stacks: {
52
+ [K in keyof Components]?: IConstruct;
53
+ }): Lifecycle<BuildResult<Components>>;
54
+ /**
55
+ * Returns a new {@link Lifecycle} that uses a {@link StackStrategy} to
56
+ * determine each component's scope during build.
57
+ *
58
+ * @param strategy - The strategy that resolves scopes for components.
59
+ * @returns A {@link Lifecycle} with strategy-based stack routing applied.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * compose({ handler, api, table }, { ... })
64
+ * .withStackStrategy(groupedStacks(
65
+ * key => key === "table" ? "persistence" : "service",
66
+ * (app, id) => new Stack(app, id),
67
+ * ))
68
+ * .build(app, "MySystem");
69
+ * ```
70
+ */
71
+ withStackStrategy(strategy: StackStrategy): Lifecycle<BuildResult<Components>>;
72
+ }
73
+ /**
74
+ * Composes a set of {@link Lifecycle} components into a single system that
75
+ * manages their build order and dependency resolution.
76
+ *
77
+ * A directed acyclic graph is built eagerly from the declared dependencies.
78
+ * Cyclic dependencies are detected at composition time and throw immediately.
79
+ * When `build` is called, components are built in topological order, each
80
+ * receiving the build outputs of its dependencies as its context.
81
+ *
82
+ * The returned {@link ComposedSystem} is a {@link Lifecycle}, so it can be
83
+ * nested as a component in a larger `compose` call.
84
+ *
85
+ * @param components - A record of named {@link Lifecycle} components.
86
+ * @param dependencies - For each component, the list of other component keys it depends on.
87
+ * @returns A {@link ComposedSystem} whose build output is the combined {@link BuildResult} of all components.
88
+ */
89
+ export declare function compose<Components extends Record<string, Lifecycle>>(components: Components, dependencies: {
90
+ [Property in keyof Components]: Dependency<Components>;
91
+ }): ComposedSystem<Components>;
92
+ export {};
93
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;GAMG;AACH,KAAK,WAAW,CAAC,CAAC,SAAS;KAAG,QAAQ,IAAI,MAAM,CAAC,GAAG,SAAS;CAAE,IAAI;KAChE,QAAQ,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;CACxD,CAAC;AAEF;;;;;;GAMG;AACH,KAAK,UAAU,CAAC,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,UAAU,CAAC,EAAE,CAAC;AA+BrF;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc,CAAC,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAE,SAAQ,SAAS,CAC7F,WAAW,CAAC,UAAU,CAAC,CACxB;IACC;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,EAAE,UAAU;KAAE,GAAG,SAAS,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjG;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;CAChF;AAyDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAClE,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE;KAAG,QAAQ,IAAI,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;CAAE,GACvE,cAAc,CAAC,UAAU,CAAC,CAE5B"}
@@ -0,0 +1,81 @@
1
+ import { alg, json } from "@dagrejs/graphlib";
2
+ import { CyclicDependencyError } from "./cyclic-dependency-error.js";
3
+ /**
4
+ * Builds a directed acyclic graph from component dependency declarations.
5
+ * The graph is described declaratively as nodes and edges, then constructed
6
+ * via {@link json.read}. Nodes are component keys, edges point from a
7
+ * dependency to its dependent. Throws if the graph contains a cycle.
8
+ */
9
+ function buildDependencyGraph(components, dependencies) {
10
+ const nodes = Object.keys(components).map((v) => ({ v }));
11
+ const edges = Object.entries(dependencies).flatMap(([key, deps]) => deps.map((dep) => ({ v: dep, w: key })));
12
+ const graph = json.read({
13
+ options: { directed: true },
14
+ nodes,
15
+ edges,
16
+ });
17
+ if (!alg.isAcyclic(graph)) {
18
+ throw new CyclicDependencyError(alg.findCycles(graph));
19
+ }
20
+ return graph;
21
+ }
22
+ /**
23
+ * A composed system of {@link Lifecycle} components. Holds the dependency graph
24
+ * built at composition time and traverses it in topological order during build.
25
+ */
26
+ class ComposedLifecycle {
27
+ components;
28
+ dependencies;
29
+ graph;
30
+ constructor(components, dependencies) {
31
+ this.components = components;
32
+ this.dependencies = dependencies;
33
+ this.graph = buildDependencyGraph(components, dependencies);
34
+ }
35
+ withStacks(stacks) {
36
+ return {
37
+ build: (scope, id) => this.buildWith(scope, id, stacks),
38
+ };
39
+ }
40
+ withStackStrategy(strategy) {
41
+ return {
42
+ build: (scope, id) => {
43
+ const stacks = Object.fromEntries(Object.keys(this.components).map((key) => [key, strategy.resolve(scope, id, key)]));
44
+ return this.buildWith(scope, id, stacks);
45
+ },
46
+ };
47
+ }
48
+ build(scope, id) {
49
+ return this.buildWith(scope, id);
50
+ }
51
+ buildWith(scope, id, stacks) {
52
+ const results = {};
53
+ for (const key of alg.topsort(this.graph)) {
54
+ const componentScope = stacks?.[key] ?? scope;
55
+ const deps = (this.dependencies[key] ?? []);
56
+ const context = Object.fromEntries(deps.map((dep) => [dep, results[dep]]));
57
+ results[key] = this.components[key].build(componentScope, `${id}/${key}`, context);
58
+ }
59
+ return results;
60
+ }
61
+ }
62
+ /**
63
+ * Composes a set of {@link Lifecycle} components into a single system that
64
+ * manages their build order and dependency resolution.
65
+ *
66
+ * A directed acyclic graph is built eagerly from the declared dependencies.
67
+ * Cyclic dependencies are detected at composition time and throw immediately.
68
+ * When `build` is called, components are built in topological order, each
69
+ * receiving the build outputs of its dependencies as its context.
70
+ *
71
+ * The returned {@link ComposedSystem} is a {@link Lifecycle}, so it can be
72
+ * nested as a component in a larger `compose` call.
73
+ *
74
+ * @param components - A record of named {@link Lifecycle} components.
75
+ * @param dependencies - For each component, the list of other component keys it depends on.
76
+ * @returns A {@link ComposedSystem} whose build output is the combined {@link BuildResult} of all components.
77
+ */
78
+ export function compose(components, dependencies) {
79
+ return new ComposedLifecycle(components, dependencies);
80
+ }
81
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Thrown when {@link compose} detects a cycle in the component dependency graph.
3
+ * Carries the detected cycles so callers can inspect or report them programmatically.
4
+ */
5
+ export declare class CyclicDependencyError extends Error {
6
+ readonly cycles: string[][];
7
+ /**
8
+ * @param cycles - Each element is an array of component keys forming a cycle.
9
+ */
10
+ constructor(cycles: string[][]);
11
+ }
12
+ //# sourceMappingURL=cyclic-dependency-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cyclic-dependency-error.d.ts","sourceRoot":"","sources":["../src/cyclic-dependency-error.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;aAIlB,MAAM,EAAE,MAAM,EAAE,EAAE;IAH9C;;OAEG;gBACyB,MAAM,EAAE,MAAM,EAAE,EAAE;CAI/C"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Thrown when {@link compose} detects a cycle in the component dependency graph.
3
+ * Carries the detected cycles so callers can inspect or report them programmatically.
4
+ */
5
+ export class CyclicDependencyError extends Error {
6
+ cycles;
7
+ /**
8
+ * @param cycles - Each element is an array of component keys forming a cycle.
9
+ */
10
+ constructor(cycles) {
11
+ super(`Cyclic dependencies detected: ${cycles.map((c) => c.join(" -> ")).join("; ")}`);
12
+ this.cycles = cycles;
13
+ this.name = "CyclicDependencyError";
14
+ }
15
+ }
16
+ //# sourceMappingURL=cyclic-dependency-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cyclic-dependency-error.js","sourceRoot":"","sources":["../src/cyclic-dependency-error.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAIlB;IAH5B;;OAEG;IACH,YAA4B,MAAkB;QAC5C,KAAK,CAAC,iCAAiC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAD7D,WAAM,GAAN,MAAM,CAAY;QAE5C,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export { Builder, type IBuilder } from "./builder.js";
2
+ export { compose, type ComposedSystem } from "./compose.js";
3
+ export { CyclicDependencyError } from "./cyclic-dependency-error.js";
4
+ export { type Lifecycle } from "./lifecycle.js";
5
+ export { Ref, ref, resolve, isRef, type Resolvable } from "./ref.js";
6
+ export { type StackStrategy, type ScopeFactory, singleStack, groupedStacks, } from "./stack-strategy.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,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { Builder } from "./builder.js";
2
+ export { compose } from "./compose.js";
3
+ export { CyclicDependencyError } from "./cyclic-dependency-error.js";
4
+ export { Ref, ref, resolve, isRef } from "./ref.js";
5
+ export { singleStack, groupedStacks, } from "./stack-strategy.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAuB,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAmB,MAAM,UAAU,CAAC;AACrE,OAAO,EAGL,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type IConstruct } from "constructs";
2
+ /**
3
+ * Base type for the dependency context passed to a component's {@link Lifecycle.build} method.
4
+ * A record of named dependencies, each being a record of their build outputs.
5
+ */
6
+ type LifecycleComponentBase = Record<string, object>;
7
+ /**
8
+ * The core interface for all ComposureCDK components. A `Lifecycle` represents
9
+ * a unit of infrastructure that can be built within a CDK construct tree.
10
+ *
11
+ * Components implement this interface to define what resources they create
12
+ * and what dependencies they require. The {@link compose} function assembles
13
+ * components into a system, resolving dependencies and invoking `build` in
14
+ * the correct order.
15
+ *
16
+ * @typeParam T - The record of resources and values this component produces when built.
17
+ * @typeParam Context - The resolved dependencies this component requires, keyed by component name.
18
+ */
19
+ export interface Lifecycle<T extends object = object, Context extends LifecycleComponentBase = LifecycleComponentBase> {
20
+ build(scope: IConstruct, id: string, context?: Context): T;
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,KAAK,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS,CACxB,CAAC,SAAS,MAAM,GAAG,MAAM,EACzB,OAAO,SAAS,sBAAsB,GAAG,sBAAsB;IAE/D,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;CAC5D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":""}
package/dist/ref.d.ts ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A lazy reference to a value produced by another component at build time.
3
+ *
4
+ * `Ref` enables declarative cross-component wiring: a builder can capture a
5
+ * reference to a dependency's output at configuration time, and the value is
6
+ * resolved when the system is built. This keeps all configuration in one
7
+ * place — no split between eager props and deferred hooks.
8
+ *
9
+ * Create a `Ref` with the {@link ref} factory, then optionally narrow it
10
+ * with {@link Ref.get | .get()} or transform it with {@link Ref.map | .map()}.
11
+ *
12
+ * @typeParam T - The type of the value this reference resolves to.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // Reference a component's full build output
17
+ * ref<FunctionBuilderResult>("handler")
18
+ *
19
+ * // Narrow to a specific property
20
+ * ref<FunctionBuilderResult>("handler").get("function")
21
+ *
22
+ * // Transform the referenced value
23
+ * ref<FunctionBuilderResult>("handler")
24
+ * .get("function")
25
+ * .map(fn => new LambdaIntegration(fn))
26
+ * ```
27
+ */
28
+ export declare class Ref<T> {
29
+ private readonly _resolver;
30
+ private constructor();
31
+ /**
32
+ * Creates a `Ref` that resolves to a component's full build output.
33
+ *
34
+ * @param component - The key of the component in the composed system.
35
+ * @returns A `Ref` to the component's build result.
36
+ */
37
+ static to<T extends object>(component: string): Ref<T>;
38
+ /**
39
+ * Narrows this reference to a specific property of the resolved value.
40
+ *
41
+ * @param key - The property key to select.
42
+ * @returns A new `Ref` to the selected property.
43
+ */
44
+ get<K extends keyof T>(key: K): Ref<T[K]>;
45
+ /**
46
+ * Transforms the resolved value using the provided function.
47
+ *
48
+ * This is the primary way to adapt a dependency's output into the shape
49
+ * a consumer needs — for example, wrapping a Lambda function in a
50
+ * `LambdaIntegration`.
51
+ *
52
+ * @param fn - A function that transforms the resolved value.
53
+ * @returns A new `Ref` whose resolved value is the result of `fn`.
54
+ */
55
+ map<U>(fn: (value: T) => U): Ref<U>;
56
+ /**
57
+ * Resolves this reference against a build context.
58
+ *
59
+ * Called internally during the build phase. Not typically called by users.
60
+ *
61
+ * @param context - The resolved dependency outputs, keyed by component name.
62
+ * @returns The resolved value.
63
+ */
64
+ resolve(context: Record<string, object>): T;
65
+ }
66
+ /**
67
+ * Creates a {@link Ref} to a component's build output within a composed system.
68
+ *
69
+ * Called with just a component key, it returns a `Ref` to the full build result
70
+ * that can be further narrowed with {@link Ref.get | .get()} or
71
+ * {@link Ref.map | .map()}.
72
+ *
73
+ * Called with a transform function, it returns a `Ref` whose resolved value is
74
+ * the result of applying the transform to the component's build output. This is
75
+ * a shorthand for `ref<T>(component).map(transform)`.
76
+ *
77
+ * @param component - The key of the component in the composed system.
78
+ * @param transform - Optional function that transforms the component's build output.
79
+ * @returns A `Ref` to the component's build result, optionally transformed.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * // Without transform — chain .get() / .map() as needed
84
+ * ref<FunctionBuilderResult>("handler")
85
+ * .get("function")
86
+ * .map(fn => new LambdaIntegration(fn))
87
+ *
88
+ * // With transform — concise single-call form
89
+ * ref<FunctionBuilderResult>("handler", r => new LambdaIntegration(r.function))
90
+ * ```
91
+ */
92
+ export declare function ref<T extends object>(component: string): Ref<T>;
93
+ export declare function ref<T extends object, U>(component: string, transform: (value: T) => U): Ref<U>;
94
+ /**
95
+ * A value that is either concrete or a lazy {@link Ref} resolved at build time.
96
+ *
97
+ * Builders accept `Resolvable<T>` wherever they would normally accept `T`,
98
+ * making refs and concrete values interchangeable at the call site.
99
+ */
100
+ export type Resolvable<T> = T | Ref<T>;
101
+ /**
102
+ * Type guard that checks whether a value is a {@link Ref}.
103
+ */
104
+ export declare function isRef<T>(value: Resolvable<T>): value is Ref<T>;
105
+ /**
106
+ * Resolves a {@link Resolvable} value. If it is a {@link Ref}, resolves it
107
+ * against the provided context. Otherwise returns the value as-is.
108
+ *
109
+ * @param value - A concrete value or a `Ref`.
110
+ * @param context - The resolved dependency outputs, keyed by component name.
111
+ * @returns The concrete value.
112
+ */
113
+ export declare function resolve<T>(value: Resolvable<T>, context: Record<string, object>): T;
114
+ //# sourceMappingURL=ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../src/ref.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,GAAG,CAAC,CAAC;IACI,OAAO,CAAC,QAAQ,CAAC,SAAS;IAA9C,OAAO;IAEP;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;IAYtD;;;;;OAKG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAIzC;;;;;;;;;OASG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAInC;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC;CAG5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAEjE,wBAAgB,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAShG;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAEvC;;GAEG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAEnF"}
package/dist/ref.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A lazy reference to a value produced by another component at build time.
3
+ *
4
+ * `Ref` enables declarative cross-component wiring: a builder can capture a
5
+ * reference to a dependency's output at configuration time, and the value is
6
+ * resolved when the system is built. This keeps all configuration in one
7
+ * place — no split between eager props and deferred hooks.
8
+ *
9
+ * Create a `Ref` with the {@link ref} factory, then optionally narrow it
10
+ * with {@link Ref.get | .get()} or transform it with {@link Ref.map | .map()}.
11
+ *
12
+ * @typeParam T - The type of the value this reference resolves to.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // Reference a component's full build output
17
+ * ref<FunctionBuilderResult>("handler")
18
+ *
19
+ * // Narrow to a specific property
20
+ * ref<FunctionBuilderResult>("handler").get("function")
21
+ *
22
+ * // Transform the referenced value
23
+ * ref<FunctionBuilderResult>("handler")
24
+ * .get("function")
25
+ * .map(fn => new LambdaIntegration(fn))
26
+ * ```
27
+ */
28
+ export class Ref {
29
+ _resolver;
30
+ constructor(_resolver) {
31
+ this._resolver = _resolver;
32
+ }
33
+ /**
34
+ * Creates a `Ref` that resolves to a component's full build output.
35
+ *
36
+ * @param component - The key of the component in the composed system.
37
+ * @returns A `Ref` to the component's build result.
38
+ */
39
+ static to(component) {
40
+ return new Ref((context) => {
41
+ if (!(component in context)) {
42
+ throw new Error(`Ref to "${component}" cannot be resolved: component not found in context. ` +
43
+ `Ensure "${component}" is declared as a dependency.`);
44
+ }
45
+ return context[component];
46
+ });
47
+ }
48
+ /**
49
+ * Narrows this reference to a specific property of the resolved value.
50
+ *
51
+ * @param key - The property key to select.
52
+ * @returns A new `Ref` to the selected property.
53
+ */
54
+ get(key) {
55
+ return new Ref((context) => this._resolver(context)[key]);
56
+ }
57
+ /**
58
+ * Transforms the resolved value using the provided function.
59
+ *
60
+ * This is the primary way to adapt a dependency's output into the shape
61
+ * a consumer needs — for example, wrapping a Lambda function in a
62
+ * `LambdaIntegration`.
63
+ *
64
+ * @param fn - A function that transforms the resolved value.
65
+ * @returns A new `Ref` whose resolved value is the result of `fn`.
66
+ */
67
+ map(fn) {
68
+ return new Ref((context) => fn(this._resolver(context)));
69
+ }
70
+ /**
71
+ * Resolves this reference against a build context.
72
+ *
73
+ * Called internally during the build phase. Not typically called by users.
74
+ *
75
+ * @param context - The resolved dependency outputs, keyed by component name.
76
+ * @returns The resolved value.
77
+ */
78
+ resolve(context) {
79
+ return this._resolver(context);
80
+ }
81
+ }
82
+ export function ref(component, transform) {
83
+ const base = Ref.to(component);
84
+ return transform ? base.map(transform) : base;
85
+ }
86
+ /**
87
+ * Type guard that checks whether a value is a {@link Ref}.
88
+ */
89
+ export function isRef(value) {
90
+ return value instanceof Ref;
91
+ }
92
+ /**
93
+ * Resolves a {@link Resolvable} value. If it is a {@link Ref}, resolves it
94
+ * against the provided context. Otherwise returns the value as-is.
95
+ *
96
+ * @param value - A concrete value or a `Ref`.
97
+ * @param context - The resolved dependency outputs, keyed by component name.
98
+ * @returns The concrete value.
99
+ */
100
+ export function resolve(value, context) {
101
+ return isRef(value) ? value.resolve(context) : value;
102
+ }
103
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../src/ref.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,GAAG;IACuB;IAArC,YAAqC,SAAiD;QAAjD,cAAS,GAAT,SAAS,CAAwC;IAAG,CAAC;IAE1F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAmB,SAAiB;QAC3C,OAAO,IAAI,GAAG,CAAI,CAAC,OAAO,EAAE,EAAE;YAC5B,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CACb,WAAW,SAAS,wDAAwD;oBAC1E,WAAW,SAAS,gCAAgC,CACvD,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,SAAS,CAAM,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAoB,GAAM;QAC3B,OAAO,IAAI,GAAG,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IAED;;;;;;;;;OASG;IACH,GAAG,CAAI,EAAmB;QACxB,OAAO,IAAI,GAAG,CAAI,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,OAA+B;QACrC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;CACF;AA+BD,MAAM,UAAU,GAAG,CACjB,SAAiB,EACjB,SAA2B;IAE3B,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAI,SAAS,CAAC,CAAC;IAClC,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAUD;;GAEG;AACH,MAAM,UAAU,KAAK,CAAI,KAAoB;IAC3C,OAAO,KAAK,YAAY,GAAG,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAI,KAAoB,EAAE,OAA+B;IAC9E,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { type IConstruct } from "constructs";
2
+ /**
3
+ * A factory function that creates scopes (typically Stacks) for components.
4
+ * Accepts any scope constructor — `Stack`, `DeploymentStack`, or custom
5
+ * subclasses.
6
+ *
7
+ * @param scope - The parent scope (typically an `App`).
8
+ * @param id - A unique identifier for the new scope.
9
+ * @returns A new scope to attach components to.
10
+ */
11
+ export type ScopeFactory = (scope: IConstruct, id: string) => IConstruct;
12
+ /**
13
+ * Determines which scope a component should be built in.
14
+ *
15
+ * A `StackStrategy` is passed the parent scope, a system identifier,
16
+ * and each component's key. It returns the scope that component should
17
+ * use. Strategies can create scopes lazily, reuse them across components,
18
+ * or delegate to a {@link ScopeFactory} for custom scope types.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Every component in one auto-created stack
23
+ * compose({ handler, api }, { handler: [], api: ["handler"] })
24
+ * .withStackStrategy(singleStack(myFactory))
25
+ * .build(app, "MySystem");
26
+ *
27
+ * // Components grouped by a key function
28
+ * compose({ handler, api, table }, { ... })
29
+ * .withStackStrategy(groupedStacks(key => key === "table" ? "persistence" : "service", myFactory))
30
+ * .build(app, "MySystem");
31
+ * ```
32
+ */
33
+ export interface StackStrategy {
34
+ /**
35
+ * Returns the scope a component should be built in.
36
+ *
37
+ * @param scope - The parent scope passed to `build`.
38
+ * @param systemId - The system identifier passed to `build`.
39
+ * @param componentKey - The key of the component in the composed system.
40
+ * @returns The scope to use for this component.
41
+ */
42
+ resolve(scope: IConstruct, systemId: string, componentKey: string): IConstruct;
43
+ }
44
+ /**
45
+ * Creates a strategy that places all components in a single auto-created scope.
46
+ *
47
+ * The scope is created lazily on the first call to `resolve` and reused for
48
+ * all subsequent components.
49
+ *
50
+ * @param factory - Factory for creating the scope (e.g. a Stack).
51
+ * @returns A {@link StackStrategy} that groups all components into one scope.
52
+ */
53
+ export declare function singleStack(factory: ScopeFactory): StackStrategy;
54
+ /**
55
+ * Creates a strategy that groups components into named scopes determined by
56
+ * a classifier function.
57
+ *
58
+ * Components that return the same group key share a scope. Scopes are created
59
+ * lazily as new group keys are encountered.
60
+ *
61
+ * @param classify - A function that maps a component key to a group name.
62
+ * @param factory - Factory for creating scopes (e.g. Stacks). The factory
63
+ * receives `${systemId}-${group}` as the id.
64
+ * @returns A {@link StackStrategy} that groups components by classifier output.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * groupedStacks(
69
+ * key => key === "table" ? "persistence" : "service",
70
+ * (app, id) => new Stack(app, id),
71
+ * )
72
+ * ```
73
+ */
74
+ export declare function groupedStacks(classify: (componentKey: string) => string, factory: ScopeFactory): StackStrategy;
75
+ //# sourceMappingURL=stack-strategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-strategy.d.ts","sourceRoot":"","sources":["../src/stack-strategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC;CAChF;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAUhE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,EAC1C,OAAO,EAAE,YAAY,GACpB,aAAa,CAaf"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Creates a strategy that places all components in a single auto-created scope.
3
+ *
4
+ * The scope is created lazily on the first call to `resolve` and reused for
5
+ * all subsequent components.
6
+ *
7
+ * @param factory - Factory for creating the scope (e.g. a Stack).
8
+ * @returns A {@link StackStrategy} that groups all components into one scope.
9
+ */
10
+ export function singleStack(factory) {
11
+ return {
12
+ resolve: (() => {
13
+ let stack;
14
+ return (scope, systemId) => {
15
+ stack ??= factory(scope, systemId);
16
+ return stack;
17
+ };
18
+ })(),
19
+ };
20
+ }
21
+ /**
22
+ * Creates a strategy that groups components into named scopes determined by
23
+ * a classifier function.
24
+ *
25
+ * Components that return the same group key share a scope. Scopes are created
26
+ * lazily as new group keys are encountered.
27
+ *
28
+ * @param classify - A function that maps a component key to a group name.
29
+ * @param factory - Factory for creating scopes (e.g. Stacks). The factory
30
+ * receives `${systemId}-${group}` as the id.
31
+ * @returns A {@link StackStrategy} that groups components by classifier output.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * groupedStacks(
36
+ * key => key === "table" ? "persistence" : "service",
37
+ * (app, id) => new Stack(app, id),
38
+ * )
39
+ * ```
40
+ */
41
+ export function groupedStacks(classify, factory) {
42
+ const groups = new Map();
43
+ return {
44
+ resolve(scope, systemId, componentKey) {
45
+ const group = classify(componentKey);
46
+ let groupScope = groups.get(group);
47
+ if (!groupScope) {
48
+ groupScope = factory(scope, `${systemId}-${group}`);
49
+ groups.set(group, groupScope);
50
+ }
51
+ return groupScope;
52
+ },
53
+ };
54
+ }
55
+ //# sourceMappingURL=stack-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-strategy.js","sourceRoot":"","sources":["../src/stack-strategy.ts"],"names":[],"mappings":"AA8CA;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,OAAqB;IAC/C,OAAO;QACL,OAAO,EAAE,CAAC,GAAG,EAAE;YACb,IAAI,KAA6B,CAAC;YAClC,OAAO,CAAC,KAAiB,EAAE,QAAgB,EAAE,EAAE;gBAC7C,KAAK,KAAK,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACnC,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;QACJ,CAAC,CAAC,EAAE;KACL,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA0C,EAC1C,OAAqB;IAErB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC7C,OAAO;QACL,OAAO,CAAC,KAAiB,EAAE,QAAgB,EAAE,YAAoB;YAC/D,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;YACrC,IAAI,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC;gBACpD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,UAAU,CAAC;QACpB,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@composurecdk/core",
3
+ "version": "0.1.0",
4
+ "description": "Composable CDK component system — lifecycle, dependency resolution, and builder pattern",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "clean": "rm -rf dist",
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "lint-staged": {
26
+ "*.ts": [
27
+ "eslint --fix",
28
+ "prettier --write"
29
+ ]
30
+ },
31
+ "keywords": [],
32
+ "author": "Jason Duffett (https://github.com/laazyj)",
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "type": "module",
38
+ "peerDependencies": {
39
+ "constructs": "^10.6.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^25.5.0",
43
+ "typescript": "^6.0.2",
44
+ "vitest": "^4.1.2"
45
+ },
46
+ "dependencies": {
47
+ "@dagrejs/graphlib": "^4.0.1"
48
+ }
49
+ }