@aws/pdk
Version:
All documentation is located at: https://aws.github.io/aws-pdk
1,444 lines • 254 kB
JavaScript
"use strict";
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RootNode = exports.AppNode = exports.StageNode = exports.NestedStackNode = exports.StackNode = exports.ParameterNode = exports.OutputNode = exports.CfnResourceNode = exports.ResourceNode = exports.Node = exports.ImportReference = exports.AttributeReference = exports.Reference = exports.Dependency = exports.Edge = exports.BaseEntity = exports.Store = void 0;
exports.isResourceLike = isResourceLike;
exports.deserializeStore = deserializeStore;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0 */
const console_1 = require("console");
const constructs_1 = require("constructs");
const cloneDeep = require("lodash.clonedeep"); // eslint-disable-line @typescript-eslint/no-require-imports
const isEmpty = require("lodash.isempty"); // eslint-disable-line @typescript-eslint/no-require-imports
const omit = require("lodash.omit"); // eslint-disable-line @typescript-eslint/no-require-imports
const uniq = require("lodash.uniq"); // eslint-disable-line @typescript-eslint/no-require-imports
const counter_1 = require("./counter");
const types_1 = require("./types");
const utils_1 = require("./utils");
/** Store class provides the in-memory database-like interface for managing all entities in the graph */
class Store {
/** Builds store from serialized store data */
static fromSerializedStore(serializedStore) {
return deserializeStore(serializedStore);
}
constructor(allowDestructiveMutations = false) {
/** Current SemVer version of the store */
this.version = "0.0.0";
/** @internal */
this._edges = new Map();
/** @internal */
this._nodes = new Map();
/** @internal */
this._stacks = new Map();
/** @internal */
this._stages = new Map();
/** @internal */
this._logicalIdLookup = new Map();
/** @internal */
this._importArnTokenLookup = new Map();
/** @internal */
this._counters = {
cfnResources: new counter_1.Counter(),
nodeTypes: new counter_1.Counter(),
edgeTypes: new counter_1.Counter(),
};
this._root = new RootNode(this);
this.allowDestructiveMutations = allowDestructiveMutations;
}
/**
* Root node in the store. The **root** node is not the computed root, but the graph root
* which is auto-generated and can not be mutated.
*/
get root() {
return this._root;
}
/**
* Gets all stored **edges**
* @type ReadonlyArray<Edge>
*/
get edges() {
return Array.from(this._edges.values());
}
/**
* Gets all stored **nodes**
* @type ReadonlyArray<Node>
*/
get nodes() {
return Array.from(this._nodes.values());
}
/**
* Gets all stored **stack** nodes
* @type ReadonlyArray<StackNode>
*/
get stacks() {
return Array.from(this._stacks.values());
}
/**
* Gets all stored **stage** nodes
* @type ReadonlyArray<StageNode>
*/
get stages() {
return Array.from(this._stages.values());
}
/**
* Gets all stored **root stack** nodes
* @type ReadonlyArray<StackNode>
*/
get rootStacks() {
return this.stacks.filter((stack) => StackNode.isStackNode(stack));
}
/** Get record of all store counters */
get counts() {
return {
nodes: this._nodes.size,
edges: this._edges.size,
stacks: this._stacks.size,
stages: this._stages.size,
nodeTypes: this._counters.nodeTypes.counts,
edgeTypes: this._counters.edgeTypes.counts,
cfnResources: this._counters.cfnResources.counts,
};
}
/** Add **edge** to the store */
addEdge(edge) {
this._edges.set(edge.uuid, edge);
this._counters.edgeTypes.add(edge.edgeType);
}
/** Get stored **edge** by UUID */
getEdge(uuid) {
const edge = this._edges.get(uuid);
if (edge != null) {
return edge;
}
throw new Error(`Edge ${uuid} is not defined`);
}
/** Add **node** to the store */
addNode(node) {
// Do not store root node
if (RootNode.isRootNode(node) === true) {
return;
}
this._nodes.set(node.uuid, node);
this._counters.nodeTypes.add(node.nodeType);
if (CfnResourceNode.isCfnResourceNode(node) && node.cfnType) {
this._counters.cfnResources.add(node.cfnType);
}
}
/** Get stored **node** by UUID */
getNode(uuid) {
// Root node is not stored in "nodes" map
if (uuid === RootNode.UUID && this.root) {
return this.root;
}
const node = this._nodes.get(uuid);
if (node != null) {
return node;
}
throw new Error(`Node ${uuid} is not defined`);
}
/** Add **stack** node to the store */
addStack(stack) {
this._stacks.set(stack.uuid, stack);
}
/** Get stored **stack** node by UUID */
getStack(uuid) {
const stack = this._stacks.get(uuid);
if (stack != null) {
return stack;
}
throw new Error(`Stack ${uuid} is not defined`);
}
/** Add **stage** to the store */
addStage(stage) {
this._stages.set(stage.uuid, stage);
}
/** Get stored **stage** node by UUID */
getStage(uuid) {
const stage = this._stages.get(uuid);
if (stage != null) {
return stage;
}
throw new Error(`Stage ${uuid} is not defined`);
}
/**
* Compute **universal** *logicalId* based on parent stack and construct *logicalId* (`<stack>:<logicalId>`).
*
* Construct *logicalIds are only unique within their containing stack, so to use *logicalId*
* lookups universally (like resolving references) we need a universal key.
*/
computeLogicalUniversalId(stack, logicalId) {
return `${stack.uuid}:${logicalId}`;
}
/** Find node by **universal** *logicalId* (`<stack>:<logicalId>`) */
findNodeByLogicalUniversalId(uid) {
const [stackUUID, logicalId] = uid.split(":");
const stack = this.getStack(stackUUID);
return this.findNodeByLogicalId(stack, logicalId);
}
/** Find node within given **stack** with given *logicalId* */
findNodeByLogicalId(stack, logicalId) {
const uid = this.computeLogicalUniversalId(stack, logicalId);
const nodeUUID = this._logicalIdLookup.get(uid);
if (nodeUUID == null) {
if (stack instanceof NestedStackNode && stack.parentStack) {
return this.findNodeByLogicalId(stack.parentStack, logicalId);
}
throw new Error(`Failed to find node by logicalId: ${uid}`);
}
const node = this._nodes.get(nodeUUID);
if (node != null) {
return node;
}
throw new Error(`Unable to find node mapped to logical id ${logicalId}`);
}
/** Record a **universal** *logicalId* to node mapping in the store */
recordLogicalId(stack, logicalId, resource) {
const uid = this.computeLogicalUniversalId(stack, logicalId);
this._logicalIdLookup.set(uid, resource.uuid);
}
/**
* Records arn tokens from imported resources (eg: `s3.Bucket.fromBucketArn()`)
* that are used for resolving references.
*/
recordImportArn(arnToken, resource) {
this._importArnTokenLookup.set(arnToken, resource.uuid);
}
/**
* Attempts to lookup the {@link Node} associated with a given *import arn token*.
* @param value Import arn value, which is either object to tokenize or already tokenized string.
* @returns Returns matching {@link Node} if found, otherwise undefined.
*/
findNodeByImportArn(value) {
if (typeof value !== "string") {
value = (0, utils_1.tokenizeImportArn)(value);
}
const nodeUUID = this._importArnTokenLookup.get(value);
if (nodeUUID) {
return this._nodes.get(nodeUUID);
}
return undefined;
}
/** Serialize the store */
serialize() {
return {
version: this.version,
tree: this.root._serialize(),
edges: Array.from(this.edges).map((edge) => edge._serialize()),
};
}
/**
* Clone the store to allow destructive mutations.
* @param allowDestructiveMutations Indicates if destructive mutations are allowed; defaults to `true`
* @returns {Store} Returns a clone of the store that allows destructive mutations
*/
clone(allowDestructiveMutations = true) {
return deserializeStore(this.serialize(), allowDestructiveMutations);
}
/**
* Verifies that the store allows destructive mutations.
* @throws Error is store does **not** allow mutations
*/
verifyDestructiveMutationAllowed() {
if (!this.allowDestructiveMutations) {
throw new Error("GraphStore must be a clone to perform destructive mutations");
}
}
/**
* Remove **edge** from the store
* @destructive
*/
mutateRemoveEdge(edge) {
const deleted = this._edges.delete(edge.uuid);
if (deleted) {
this._counters.edgeTypes.subtract(edge.edgeType);
}
return deleted;
}
/**
* Remove **node** from the store
* @destructive
*/
mutateRemoveNode(node) {
// Root node can not be removed
if (RootNode.isRootNode(node) === true) {
throw new Error("Root not can not be removed");
}
if (node.logicalId && node.stack) {
this._logicalIdLookup.delete(this.computeLogicalUniversalId(node.stack, node.logicalId));
}
if (StackNode.isStackNode(node)) {
this._stacks.delete(node.uuid);
}
const deleted = this._nodes.delete(node.uuid);
if (deleted) {
this._counters.nodeTypes.subtract(node.nodeType);
if (CfnResourceNode.isCfnResourceNode(node) && node.cfnType) {
this._counters.cfnResources.subtract(node.cfnType);
}
}
return deleted;
}
}
exports.Store = Store;
_a = JSII_RTTI_SYMBOL_1;
Store[_a] = { fqn: "@aws/pdk.cdk_graph.Store", version: "0.26.14" };
/** Base class for all store entities (Node and Edges) */
class BaseEntity {
constructor(props) {
/** @internal */
this._destroyed = false;
this.store = props.store;
this.uuid = props.uuid;
this._attributes = props.attributes || {};
this._metadata = props.metadata || [];
this._tags = new Map(Object.entries(props.tags || {}));
this._flags = new Set(props.flags);
}
/**
* Get *readonly* record of all attributes
* @type Readonly<SerializedGraph.Attributes>
*/
get attributes() {
return cloneDeep(this._attributes);
}
/**
* Get *readonly* list of all metadata entries
* @type Readonly<SerializedGraph.Metadata>
*/
get metadata() {
return cloneDeep(this._metadata);
}
/**
* Get *readonly* record of all tags
* @type Readonly<SerializedGraph.Tags>
*/
get tags() {
return Object.fromEntries(this._tags);
}
/**
* Get *readonly* list of all flags
* @type ReadonlyArray<FlagEnum>
*/
get flags() {
return Array.from(this._flags);
}
/** Indicates if the entity has been destroyed (eg: removed from store) */
get isDestroyed() {
return this._destroyed;
}
/** Indicates if the entity has had destructive mutations applied */
get isMutated() {
return this.hasFlag(types_1.FlagEnum.MUTATED);
}
/** Indicates if entity has a given attribute defined, and optionally with a specific value */
hasAttribute(key, value) {
if (key in this._attributes) {
if (value !== undefined) {
return this._attributes[key] === value;
}
return true;
}
return false;
}
/**
* Add attribute.
*
* @throws Error if attribute for key already exists
*/
addAttribute(key, value) {
if (this.hasAttribute(key)) {
throw new Error(`Entity ${String(this)} already has attribute ${key}; use setAttribute to override`);
}
this.setAttribute(key, value);
}
/** Set attribute. This will overwrite existing attribute. */
setAttribute(key, value) {
// @ts-ignore
this._attributes[key] = value;
}
/** Get attribute by key */
getAttribute(key) {
return this._attributes[key];
}
/** Add metadata entry */
addMetadata(metadataType, data) {
this._metadata.push({
type: metadataType,
data,
});
}
/** Indicates if entity has matching metadata entry */
hasMetadata(metadataType, data) {
return !!this._metadata.find((metadata) => {
if (metadata.type !== metadataType)
return false;
if (metadata.data !== data)
return false;
return true;
});
}
/**
* Retrieves all metadata entries of a given type
* @type Readonly<SerializedGraph.Metadata>
*/
findMetadata(metadataType) {
return this._metadata.filter((entry) => entry.type === metadataType);
}
/**
* Add tag.
* @throws Throws Error is tag for key already exists
*/
addTag(key, value) {
if (this.hasTag(key)) {
throw new Error(`Entity ${String(this)} already has tag ${key}; use setTag to override`);
}
this.setTag(key, value);
}
/** Set tag. Will overwrite existing tag. */
setTag(key, value) {
this._tags.set(key, value);
}
/** Indicates if entity has tag, optionally verifying tag value */
hasTag(key, value) {
if (!this._tags.has(key))
return false;
if (value !== undefined && this._tags.get(key) !== value)
return false;
return true;
}
/** Get tag by key */
getTag(key) {
return this._tags.get(key);
}
/** Add flag */
addFlag(flag) {
this._flags.add(flag);
}
/** Indicates if entity has a given flag */
hasFlag(flag) {
return this._flags.has(flag);
}
/**
* Applies data (attributes, metadata, tags, flag) to entity.
*
* Generally used only for mutations such as collapse and consume to retain data.
* @param data - The data to apply
* @param {boolean} [applyFlags=false] - Indicates if data is overwritten
* @param {boolean} [applyFlags=false] - Indicates if flags should be applied
*/
applyData(data, overwrite = false, applyFlags = false) {
if (data.attributes) {
Object.entries(data.attributes).forEach(([key, value]) => {
if (overwrite || !this.hasAttribute(key)) {
this.setAttribute(key, value);
}
});
}
if (data.metadata) {
data.metadata.forEach((v) => {
if (!this.hasMetadata(v.type, v.data)) {
this.addMetadata(v.type, v.data);
}
});
}
if (data.tags) {
Object.entries(data.tags).forEach(([key, value]) => {
if (overwrite || !this.hasTag(key)) {
this.setTag(key, value);
}
});
}
if (applyFlags && data.flags) {
data.flags.forEach((flag) => {
this.addFlag(flag);
});
}
}
/**
* Performs pre-mutate operations on entity and store
* @internal
*/
_preMutate() {
this.store.verifyDestructiveMutationAllowed();
this.addFlag(types_1.FlagEnum.MUTATED);
}
/**
* Serialize entity
* @internal
*/
_serialize() {
return {
uuid: this.uuid,
attributes: isEmpty(this._attributes) ? undefined : this._attributes,
metadata: isEmpty(this._metadata) ? undefined : this._metadata,
tags: this._tags.size ? Object.fromEntries(this._tags) : undefined,
flags: this._flags.size ? Array.from(this._flags) : undefined,
};
}
}
exports.BaseEntity = BaseEntity;
_b = JSII_RTTI_SYMBOL_1;
BaseEntity[_b] = { fqn: "@aws/pdk.cdk_graph.BaseEntity", version: "0.26.14" };
/** Edge class defines a link (relationship) between nodes, as in standard [graph theory](https://en.wikipedia.org/wiki/Graph_theory) */
class Edge extends BaseEntity {
/** Find first edge matching predicate within an EdgeChain */
static findInChain(chain, predicate) {
for (const entry of chain) {
if (Array.isArray(entry)) {
const edge = Edge.findInChain(entry, predicate);
if (edge)
return edge;
}
else {
if (predicate.filter(entry))
return entry;
}
}
return undefined;
}
/** Find all matching edges based on predicate within an EdgeChain */
static findAllInChain(chain, predicate) {
const edges = [];
for (const entry of chain) {
if (Array.isArray(entry)) {
const edge = Edge.findInChain(entry, predicate);
if (edge) {
edges.push(edge);
}
}
else {
if (predicate.filter(entry)) {
edges.push(entry);
}
}
}
return edges;
}
/** Edge **source** is the node that defines the edge (tail) */
get source() {
return this._source;
}
/** Edge **target** is the node being referenced by the **source** (head) */
get target() {
return this._target;
}
/** Indicates the direction in which the edge is directed */
get direction() {
return this._direction;
}
/** Indicates if **source** and **target** nodes reside in different *root* stacks */
get isCrossStack() {
return this._source.rootStack !== this._target.rootStack;
}
/**
* Indicates if the Edge's **source** and **target** are the same, or were the same
* when it was created (prior to mutations).
*
* To check whether it was originally closed, use `hasFlag(FlagEnum.CLOSED_EDGE)` instead.
*/
get isClosed() {
return this._source === this._target || this.hasFlag(types_1.FlagEnum.CLOSED_EDGE);
}
/**
* Indicates if edge is extraneous which is determined by explicitly having *EXTRANEOUS* flag
* added and/or being a closed loop (source===target).
*/
get isExtraneous() {
return this.hasFlag(types_1.FlagEnum.EXTRANEOUS) || this.isClosed;
}
constructor(props) {
super(props);
this.edgeType = props.edgeType;
this._direction = props.direction;
this._source = props.source;
this._target = props.target;
// Do not change original closed edge flag from a mutation.
if (this._target === this._source && this.hasFlag(types_1.FlagEnum.MUTATED)) {
this.addFlag(types_1.FlagEnum.CLOSED_EDGE);
}
// wire up links
this._source.addLink(this);
this._target.addReverseLink(this);
this.store.addEdge(this);
}
/**
* Indicates if this edge is equivalent to another edge.
*
* Edges are considered equivalent if they share same type, source, and target.
*/
isEquivalent(edge) {
if (edge.edgeType !== this.edgeType)
return false;
if (edge.source !== this.source)
return false;
if (edge.target !== this.target)
return false;
return true;
}
/** Indicates if edge allows destructive mutations */
get allowDestructiveMutations() {
return this.store.allowDestructiveMutations;
}
/**
* Change the edge **direction**
* @destructive
*/
mutateDirection(direction) {
this._preMutate();
this._direction = direction;
}
/**
* Change the edge **source**
* @destructive
*/
mutateSource(node) {
this._preMutate();
this._source.mutateRemoveLink(this);
this._source = node;
this._source.addLink(this);
}
/**
* Change the edge **target**
* @destructive
*/
mutateTarget(node) {
this._preMutate();
this._target.mutateRemoveReverseLink(this);
this._target = node;
this._target.addReverseLink(this);
}
/**
* Destroy the edge. Remove all references and remove from store.
* @destructive
*/
mutateDestroy(_strict = false) {
this._preMutate();
this.source.mutateRemoveLink(this);
this.target.mutateRemoveReverseLink(this);
this.store.mutateRemoveEdge(this);
this._destroyed = true;
}
/**
* Merge an equivalent edge's data into this edge and destroy the other edge.
*
* Used during filtering operations to consolidate equivalent edges.
* @param edge - The edge to consume
* @throws Error is edge is not *equivalent*
* @destructive
*/
mutateConsume(edge) {
this._preMutate();
if (!this.isEquivalent(edge)) {
throw new Error(`Only equivalent edges can be consumed: ${edge} > ${this}`);
}
// propagate edge data
this.applyData(edge);
// destroy the consumed edge
edge.mutateDestroy();
}
/** Get string representation of this edge */
toString() {
return `Edge:${this.edgeType}::${this.uuid}::${this.direction}(${this.source}->${this.target})`;
}
/** @internal */
_serialize() {
return {
...super._serialize(),
edgeType: this.edgeType,
direction: this.direction,
source: this.source.uuid,
target: this.target.uuid,
};
}
}
exports.Edge = Edge;
_c = JSII_RTTI_SYMBOL_1;
Edge[_c] = { fqn: "@aws/pdk.cdk_graph.Edge", version: "0.26.14" };
/** Dependency edge class defines CloudFormation dependency between resources */
class Dependency extends Edge {
/** Indicates if given edge is a {@link Dependency} edge */
static isDependency(edge) {
return edge.edgeType === types_1.EdgeTypeEnum.DEPENDENCY;
}
constructor(props) {
super({
...props,
edgeType: types_1.EdgeTypeEnum.DEPENDENCY,
direction: types_1.EdgeDirectionEnum.FORWARD,
});
this.addFlag(types_1.FlagEnum.EXTRANEOUS);
}
}
exports.Dependency = Dependency;
_d = JSII_RTTI_SYMBOL_1;
Dependency[_d] = { fqn: "@aws/pdk.cdk_graph.Dependency", version: "0.26.14" };
/** Edge prefix to denote dependency edge */
Dependency.PREFIX = "DEP:";
/** Reference edge class defines a directed relationship between nodes */
class Reference extends Edge {
/** Indicates if edge is a {@link Reference} */
static isReference(edge) {
return edge.edgeType === types_1.EdgeTypeEnum.REFERENCE;
}
/** Indicates if edge is a **Ref** based {@link Reference} edge */
static isRef(edge) {
return edge.referenceType === types_1.ReferenceTypeEnum.REF;
}
constructor(props) {
super({
edgeType: types_1.EdgeTypeEnum.REFERENCE,
direction: types_1.EdgeDirectionEnum.FORWARD,
...props,
});
this.setAttribute(Reference.ATT_TYPE, props.referenceType || types_1.ReferenceTypeEnum.REF);
}
/** Get type of reference */
get referenceType() {
return this.getAttribute(Reference.ATT_TYPE);
}
/** Resolve reference chain */
resolveChain() {
if (OutputNode.isOutputNode(this.target)) {
function _resolveChain(_ref) {
if (OutputNode.isOutputNode(_ref.target)) {
return [
_ref,
..._ref.target.referenceLinks.map(_resolveChain),
];
}
return [_ref];
}
return [
this,
...this.target.referenceLinks.map(_resolveChain),
];
}
return [this];
}
/**
* Resolve targets by following potential edge chain.
*
* @see {@link EdgeChain}
*/
resolveTargets() {
if (OutputNode.isOutputNode(this.target)) {
function resolveOutputTarget(_target) {
if (OutputNode.isOutputNode(_target))
return resolveOutputTarget(_target);
return [_target];
}
return this.target.referenceLinks.flatMap((ref) => resolveOutputTarget(ref.target));
}
return [this.target];
}
}
exports.Reference = Reference;
_e = JSII_RTTI_SYMBOL_1;
Reference[_e] = { fqn: "@aws/pdk.cdk_graph.Reference", version: "0.26.14" };
/** Edge prefix to denote **Ref** type reference edge */
Reference.PREFIX = "REF:";
/** Attribute defining the type of reference */
Reference.ATT_TYPE = "graph:reference:type";
/** Attribute type reference edge */
class AttributeReference extends Reference {
/** Indicates if edge in an **Fn::GetAtt** {@link Reference} */
static isAtt(edge) {
return edge.referenceType === types_1.ReferenceTypeEnum.ATTRIBUTE;
}
constructor(props) {
super({
...props,
referenceType: types_1.ReferenceTypeEnum.ATTRIBUTE,
});
this.setAttribute(AttributeReference.ATT_VALUE, props.value);
}
/** Get the resolved attribute value */
get value() {
return this.getAttribute(AttributeReference.ATT_VALUE);
}
}
exports.AttributeReference = AttributeReference;
_f = JSII_RTTI_SYMBOL_1;
AttributeReference[_f] = { fqn: "@aws/pdk.cdk_graph.AttributeReference", version: "0.26.14" };
/** Edge prefix to denote **Fn::GetAtt** type reference edge */
AttributeReference.PREFIX = "ATT:";
/** Attribute key for resolved value of attribute reference */
AttributeReference.ATT_VALUE = "graph:reference:attribute:value";
/** Import reference defines **Fn::ImportValue** type reference edge. */
class ImportReference extends Reference {
/** Indicates if edge is **Fn::ImportValue** based {@link Reference} */
static isImport(edge) {
return edge.referenceType === types_1.ReferenceTypeEnum.IMPORT;
}
constructor(props) {
super({
...props,
referenceType: types_1.ReferenceTypeEnum.IMPORT,
});
}
}
exports.ImportReference = ImportReference;
_g = JSII_RTTI_SYMBOL_1;
ImportReference[_g] = { fqn: "@aws/pdk.cdk_graph.ImportReference", version: "0.26.14" };
/** Edge prefix to denote **Fn::ImportValue** type reference edge */
ImportReference.PREFIX = "IMP:";
/** Node class is the base definition of **node** entities in the graph, as in standard [graph theory](https://en.wikipedia.org/wiki/Graph_theory) */
class Node extends BaseEntity {
/** Stack the node is contained in */
get stack() {
return this._stack;
}
/** Parent node. Only the root node should not have parent. */
get parent() {
return this._parent;
}
constructor(props) {
super(props);
/** @internal */
this._children = new Map();
/** @internal */
this._links = new Map();
/** @internal */
this._reverseLinks = new Map();
this.nodeType = props.nodeType;
this.id = props.id;
this.path = props.path;
this.constructInfo = props.constructInfo;
this._cfnType = props.cfnType;
this._parent = props.parent;
this.depth = this.parent ? this.parent.depth + 1 : 0;
this._stack = props.stack || (this instanceof StackNode ? this : undefined);
this.logicalId = props.logicalId;
if (this.logicalId) {
if (this.stack == null) {
throw new Error(`LogicalId defined outside of stack: ${this.logicalId} - ${String(this)}`);
}
this.store.recordLogicalId(this.stack, this.logicalId, this);
}
if (this.parent) {
this.parent.addChild(this);
}
this.store.addNode(this);
}
/** Gets descending ordered list of ancestors from the root */
get scopes() {
if (this.parent) {
return [...this.parent.scopes, this.parent];
}
return [];
}
/** Indicates if node is direct child of the graph root node */
get isTopLevel() {
return this.parent === this.store.root;
}
/** Get **root** stack */
get rootStack() {
if (StackNode.isStackNode(this))
return this;
return this.scopes.find((scope) => StackNode.isStackNode(scope));
}
/** Get all direct child nodes */
get children() {
return Array.from(this._children.values());
}
/** Indicates if this node is a *leaf* node, which means it does not have children */
get isLeaf() {
return this._children.size === 0;
}
/** Gets all links (edges) in which this node is the **source** */
get links() {
return Array.from(this._links.values());
}
/** Gets all links (edges) in which this node is the **target** */
get reverseLinks() {
return Array.from(this._reverseLinks.values());
}
/** Synthesized construct information defining jii resolution data */
get constructInfoFqn() {
return this.constructInfo?.fqn;
}
/** Indicates if node is a *Custom Resource* */
get isCustomResource() {
return types_1.ConstructInfoFqnEnum.CUSTOM_RESOURCE === this.constructInfoFqn;
}
/**
* Indicates if node ConstructInfoFqn denotes a `aws-cdk-lib.*.Cfn*` construct.
* @see {@link FlagEnum.CFN_FQN}
*/
get isCfnFqn() {
return this.hasFlag(types_1.FlagEnum.CFN_FQN);
}
/** Gets CloudFormation properties for this node */
get cfnProps() {
return this.attributes[types_1.CfnAttributesEnum.PROPS];
}
/** Get the CloudFormation resource type for this node */
get cfnType() {
return this._cfnType;
}
/** Gets list of {@link Dependency} links (edges) where this node is the **source** */
get dependencyLinks() {
return Array.from(this._links.values()).filter((link) => {
return link.edgeType === types_1.EdgeTypeEnum.DEPENDENCY;
});
}
/** Gets list of {@link Dependency} links (edges) where this node is the **target** */
get reverseDependencyLinks() {
return Array.from(this._reverseLinks.values()).filter((link) => {
return link.edgeType === types_1.EdgeTypeEnum.DEPENDENCY;
});
}
/** Gets list of {@link Reference} links (edges) where this node is the **source** */
get referenceLinks() {
return Array.from(this._links.values()).filter((link) => {
return link.edgeType === types_1.EdgeTypeEnum.REFERENCE;
});
}
/** Gets list of {@link Reference} links (edges) where this node is the **target** */
get reverseReferenceLinks() {
return Array.from(this._reverseLinks.values()).filter((link) => {
return link.edgeType === types_1.EdgeTypeEnum.REFERENCE;
});
}
/**
* Get list of **Nodes** that *this node references*
* @see {@link Node.referenceLinks}
*/
get references() {
return uniq(this.referenceLinks.flatMap((link) => link.resolveTargets()));
}
/**
* Get list of **Nodes** that *reference this node*
* @see {@link Node.reverseReferenceLinks}
*/
get referencedBy() {
return uniq(this.reverseReferenceLinks.flatMap((link) => link.source));
}
/**
* Get list of **Nodes** that *this node depends on*
* @see {@link Node.dependencyLinks}
*/
get dependencies() {
return uniq(this.dependencyLinks.flatMap((link) => link.target));
}
/**
* Get list of **Nodes** that *depend on this node*
* @see {@link Node.reverseDependencyLinks}
*/
get dependedOnBy() {
return uniq(this.reverseDependencyLinks.flatMap((link) => link.source));
}
/** Indicates if this node is considered a {@link FlagEnum.GRAPH_CONTAINER} */
get isGraphContainer() {
return this.hasFlag(types_1.FlagEnum.GRAPH_CONTAINER);
}
/** Indicates if this node is considered a {@link FlagEnum.CLUSTER} */
get isCluster() {
return this.hasFlag(types_1.FlagEnum.CLUSTER);
}
/**
* Indicates if this node is considered a {@link FlagEnum.EXTRANEOUS} node
* or determined to be extraneous:
* - Clusters that contain no children
*/
get isExtraneous() {
return this.hasFlag(types_1.FlagEnum.EXTRANEOUS) || (this.isCluster && this.isLeaf);
}
/** Indicates if this node is considered a {@link FlagEnum.ASSET} */
get isAsset() {
return this.hasFlag(types_1.FlagEnum.ASSET);
}
/** Get list of *siblings* of this node. */
get siblings() {
if (this.parent) {
return this.parent.children.filter((child) => child !== this);
}
return [];
}
/** Get specific CloudFormation property */
getCfnProp(key) {
return this.cfnProps && this.cfnProps[key];
}
/** Add *link* to another node */
addLink(edge) {
this._links.set(edge.uuid, edge);
}
/** Add *link* from another node */
addReverseLink(edge) {
this._reverseLinks.set(edge.uuid, edge);
}
/** Add *child* node */
addChild(node) {
this._children.set(node.id, node);
}
/** Indicates if specific *node* is a *child* of *this node* */
isChild(node) {
for (const child of this._children.values()) {
if (child === node)
return true;
}
return false;
}
/** Indicates if a specific *node* is an *ancestor* of *this node* */
isAncestor(ancestor) {
return this.scopes.includes(ancestor);
}
/**
* Find nearest *ancestor* of *this node* matching given predicate.
* @param predicate - Predicate to match ancestor
* @max {number} [max] - Optional maximum levels to ascend
*/
findAncestor(predicate, max) {
let ancestors = this.scopes.slice().reverse();
if (max) {
ancestors = ancestors.slice(0, max);
}
return ancestors.find(predicate.filter);
}
/**
* Gets the nearest **common** *ancestor* shared between *this node* and another *node*.
* @throws Error if *node* does not share a **common** *ancestor*
*/
getNearestAncestor(node) {
if (node === this)
throw new Error("Node is the current node");
const aScopes = this.scopes.reverse();
const bScopes = node.scopes.reverse();
for (const aScope of aScopes) {
for (const bScope of bScopes) {
if (aScope === bScope)
return aScope;
}
}
throw new Error(`Nodes do not share common ancestor: ${String(this)} ^ ${String(node)}`);
}
/**
* Return this construct and all of its sub-nodes in the given order.
*
* Optionally filter nodes based on predicate.
*/
findAll(options) {
const { predicate, order = constructs_1.ConstructOrder.PREORDER } = options || {};
const all = new Array();
function visit(c) {
if (order === constructs_1.ConstructOrder.PREORDER && !RootNode.isRootNode(c)) {
all.push(c);
}
for (const child of c.children) {
visit(child);
}
if (order === constructs_1.ConstructOrder.POSTORDER && !RootNode.isRootNode(c)) {
all.push(c);
}
}
visit(this);
if (predicate) {
return all.filter(predicate.filter);
}
return all;
}
/** Recursively find the nearest sub-node matching predicate */
find(predicate) {
if (predicate.filter(this))
return this;
for (const child of this.children) {
const node = child.find(predicate);
if (node != null)
return node;
}
return undefined;
}
/**
* Get *child* node with given *id*.
*
* @throws Error if no child with given id
*/
getChild(id) {
const child = this._children.get(id);
if (child == null) {
throw new Error(`${String(this)} does not have child with id "${id}"`);
}
return child;
}
/** Find child with given *id*. Similar to `find` but does not throw error if no child found. */
findChild(id) {
return this._children.get(id);
}
/**
* Return all direct links of this node and that of all sub-nodes.
*
* Optionally filter links based on predicate.
*/
findAllLinks(options) {
const { predicate, order = constructs_1.ConstructOrder.PREORDER, reverse, } = options || {};
const all = new Array();
visit(this);
if (predicate) {
return all.filter(predicate.filter);
}
return all;
function visit(c) {
if (order === constructs_1.ConstructOrder.PREORDER) {
all.push(...c[reverse ? "reverseLinks" : "links"]);
}
for (const child of c.children) {
visit(child);
}
if (order === constructs_1.ConstructOrder.POSTORDER) {
all.push(...c[reverse ? "reverseLinks" : "links"]);
}
}
}
/**
* Resolve all link chains
* @see {@link EdgeChain}
*/
getLinkChains(reverse = false) {
let links = this[reverse ? "reverseLinks" : "links"];
return links.map((link) => {
if (Reference.isReference(link)) {
return link.resolveChain();
}
return [link];
});
}
/**
* Find link of this node based on predicate. By default this will follow link
* chains to evaluate the predicate against and return the matching direct link
* of this node.
*
* @param predicate Edge predicate function to match edge
* @param reverse Indicates if links are search in reverse order
* @param follow Indicates if link chain is followed
* @param direct Indicates that only *direct* links should be searched
* @returns
*/
findLink(predicate, reverse = false, follow = true, direct = true) {
if (follow) {
const chains = this.getLinkChains(reverse);
for (const chain of chains) {
const edge = Edge.findInChain(chain, predicate);
if (edge) {
if (direct)
return chain[0];
return edge;
}
}
return undefined;
}
return this[reverse ? "reverseLinks" : "links"].find(predicate.filter);
}
/**
* Find all links of this node based on predicate. By default this will follow link
* chains to evaluate the predicate against and return the matching direct links
* of this node.
*
* @param predicate Edge predicate function to match edge
* @param reverse Indicates if links are search in reverse order
* @param follow Indicates if link chain is followed
* @param direct Indicates that only *direct* links should be searched
* @returns
*/
findLinks(predicate, reverse = false, follow = true, direct = true) {
if (follow) {
return this.getLinkChains(reverse).flatMap((chain) => {
const edges = Edge.findAllInChain(chain, predicate);
if (direct) {
return edges.length ? [chain[0]] : [];
}
return edges;
});
}
return this[reverse ? "reverseLinks" : "links"].filter(predicate.filter);
}
/** Indicates if *this node* references *another node* */
doesReference(node) {
return this.references.includes(node);
}
/** Indicates if *this node* depends on *another node* */
doesDependOn(node) {
return this.dependencies.includes(node);
}
/**
* Indicates if this node allows destructive mutations
* @see {@link Store.allowDestructiveMutations}
*/
get allowDestructiveMutations() {
return this.store.allowDestructiveMutations;
}
/**
* Collapses all sub-nodes of *this node* into *this node*.
* @destructive
*/
mutateCollapse() {
this._preMutate();
this.children.forEach((child) => child.mutateCollapseToParent());
this._mutateReconcileLinks();
}
/**
* Collapses *this node* into *it's parent node*
* @destructive
*/
mutateCollapseToParent() {
this._preMutate();
if (this.parent == null) {
throw new Error(`${this} does not have parent to collapse to.`);
}
return this.mutateCollapseTo(this.parent);
}
/**
* Collapses *this node* into *an ancestor*
* @destructive
*/
mutateCollapseTo(ancestor) {
this._preMutate();
if (!this.isAncestor(ancestor)) {
throw new Error(`${ancestor} is not an ancestor of ${this}`);
}
// TODO: should we retain the child attributes somewhere?
this.children.forEach((child) => {
if (child.isDestroyed)
return;
child.mutateCollapseToParent();
});
this._mutateReconcileLinks();
// redirect all links to parent
// while also deleting links to parent
this.links.forEach((link) => {
if (link.isDestroyed)
return;
if (link.target === ancestor) {
link.mutateDestroy();
}
else {
link.mutateSource(ancestor);
}
});
// redirect all "reverse" links to parent
// while also deleting links from parent
this.reverseLinks.forEach((link) => {
if (link.isDestroyed)
return;
if (link.source === ancestor) {
link.mutateDestroy();
}
else {
link.mutateTarget(ancestor);
}
});
this.mutateDestroy(true);
ancestor._mutateReconcileLinks();
return ancestor;
}
/**
* Destroys this node by removing all references and removing this node from the store.
* @param {boolean} [strict=false] - Indicates that this node must not have references
* @destructive
*/
mutateDestroy(strict = false) {
this._preMutate();
if (strict) {
if (this.children.length) {
throw new Error(`[strict] ${this} can not destroys because it has children`);
}
if (this.links.length || this.reverseLinks.length) {
throw new Error(`[strict] ${this} can not destroys because there are links referencing it`);
}
}
if (strict && (this.links.length || this.reverseLinks.length)) {
throw new Error(`[strict] ${this} can not destroys because there are links referencing it`);
}
this.children.forEach((child) => {
child.mutateDestroy();
});
this.links.forEach((link) => {
link.mutateDestroy();
});
this.reverseLinks.forEach((link) => {
link.mutateDestroy();
});
if (this.parent) {
this.parent.mutateRemoveChild(this);
}
this._parent = undefined;
this._stack = undefined;
this.store.mutateRemoveNode(this);
this._destroyed = true;
}
/**
* Reconciles links defined by this node. During mutations, multiple *equivalent* links may exist and should be
* consolidated into a single link. This operation should be called after collapsing children to remove duplicates.
* @internal
* @destructive
*/
_mutateReconcileLinks() {
this._preMutate();
const links = this.links;
for (const a of links) {
if (a.isDestroyed)
continue;
if (a.isClosed && a.edgeType !== types_1.EdgeTypeEnum.CUSTOM) {
a.mutateDestroy();
continue;
}
for (const b of links) {
if (a === b || b.isDestroyed)
continue;
if (a.isEquivalent(b)) {
a.mutateConsume(b);
}
}
}
const reverseLinks = this.reverseLinks;
for (const a of reverseLinks) {
if (a.isDestroyed)
continue;
if (a.isClosed && a.edgeType !== types_1.EdgeTypeEnum.CUSTOM) {
a.mutateDestroy();
continue;
}
for (const b of reverseLinks) {
if (a === b || b.isDestroyed)
continue;
if (a.isEquivalent(b)) {
a.mutateConsume(b);
}
}
}
}
/**
* Remove a *child* node from *this node*
* @destructive
*/
mutateRemoveChild(node) {
this._preMutate();
if (!this.isChild(node)) {
throw new Error(`${node} is not a child of ${this}`);
}
// NB: children are stored by "id" not "uuid"
return this._children.delete(node.id);
}
/**
* Remove a *link* from *this node*
* @destructive
*/
mutateRemoveLink(link) {
this._preMutate();
return this._links.delete(link.uuid);
}
/**
* Remove a *link* to *this node*
* @destructive
*/
mutateRemoveReverseLink(link) {
this._preMutate();
return this._reverseLinks.delete(link.uuid);
}
/**
* Hoist *this node* to an *ancestor* by removing it from its current parent node and
* in turn moving it to the ancestor.
* @destructive
*/
mutateHoist(newParent) {
this._preMutate();
if (!this.isAncestor(newParent)) {
throw new Error(`${newParent} is not an ancestor of ${this}`);
}
if (this.parent) {
this.parent.mutateRemoveChild(this);
}
this._parent = newParent;
newParent.addChild(this);
if (this.stack &&
this.stack !== this &&
!this.isAncestor(this.stack)) {
this._stack = this.findAncestor({
filter: (node) => StackNode.isStackNode(node) ||
NestedStackNode.isNestedStackNode(node),
});
}
}
/**
* Hoist all children to parent and collapse node to parent.
* @destructive
*/
mutateUncluster() {
this._preMutate();
if (this.parent && !this.isLeaf) {
for (const child of this.children) {
child.mutateHoist(this.parent);
}
this.mutateCollapseToParent();
}
}
/**
* Move this node into a new parent node.
* @param {Node} newParent - The parent to move this node to.
* @destructive
*/
mutateMove(newParent) {
this._preMutate();
if (this.parent) {
this.parent.mutateRemoveChild(this);
this.parent._mutateReconcileLinks();
}
newParent.addChild(this);
this._parent = newParent;
newParent._mutateReconcileLinks();
}
/** Get string representation of this node */
toString() {
return `Node:${this.nodeType}::${this.uuid}`;
}
/**
* Serialize this node
* @internal
*/
_serialize() {
return {
...super._serialize(),
nodeType: this.nodeType,
stack: this.stack?.uuid,
parent: this.parent?.uuid,
id: this.id,
path: this.path,
constructInfo: this.constructInfo,
logicalId: this.logicalId,
cfnType: this.cfnType,
edges: this._links.size
? Array.from(this._links.values()).map(({ uuid }) => uuid)
: undefined,
children: this._children.size
? Object.fromEntries(Array.from(this._children.entries()).map(([key, node]) => [
key,
node._serialize(),
]))
: undefined,
};
}
}
exports.Node = Node;
_h = JSII_RTTI_SYMBOL_1;
Node[_h] = { fqn: "@aws/pdk.cdk_graph.Node", version: "0.26.14" };
/** ResourceNode class defines a L2 cdk resource construct */
class ResourceNode extends Node {
/** Indicates if node is a {@link ResourceNode} */
static isResourceNode(node) {
return node.nodeType === types_1.NodeTypeEnum.RESOURCE;
}
constructor(props) {
super({
nodeType: types_1.NodeTypeEnum.RESOURCE,
...props,
});
if (props.cdkOwned) {
this.addFlag(types_1.FlagEnum.CDK_OWNED);
}
}
/** Get the CloudFormation resource type for this L2 resource or for the L1 resource is wraps. */
get cfnType() {
return (super.cfnType ||
this.getAttribute(ResourceNode.ATT_WRAPPED_CFN_TYPE) ||
this.cfnResource?.cfnType ||
(thi