or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

context-metadata.mdcore-constructs.mddependency-management.mdindex.mdtree-nodes.mdvalidation.md
tile.json

dependency-management.mddocs/

Dependency Management

The dependency management system provides a framework for declaring ordering dependencies between constructs. This enables controlled deployment sequences where certain constructs must be created or deployed before others.

Capabilities

IDependable Interface

Marker interface that identifies objects as capable of being depended upon in the construct system.

/**
 * Trait marker for classes that can be depended upon
 * 
 * The presence of this interface indicates that an object has
 * an `IDependable` implementation.
 * 
 * This interface can be used to take an (ordering) dependency on a set of
 * constructs. An ordering dependency implies that the resources represented by
 * those constructs are deployed before the resources depending ON them are
 * deployed.
 */
interface IDependable {
  // Empty, this interface is a trait marker
}

Dependable Abstract Class

Abstract class that implements the trait system for dependency management, providing the foundation for making objects dependable.

/**
 * Trait for IDependable
 * 
 * Traits are interfaces that are privately implemented by objects. Instead of
 * showing up in the public interface of a class, they need to be queried
 * explicitly. This is used to implement certain framework features that are
 * not intended to be used by Construct consumers, and so should be hidden
 * from accidental use.
 */
abstract class Dependable {
  /**
   * The set of constructs that form the root of this dependable
   * 
   * All resources under all returned constructs are included in the ordering
   * dependency.
   */
  abstract readonly dependencyRoots: IConstruct[];
  
  /**
   * Turn any object into an IDependable.
   * @param instance The object to make dependable
   * @param trait The dependable trait implementation
   */
  static implement(instance: IDependable, trait: Dependable): void;
  
  /**
   * Return the matching Dependable for the given class instance.
   * @param instance The dependable instance
   * @returns The dependable trait
   * @throws Error if the instance does not implement IDependable
   */
  static of(instance: IDependable): Dependable;
  
  /**
   * Return the matching Dependable for the given class instance.
   * @param instance The dependable instance  
   * @returns The dependable trait
   * @deprecated use `of`
   */
  static get(instance: IDependable): Dependable;
}

Usage Examples:

import { Construct, RootConstruct, Dependable, IDependable } from "constructs";

const app = new RootConstruct("App");
const database = new Construct(app, "Database");
const api = new Construct(app, "API");

// All constructs automatically implement IDependable
console.log(database instanceof Object); // true - but checking IDependable requires trait access

// Access the dependable trait
const dbDependable = Dependable.of(database);
console.log(dbDependable.dependencyRoots); // [database]

// Create custom dependable object
class CustomResource implements IDependable {
  constructor(private constructs: Construct[]) {
    Dependable.implement(this, {
      get dependencyRoots() {
        return constructs;
      }
    });
  }
}

const resource = new CustomResource([database, api]);
const resourceDependable = Dependable.of(resource);
console.log(resourceDependable.dependencyRoots); // [database, api]

DependencyGroup Class

Class for grouping multiple disjoint constructs into a single dependable unit, useful when you need to depend on multiple unrelated constructs.

/**
 * A set of constructs to be used as a dependable
 * 
 * This class can be used when a set of constructs which are disjoint in the
 * construct tree needs to be combined to be used as a single dependable.
 */
class DependencyGroup implements IDependable {
  /**
   * Create a new dependency group
   * @param deps Initial set of dependable objects
   */
  constructor(...deps: IDependable[]);
  
  /**
   * Add a construct to the dependency roots
   * @param scopes Additional dependable objects to include
   */
  add(...scopes: IDependable[]): void;
}

Usage Examples:

import { Construct, RootConstruct, DependencyGroup } from "constructs";

const app = new RootConstruct("App");
const database = new Construct(app, "Database");
const cache = new Construct(app, "Cache");  
const queue = new Construct(app, "Queue");
const api = new Construct(app, "API");

// Create dependency group for multiple infrastructure components
const infrastructure = new DependencyGroup(database, cache);

// Add more dependencies to the group
infrastructure.add(queue);

// API depends on all infrastructure components
api.node.addDependency(infrastructure);

// Verify the dependency structure
const apiDeps = api.node.dependencies;
console.log(apiDeps.includes(database)); // true
console.log(apiDeps.includes(cache)); // true  
console.log(apiDeps.includes(queue)); // true

// Create groups with constructor
const monitoring = new DependencyGroup(
  new Construct(app, "Metrics"),
  new Construct(app, "Logging"),
  new Construct(app, "Alerting")
);

const frontend = new Construct(app, "Frontend");
frontend.node.addDependency(api, monitoring);

Adding Dependencies to Constructs

The Node class provides methods for declaring dependencies between constructs.

/**
 * Add an ordering dependency on another construct.
 * An `IDependable` can be a single construct or a group of constructs.
 * @param deps One or more dependable objects
 */
addDependency(...deps: IDependable[]): void;

/**
 * Return all dependencies registered on this node (non-recursive).
 * @returns Array of constructs that this construct depends on
 */
readonly dependencies: IConstruct[];

Usage Examples:

import { Construct, RootConstruct, DependencyGroup } from "constructs";

const app = new RootConstruct("App");
const vpc = new Construct(app, "VPC");
const database = new Construct(app, "Database");
const cache = new Construct(app, "Cache");
const api = new Construct(app, "API");
const frontend = new Construct(app, "Frontend");

// Simple dependency: API depends on database
api.node.addDependency(database);

// Multiple dependencies: Frontend depends on both API and cache
frontend.node.addDependency(api, cache);

// Dependency group: Create infrastructure group
const infrastructure = new DependencyGroup(vpc, database, cache);
const webTier = new Construct(app, "WebTier");
webTier.node.addDependency(infrastructure);

// Check dependencies
console.log(api.node.dependencies); // [database]
console.log(frontend.node.dependencies); // [api, cache]
console.log(webTier.node.dependencies); // [vpc, database, cache]

// Chain dependencies for deployment order
const deployment = new Construct(app, "Deployment");
deployment.node.addDependency(frontend); // This also implicitly depends on everything frontend depends on

Dependency Root Resolution

Understanding how dependency roots are resolved from different dependable objects.

Usage Examples:

import { Construct, RootConstruct, DependencyGroup, Dependable } from "constructs";

const app = new RootConstruct("App");
const parent = new Construct(app, "Parent");
const child1 = new Construct(parent, "Child1");
const child2 = new Construct(parent, "Child2");

const otherConstruct = new Construct(app, "Other");

// Single construct dependency roots
const parentDeps = Dependable.of(parent);
console.log(parentDeps.dependencyRoots); // [parent]

// Group dependency roots  
const group = new DependencyGroup(parent, otherConstruct);
const groupDeps = Dependable.of(group);
console.log(groupDeps.dependencyRoots); // [parent, otherConstruct]

// When you add a dependency, all roots are included
const consumer = new Construct(app, "Consumer");
consumer.node.addDependency(group);

// The consumer now depends on both parent and otherConstruct
const consumerDeps = consumer.node.dependencies;
console.log(consumerDeps.includes(parent)); // true
console.log(consumerDeps.includes(otherConstruct)); // true
console.log(consumerDeps.includes(child1)); // false - child1 is not a root

Error Handling

Common error scenarios and handling in the dependency system.

Usage Examples:

import { Construct, RootConstruct, Dependable } from "constructs";

const app = new RootConstruct("App");

// Error: Object doesn't implement IDependable
const plainObject = {};
try {
  Dependable.of(plainObject as any);
} catch (error) {
  console.log(error.message); // "[object Object] does not implement IDependable. Use \"Dependable.implement()\" to implement"
}

// Error: Circular dependencies are not prevented at the API level
// but should be avoided in practice
const construct1 = new Construct(app, "Construct1");
const construct2 = new Construct(app, "Construct2");

construct1.node.addDependency(construct2);
construct2.node.addDependency(construct1); // This creates a circular dependency

// The API allows this, but deployment tools may detect and reject circular dependencies
console.log(construct1.node.dependencies); // [construct2]
console.log(construct2.node.dependencies); // [construct1]