aws-cdk-lib
Version:
Version 2 of the AWS Cloud Development Kit library
358 lines (357 loc) • 13.4 kB
TypeScript
import type { IResolvable } from '../resolvable';
export type MakeReadonly<A> = A extends Set<infer E> ? ReadonlySet<E> : A extends Map<infer K, infer E> ? ReadonlyMap<K, E> : A extends Array<infer E> ? ReadonlyArray<E> : A extends object ? Readonly<A> : A;
/**
* A read-only observable container that holds a value of type `A` and implements `IResolvable`.
*
* Readable boxes participate in CDK token resolution: they can be passed into
* L1 construct properties via `Token.asString()`, `Token.asList()`, etc., and
* their value will be resolved at synthesis time.
*
* Readable boxes also track stack traces of mutations (when `CDK_DEBUG` is enabled),
* which are used to emit `aws:cdk:propertyAssignment` metadata linking CloudFormation
* property values back to the user code that produced them.
*/
export interface IReadableBox<A> extends IResolvable {
/**
* Returns the current value held by this box.
*
* The returned value is wrapped in `MakeReadonly` so that consumers cannot
* mutate it in place. All mutations must go through the box's own API
* (e.g. `set`, `push`, `put`, `add`).
*/
get(): MakeReadonly<A>;
/**
* Returns the current value held by this box as a mutable type.
*
* Unlike `get()`, the returned value is not wrapped in `MakeReadonly`,
* allowing direct mutation of the underlying value.
*/
getMutable(): A;
/**
* Creates a new read-only box whose value is derived by applying `fn` to this box's value.
*
* The derived box inherits the stack traces of its source, so mutations to
* this box are correctly attributed to all CloudFormation properties that
* consume the derived value.
*
* @param fn a pure transformation function.
* @returns a new read-only box that recomputes on every `get()` / `resolve()`.
*/
derive<B>(fn: (a: MakeReadonly<A>) => B): IReadableBox<B>;
/**
* Returns the stack traces captured by this box.
*
* Each entry corresponds to a mutation (`set`, `push`, or initial construction)
* that occurred while stack trace collection was enabled (`CDK_DEBUG=1`).
* Returns an empty array when debug mode is off or no mutations were recorded.
*/
getStackTraces(): Array<StackTrace>;
}
/**
* A mutable box that extends `IReadableBox` with the ability to replace its value.
*
* When `set` is called (and the new value differs from the current one), the box
* replaces its stored stack traces with a single new trace captured at the call
* site, so that the metadata points to the code that last changed the value.
*/
export interface IBox<A> extends IReadableBox<A> {
/**
* Replaces the value held by this box.
*
* If the new value is equal to the current value (by reference equality, or
* by the custom `equals` function provided at construction), this is a no-op
* and no stack trace is captured.
*
* @param a the new value.
*/
set(a: A): void;
}
/**
* A mutable box specialized for arrays, extending `Box<Array<A>>` with `push`.
*
* Unlike `set` (which replaces all stack traces), `push` *appends* a new stack
* trace to the existing list. This means that each element addition is tracked
* individually, and the resulting metadata will contain one entry per `push` call
* (plus one for the initial construction or last `set`, if any).
*/
export interface IArrayBox<A> extends IBox<Array<A>>, Iterable<A> {
/**
* Returns the number of elements in the array.
*/
readonly length: number;
/**
* Appends one or more elements to the array and captures a stack trace for this addition.
*
* @param items the elements to append.
*/
push(...items: A[]): void;
/**
* Removes the last element from the array and captures a stack trace for this removal.
*
* @returns the removed element, or `undefined` if the array is empty.
*/
pop(): A | undefined;
/**
* Returns the index of the first element that satisfies the predicate, or -1.
*
* Delegates to `Array.prototype.findIndex` on the underlying array.
*
* @param predicate a function called for each element.
* @returns the index of the first matching element, or -1.
*/
findIndex(predicate: (value: A, index: number, obj: Array<A>) => unknown): number;
/**
* Returns the first element that satisfies the predicate, or `undefined`.
*
* Delegates to `Array.prototype.find` on the underlying array.
*
* @param predicate a function called for each element.
* @returns the first matching element, or `undefined`.
*/
find(predicate: (value: A, index: number, obj: Array<A>) => unknown): A | undefined;
/**
* Removes elements from the array and optionally inserts new elements in their place.
*
* Delegates to `Array.prototype.splice` on the underlying array.
*
* @param start the zero-based index at which to start changing the array.
* @param deleteCount the number of elements to remove.
* @param items elements to insert at `start`.
* @returns an array of the removed elements.
*/
splice(start: number, deleteCount: number, ...items: A[]): A[];
/**
* Tests whether at least one element in the array passes the predicate.
*
* Delegates to `Array.prototype.some` on the underlying array.
*
* @param predicate a function called for each element.
* @returns `true` if the predicate returns a truthy value for at least one element, otherwise `false`.
*/
some(predicate: (value: A, index: number, obj: Array<A>) => unknown): boolean;
/**
* Creates a derived read-only box by applying `fn` to each element of the array.
*
* Shorthand for `this.derive(a => a.map(fn))`.
*
* @param fn a pure transformation applied to each element.
* @returns a new read-only box holding the mapped array.
*/
map<B>(fn: (a: A) => B): IReadableBox<Array<B>>;
}
/**
* A mutable box specialized for maps, extending `Box<Map<K, V>>` with
* map-mutation methods.
*
* Like `ArrayBox`, mutating methods (`put`, `delete`) *append* stack traces
* rather than replacing them, so each mutation is tracked individually.
*/
export interface IMapBox<K, V> extends IBox<Map<K, V>>, Iterable<[K, V]> {
/**
* Returns the number of entries in the map.
*/
readonly size: number;
/**
* Sets a key-value pair in the map and captures a stack trace for this mutation.
*
* @param key the key.
* @param value the value.
*/
put(key: K, value: V): void;
/**
* Removes a key from the map and captures a stack trace for this mutation.
*
* @param key the key to remove.
* @returns `true` if the key existed and was removed, `false` otherwise.
*/
delete(key: K): boolean;
/**
* Returns whether the map contains the given key.
*
* @param key the key to check.
*/
has(key: K): boolean;
/**
* Returns the value associated with the given key, or `undefined`.
*
* @param key the key to look up.
*/
getEntry(key: K): V | undefined;
/**
* Returns an iterable of the map's keys.
*/
keys(): IterableIterator<K>;
/**
* Returns an iterable of the map's values.
*/
values(): IterableIterator<V>;
/**
* Returns an iterable of the map's [key, value] pairs.
*/
entries(): IterableIterator<[K, V]>;
/**
* Executes a callback for each entry in the map.
*
* @param callbackfn a function called for each entry.
*/
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void): void;
}
/**
* A mutable box specialized for sets, extending `Box<Set<A>>` with
* set-mutation methods.
*
* Like `ArrayBox`, mutating methods (`add`, `delete`) *append* stack traces
* rather than replacing them, so each mutation is tracked individually.
*/
export interface ISetBox<A> extends IBox<Set<A>>, Iterable<A> {
/**
* Returns the number of elements in the set.
*/
readonly size: number;
/**
* Adds a value to the set and captures a stack trace for this mutation.
*
* @param value the value to add.
*/
add(value: A): void;
/**
* Removes a value from the set and captures a stack trace for this mutation.
*
* @param value the value to remove.
* @returns `true` if the value existed and was removed, `false` otherwise.
*/
delete(value: A): boolean;
/**
* Returns whether the set contains the given value.
*
* @param value the value to check.
*/
has(value: A): boolean;
/**
* Returns an iterable of the set's values.
*/
values(): IterableIterator<A>;
/**
* Returns an iterable of the set's [value, value] pairs (for compatibility with `Map`).
*/
entries(): IterableIterator<[A, A]>;
/**
* Executes a callback for each value in the set.
*
* @param callbackfn a function called for each value.
*/
forEach(callbackfn: (value: A, value2: A, set: Set<A>) => void): void;
}
type StackTrace = Array<string>;
/**
* Factory for creating boxes.
*
* Boxes are mutable, observable containers that implement `IResolvable` and
* capture stack traces when mutated (under `CDK_DEBUG`). They are intended as a
* replacement for the `Lazy` API in L2 constructs: where `Lazy` captures a
* stack trace only at creation time (typically inside the L2 constructor),
* boxes capture traces at the point of mutation — which is usually in user code.
*
* ### Typical L2 usage
*
* ```ts
* @noBoxStackTraces
* class MyL2 extends Resource {
* private readonly items: ArrayBox<string>;
*
* constructor(scope: Construct, id: string) {
* super(scope, id);
* this.items = Boxes.fromArray([]);
* new CfnResource(this, 'Resource', {
* items: Token.asList(this.items),
* });
* }
*
* addItem(item: string) {
* this.items.push(item); // stack trace captured here
* }
* }
* ```
*/
export declare class Box {
/**
* Creates a mutable box holding a single value.
*
* @param value the initial value.
* @param options.equals optional equality function used to skip no-op `set` calls.
* Defaults to reference equality (`===`).
* @returns a new `Box<A>`.
*/
static fromValue<A>(value: A, options?: {
equals?: (a: A, b: A) => boolean;
}): IBox<A>;
/**
* Creates a read-only box that combines multiple source boxes through a function.
*
* The resulting box collects stack traces from all source boxes, sorted by
* the global mutation order. This is useful when a single CloudFormation
* property depends on multiple pieces of L2 state.
*
* @param boxes a record of named source boxes.
* @param fn a pure function that receives the unwrapped values and produces the result.
* @returns a new read-only `IReadableBox<R>`.
* @example
* const a = Boxes.fromValue(10);
* const b = Boxes.fromValue(20);
* const result = Boxes.combine({ a, b }, (x) => x.a + x.b).derive((x) => x * 2);
* result.get(); // 60
*/
static combine<T extends Record<string, IReadableBox<any>>, R>(boxes: T, fn: (values: {
[K in keyof T]: T[K] extends IReadableBox<infer U> ? MakeReadonly<U> : never;
}) => R): IReadableBox<R>;
/**
* Type guard that checks whether a value is a box.
*
* Used internally by `PropertyAssignmentMetadataWriter` to decide whether a
* resolved token should contribute `aws:cdk:propertyAssignment` metadata.
*/
static isBox(x: any): x is IReadableBox<any>;
/**
* Creates a mutable array box.
*
* @param as the initial array contents.
* @param options.omitEmpty if true (the default), the box resolves to `undefined` when the
* array is empty. This behavior propagates through `map`, `derive`, and
* `reduce`, so derived boxes also resolve to `undefined` when the source
* array is empty. Set to `false` to resolve to an empty array instead.
* @returns a new `ArrayBox<A>`.
*/
static fromArray<A>(as: Array<A>, options?: {
omitEmpty?: boolean;
}): IArrayBox<A>;
/**
* Creates a mutable map box.
*
* @param map the initial map contents.
* @returns a new `MapBox<K, V>`.
*/
static fromMap<K, V>(map: Map<K, V>): IMapBox<K, V>;
/**
* Creates a mutable set box.
*
* @param set the initial set contents.
* @returns a new `SetBox<A>`.
*/
static fromSet<A>(set: Set<A>): ISetBox<A>;
/**
* Globally disables stack trace collection for all box mutations.
*
* Called by the `@noBoxStackTraces` decorator before entering an L2
* constructor, so that default/initial values set during construction
* do not produce misleading stack traces.
*/
static disableStackTraceCollection(): void;
/**
* Re-enables stack trace collection for all box mutations.
*
* Called by the `@noBoxStackTraces` decorator after the L2 constructor
* returns, so that subsequent mutations (from user code) are tracked.
*/
static enableStackTraceCollection(): void;
}
export {};