packages/@alicloud/ros-cdk-core/lib/construct-compat.ts (213 lines of code) (raw):
/**
* Constructs compatibility layer.
*
* This file, in its entirety, is expected to be removed in v2.0.
*/
import * as cxschema from "@alicloud/ros-cdk-assembly-schema";
import * as cxapi from "@alicloud/ros-cdk-cxapi";
import * as constructs from "constructs";
import { IDependable } from "./dependency";
import { Token } from "./token";
const ORIGINAL_CONSTRUCT_NODE_SYMBOL = Symbol.for("ros-cdk-core.ConstructNode");
const CONSTRUCT_SYMBOL = Symbol.for("ros-cdk-core.Construct");
/**
* Represents a construct.
*/
export interface IConstruct extends constructs.IConstruct, IDependable {
/**
* The construct tree node for this construct.
*/
readonly node: ConstructNode;
}
/**
* Represents a single session of synthesis. Passed into `Construct.synthesize()` methods.
*/
export interface ISynthesisSession {
/**
* The output directory for this synthesis session.
*/
outdir: string;
/**
* Cloud assembly builder.
*/
assembly: cxapi.CloudAssemblyBuilder;
/**
* Whether the stack should be validated after synthesis to check for error metadata
*
* @default - false
*/
validateOnSynth?: boolean;
}
/**
* Represents the building block of the construct graph.
*
* All constructs besides the root construct must be created within the scope of
* another construct.
*/
export class Construct extends constructs.Construct implements IConstruct {
/**
* Return whether the given object is a Construct
*/
public static isConstruct(x: any): x is Construct {
return typeof x === "object" && x !== null && CONSTRUCT_SYMBOL in x;
}
/**
* The construct tree node associated with this construct.
*/
public readonly node: ConstructNode;
constructor(scope: Construct, id: string) {
super(scope, id, {
nodeFactory: {
createNode: (
h: constructs.Construct,
s: constructs.IConstruct,
i: string
) => new ConstructNode(h as Construct, s as IConstruct, i)._actualNode,
},
});
if (Token.isUnresolved(id)) {
throw new Error(`Cannot use tokens in construct ID: ${id}`);
}
Object.defineProperty(this, CONSTRUCT_SYMBOL, { value: true });
this.node = ConstructNode._unwrap(constructs.Node.of(this));
const disableTrace =
this.node.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE) ||
this.node.tryGetContext(
constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA
) ||
process.env.CDK_DISABLE_STACK_TRACE;
if (disableTrace) {
this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true);
this.node.setContext(
constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA,
true
);
process.env.CDK_DISABLE_STACK_TRACE = "1";
}
}
/**
* Validate the current construct.
*
* This method can be implemented by derived constructs in order to perform
* validation logic. It is called on all constructs before synthesis.
*
* @returns An array of validation error messages, or an empty array if the construct is valid.
*/
protected onValidate(): string[] {
return this.validate();
}
/**
* Perform final modifications before synthesis
*
* This method can be implemented by derived constructs in order to perform
* final changes before synthesis. prepare() will be called after child
* constructs have been prepared.
*
* This is an advanced framework feature. Only use this if you
* understand the implications.
*/
protected onPrepare(): void {
this.prepare();
}
/**
* Allows this construct to emit artifacts into the cloud assembly during synthesis.
*
* This method is usually implemented by framework-level constructs such as `Stack` and `Asset`
* as they participate in synthesizing the cloud assembly.
*
* @param session The synthesis session.
*/
protected onSynthesize(session: constructs.ISynthesisSession): void {
this.synthesize({
outdir: session.outdir,
assembly: session.assembly!,
});
}
/**
* Validate the current construct.
*
* This method can be implemented by derived constructs in order to perform
* validation logic. It is called on all constructs before synthesis.
*
* @returns An array of validation error messages, or an empty array if the construct is valid.
*/
protected validate(): string[] {
return [];
}
/**
* Perform final modifications before synthesis
*
* This method can be implemented by derived constructs in order to perform
* final changes before synthesis. prepare() will be called after child
* constructs have been prepared.
*
* This is an advanced framework feature. Only use this if you
* understand the implications.
*/
protected prepare(): void {
return;
}
/**
* Allows this construct to emit artifacts into the cloud assembly during synthesis.
*
* This method is usually implemented by framework-level constructs such as `Stack` and `Asset`
* as they participate in synthesizing the cloud assembly.
*
* @param session The synthesis session.
*/
public synthesize(session: ISynthesisSession): void {
ignore(session);
}
}
/**
* In what order to return constructs
*/
export enum ConstructOrder {
/**
* Depth-first, pre-order
*/
PREORDER,
/**
* Depth-first, post-order (leaf nodes first)
*/
POSTORDER,
}
/**
* Options for synthesis.
*
* @deprecated use `app.synth()` or `stage.synth()` instead
*/
export interface SynthesisOptions extends cxapi.AssemblyBuildOptions {
/**
* The output directory into which to synthesize the cloud assembly.
* @default - creates a temporary directory
*/
readonly outdir?: string;
/**
* Whether synthesis should skip the validation phase.
* @default false
*/
readonly skipValidation?: boolean;
}
/**
* Represents the construct node in the scope tree.
*/
export class ConstructNode {
/**
* Separator used to delimit construct path components.
*/
public static readonly PATH_SEP = "/";
/**
* Returns the wrapping `ros-cdk-core.ConstructNode` instance from a `constructs.ConstructNode`.
*
* @internal
*/
public static _unwrap(c: constructs.Node): ConstructNode {
const x = (c as any)[ORIGINAL_CONSTRUCT_NODE_SYMBOL];
if (!x) {
throw new Error("invalid ConstructNode type");
}
return x;
}
/**
* Synthesizes a CloudAssembly from a construct tree.
* @param node The root of the construct tree.
* @param options Synthesis options.
* @deprecated Use `app.synth()` or `stage.synth()` instead
*/
public static synth(
node: ConstructNode,
options: SynthesisOptions = {}
): cxapi.CloudAssembly {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const a: typeof import("./private/synthesis") = require("./private/synthesis");
return a.synthesize(node.root, options);
}
/**
* Invokes "prepare" on all constructs (depth-first, post-order) in the tree under `node`.
* @param node The root node
* @deprecated Use `app.synth()` instead
*/
public static prepare(node: ConstructNode) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
// const p: typeof import('./private/prepare-app') = require('./private/prepare-app');
const p: typeof import("./private/prepare-app") = require("./private/prepare-app");
p.prepareApp(node.root); // resolve cross refs and nested stack assets.
return node._actualNode.prepare();
}
/**
* Invokes "validate" on all constructs in the tree (depth-first, pre-order) and returns
* the list of all errors. An empty list indicates that there are no errors.
*
* @param node The root node
*/
public static validate(node: ConstructNode): ValidationError[] {
return node._actualNode
.validate()
.map((e) => ({ source: e.source as Construct, message: e.message }));
}
/**
* @internal
*/
public readonly _actualNode: constructs.Node;
constructor(host: Construct, scope: IConstruct, id: string) {
this._actualNode = new constructs.Node(host, scope, id);
// store a back reference on _actualNode so we can our ConstructNode from it
Object.defineProperty(this._actualNode, ORIGINAL_CONSTRUCT_NODE_SYMBOL, {
value: this,
configurable: false,
enumerable: false,
});
}
/**
* Returns the scope in which this construct is defined.
*
* The value is `undefined` at the root of the construct scope tree.
*/
public get scope(): IConstruct | undefined {
return this._actualNode.scope as IConstruct;
}
/**
* The id of this construct within the current scope.
*
* This is a a scope-unique id. To obtain an app-unique id for this construct, use `uniqueId`.
*/
public get id() {
return this._actualNode.id;
}
/**
* The full, absolute path of this construct in the tree.
*
* Components are separated by '/'.
*/
public get path(): string {
return this._actualNode.path;
}
/**
* A tree-global unique alphanumeric identifier for this construct.
* Includes all components of the tree.
*/
public get uniqueId(): string {
return this._actualNode.uniqueId;
}
/**
* Return a direct child by id, or undefined
*
* @param id Identifier of direct child
* @returns the child if found, or undefined
*/
public tryFindChild(id: string): IConstruct | undefined {
return this._actualNode.tryFindChild(id) as IConstruct;
}
/**
* Return a direct child by id
*
* Throws an error if the child is not found.
*
* @param id Identifier of direct child
* @returns Child with the given id.
*/
public findChild(id: string): IConstruct {
return this._actualNode.findChild(id) as IConstruct;
}
/**
* Returns the child construct that has the id `Default` or `Resource"`.
* This is usually the construct that provides the bulk of the underlying functionality.
* Useful for modifications of the underlying construct that are not available at the higher levels.
*
* @throws if there is more than one child
* @returns a construct or undefined if there is no default child
*/
public get defaultChild(): IConstruct | undefined {
return this._actualNode.defaultChild as IConstruct;
}
/**
* Override the defaultChild property.
*
* This should only be used in the cases where the correct
* default child is not named 'Resource' or 'Default' as it
* should be.
*
* If you set this to undefined, the default behavior of finding
* the child named 'Resource' or 'Default' will be used.
*/
public set defaultChild(value: IConstruct | undefined) {
this._actualNode.defaultChild = value;
}
/**
* All direct children of this construct.
*/
public get children(): IConstruct[] {
return this._actualNode.children as IConstruct[];
}
/**
* Return this construct and all of its children in the given order
*/
public findAll(
order: ConstructOrder = ConstructOrder.PREORDER
): IConstruct[] {
return this._actualNode.findAll(order) as IConstruct[];
}
/**
* This can be used to set contextual values.
* Context must be set before any children are added, since children may consult context info during construction.
* If the key already exists, it will be overridden.
* @param key The context key
* @param value The context value
*/
public setContext(key: string, value: any) {
if (Token.isUnresolved(key)) {
throw new Error("Invalid context key: context keys can't include tokens");
}
this._actualNode.setContext(key, value);
}
/**
* Retrieves a value from tree context.
*
* Context is usually initialized at the root, but can be overridden at any point in the tree.
*
* @param key The context key
* @returns The context value or `undefined` if there is no context value for thie key.
*/
public tryGetContext(key: string): any {
if (Token.isUnresolved(key)) {
throw new Error("Invalid context key: context keys can't include tokens");
}
return this._actualNode.tryGetContext(key);
}
/**
* An immutable array of metadata objects associated with this construct.
* This can be used, for example, to implement support for deprecation notices, source mapping, etc.
*/
public get metadata() {
return this._actualNode.metadata as cxapi.MetadataEntry[];
}
/**
* Adds a metadata entry to this construct.
* Entries are arbitrary values and will also include a stack trace to allow tracing back to
* the code location for when the entry was added. It can be used, for example, to include source
* mapping in templates to improve diagnostics.
*
* @param type a string denoting the type of metadata
* @param data the value of the metadata (can be a Token). If null/undefined, metadata will not be added.
* @param fromFunction a function under which to restrict the metadata entry's stack trace (defaults to this.addMetadata)
*/
public addMetadata(type: string, data: any, fromFunction?: any): void {
this._actualNode.addMetadata(type, data, fromFunction);
}
/**
* Adds a { "info": <message> } metadata entry to this construct.
* The toolkit will display the info message when apps are synthesized.
* @param message The info message.
*/
public addInfo(message: string): void {
this._actualNode.addMetadata(
cxschema.ArtifactMetadataEntryType.INFO,
message
);
}
/**
* Adds a { "warning": <message> } metadata entry to this construct.
* The toolkit will display the warning when an app is synthesized, or fail
* if run in --strict mode.
* @param message The warning message.
*/
public addWarning(message: string): void {
this._actualNode.addMetadata(
cxschema.ArtifactMetadataEntryType.WARN,
message
);
}
/**
* Adds an { "error": <message> } metadata entry to this construct.
* The toolkit will fail synthesis when errors are reported.
* @param message The error message.
*/
public addError(message: string) {
this._actualNode.addMetadata(
cxschema.ArtifactMetadataEntryType.ERROR,
message
);
}
/**
* All parent scopes of this construct.
*
* @returns a list of parent scopes. The last element in the list will always
* be the current construct and the first element will be the root of the
* tree.
*/
public get scopes(): IConstruct[] {
return this._actualNode.scopes as IConstruct[];
}
/**
* @returns The root of the construct tree.
*/
public get root(): IConstruct {
return this._actualNode.root as IConstruct;
}
/**
* Returns true if this construct or the scopes in which it is defined are
* locked.
*/
public get locked() {
return this._actualNode.locked;
}
/**
* Add an ordering dependency on another Construct.
*
* All constructs in the dependency's scope will be deployed before any
* construct in this construct's scope.
*/
public addDependency(...dependencies: IDependable[]) {
this._actualNode.addDependency(...dependencies);
}
/**
* Return all dependencies registered on this node or any of its children
*/
public get dependencies(): Dependency[] {
return this._actualNode.dependencies as Dependency[];
}
/**
* Remove the child with the given name, if present.
*
* @returns Whether a child with the given name was deleted.
* @experimental
*/
public tryRemoveChild(childName: string): boolean {
return this._actualNode.tryRemoveChild(childName);
}
}
/**
* An error returned during the validation phase.
*/
export interface ValidationError {
/**
* The construct which emitted the error.
*/
readonly source: Construct;
/**
* The error message.
*/
readonly message: string;
}
/**
* A single dependency
*/
export interface Dependency {
/**
* Source the dependency
*/
readonly source: IConstruct;
/**
* Target of the dependency
*/
readonly target: IConstruct;
}
function ignore(_x: any) {
return;
}