or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-lerna--run-topologically

Executes operations on packages in topological order with configurable concurrency and dependency graph support

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@lerna/run-topologically@6.4.x

To install, run

npx @tessl/cli install tessl/npm-lerna--run-topologically@6.4.0

index.mddocs/

@lerna/run-topologically

@lerna/run-topologically is an internal Lerna utility that executes operations on packages in maximally-saturated topological order. It orchestrates package processing by building a dependency graph and running tasks in parallel while respecting dependency relationships, using a queue-based approach with configurable concurrency.

Package Information

  • Package Name: @lerna/run-topologically
  • Package Type: npm
  • Language: JavaScript/TypeScript (CommonJS)
  • Installation: This is an internal Lerna tool, install via npm install lerna

Core Imports

const { runTopologically } = require("@lerna/run-topologically");

ES modules import (if supported by your environment):

import { runTopologically } from "@lerna/run-topologically";

Basic Usage

const { runTopologically } = require("@lerna/run-topologically");

// Execute a build operation on packages in dependency order
const packages = [/* array of Package instances */];

const buildResults = await runTopologically(
  packages,
  async (pkg) => {
    console.log(`Building ${pkg.name}...`);
    // Perform build operation
    return { name: pkg.name, success: true };
  },
  { concurrency: 4 }
);

console.log("All builds completed:", buildResults);

Architecture

The package is built around these key components:

  • Topological Execution: Processes packages in dependency order using graph traversal with maximally-saturated parallel execution
  • Concurrency Control: Uses p-queue to limit parallel execution while respecting dependencies (default: unlimited concurrency)
  • Dependency Graph: Leverages @lerna/query-graph for building and traversing package dependency relationships, with support for both allDependencies and dependencies graph types
  • Cycle Handling: Supports collapsing and processing dependency cycles when rejectCycles is false
  • Promise Aggregation: Collects and returns all results from runner executions in completion order
  • Recursive Queuing: Automatically queues next available packages as dependencies are satisfied

Capabilities

Topological Package Processing

Executes operations on packages in dependency order with controlled concurrency.

/**
 * Run callback in maximally-saturated topological order.
 * @template T
 * @param {import("@lerna/package").Package[]} packages - List of Package instances from @lerna/package
 * @param {(pkg: import("@lerna/package").Package) => Promise<T>} runner - Callback function (pkg: Package) => Promise<T>
 * @param {TopologicalConfig} [options] - Configuration options with destructuring defaults
 * @returns {Promise<T[]>} Array of values returned by runner callbacks
 */
function runTopologically(packages, runner, { concurrency, graphType, rejectCycles } = {});

Parameters:

  • packages: Array of Package instances from @lerna/package
  • runner: Async callback function that processes each package and returns a value
  • options: Optional configuration object

Options Configuration:

/**
 * Configuration object for topological execution
 * @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
 * @property {number} [concurrency] - Maximum number of concurrent operations (default: Infinity)
 * @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Type of dependencies to consider
 * @property {boolean} [rejectCycles] - Whether to reject dependency cycles
 */

Usage Examples:

const { runTopologically } = require("@lerna/run-topologically");

// Basic usage with default options (unlimited concurrency)
const results = await runTopologically(
  packages,
  async (pkg) => {
    // Process package
    return pkg.name;
  }
);

// With concurrency control
const buildResults = await runTopologically(
  packages,
  async (pkg) => {
    console.log(`Building ${pkg.name}...`);
    // Simulate build process
    await new Promise(resolve => setTimeout(resolve, 1000));
    return { package: pkg.name, buildTime: Date.now() };
  },
  { concurrency: 2 }
);

// With specific dependency graph type and cycle rejection
const testResults = await runTopologically(
  packages,
  async (pkg) => {
    // Run tests only considering production dependencies
    return runTests(pkg);
  },
  { 
    graphType: 'dependencies', // Exclude devDependencies from graph
    concurrency: 4,
    rejectCycles: true // Reject if cycles detected
  }
);

// Example with error handling
try {
  const publishResults = await runTopologically(
    packages,
    async (pkg) => {
      if (pkg.private) {
        return { name: pkg.name, skipped: true };
      }
      // Perform publish operation
      return { name: pkg.name, published: true };
    },
    { concurrency: 1, rejectCycles: true }
  );
} catch (error) {
  console.error("Topological execution failed:", error.message);
}

Types

/**
 * Configuration object extending QueryGraphConfig with concurrency control
 * @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
 * @property {number} [concurrency] - Maximum number of concurrent operations
 * @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Dependencies to consider
 * @property {boolean} [rejectCycles] - Whether to reject dependency cycles
 */

/**
 * Package instance from @lerna/package
 * @typedef {Object} Package
 * @property {string} name - Package name
 * @property {string} version - Package version
 * @property {string} location - Package location on filesystem
 * @property {string} manifestLocation - Path to package.json file
 * @property {string} nodeModulesLocation - Path to node_modules directory
 * @property {string} binLocation - Path to .bin directory
 * @property {string} contents - Path to package contents (may differ from location if publishConfig.directory is set)
 * @property {boolean} private - Whether package is marked as private
 * @property {Object} resolved - npm-package-arg resolved metadata
 * @property {string} rootPath - Root path of the monorepo
 * @property {Object} scripts - Package scripts from package.json
 * @property {Object} bin - Package bin entries
 * @property {Object} [dependencies] - Production dependencies
 * @property {Object} [devDependencies] - Development dependencies
 * @property {Object} [optionalDependencies] - Optional dependencies
 * @property {Object} [peerDependencies] - Peer dependencies
 * @property {function} get - Get arbitrary property from package.json
 * @property {function} set - Set arbitrary property in package.json
 * @property {function} toJSON - Get shallow copy of package.json
 * @property {function} refresh - Refresh package state from disk
 * @property {function} serialize - Write package.json changes to disk
 */

Implementation Details

Execution Flow:

  1. Creates a PQueue instance with specified concurrency limit
  2. Builds a QueryGraph from packages using the specified graph type
  3. Recursively processes available packages (those with satisfied dependencies):
    • Marks packages as "taken" to prevent duplicate processing
    • Queues runner execution for each available package
    • Marks packages as "done" upon completion
    • Automatically queues newly available packages
  4. Returns collected results when the queue becomes idle

Performance Characteristics:

  • Maximally-saturated execution: Runs as many packages in parallel as possible within dependency constraints
  • Memory efficient: Uses iterative queuing rather than pre-computing entire execution plan
  • Cycle aware: Handles dependency cycles gracefully when rejectCycles is false

Error Handling

The function will reject if:

  • The runner callback throws an error or returns a rejected promise
  • Dependency cycles are detected when rejectCycles: true is set
  • Invalid package dependency relationships are encountered
try {
  const results = await runTopologically(packages, runner, options);
} catch (error) {
  console.error("Topological execution failed:", error.message);
}

Common Error Scenarios:

  • Runner function failures propagate immediately, stopping execution
  • Cycle detection with rejectCycles: true prevents execution start
  • Invalid package objects or missing dependencies cause initialization errors