UNPKG

@aws/pdk

Version:

All documentation is located at: https://aws.github.io/aws-pdk

1,444 lines 254 kB
"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