or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

file-system-actions.mdindex.mdnode-tools.mdoutput-sinks.mdrule-system.mdschematic-engine.mdtemplate-engine.mdtree-operations.mdworkflow.md
tile.json

file-system-actions.mddocs/

File System Actions

Low-level atomic operations for file system modifications that can be applied to trees and tracked for optimization. Actions represent individual file system operations that can be queued, optimized, and executed through sinks.

Capabilities

Action Types

Core interfaces representing different types of file system operations.

/**
 * Base interface for all file system actions
 */
interface Action {
  readonly id: number;
  readonly parent: number;
  readonly path: Path;
  readonly kind: 'c' | 'o' | 'r' | 'd';
}

/**
 * Action to create a new file
 */
interface CreateFileAction extends Action {
  readonly kind: 'c';
  readonly content: Buffer;
}

/**
 * Action to overwrite an existing file
 */
interface OverwriteFileAction extends Action {
  readonly kind: 'o';
  readonly content: Buffer;
}

/**
 * Action to rename a file or directory
 */
interface RenameFileAction extends Action {
  readonly kind: 'r';
  readonly to: Path;
}

/**
 * Action to delete a file or directory
 */
interface DeleteFileAction extends Action {
  readonly kind: 'd';
}

type Action = CreateFileAction | OverwriteFileAction | RenameFileAction | DeleteFileAction;

Action List

Container class for managing and optimizing sequences of file system actions.

/**
 * Container for managing file system actions with optimization capabilities
 */
class ActionList implements Iterable<Action> {
  /** Create a new file action */
  create(path: Path, content: Buffer): void;
  
  /** Overwrite an existing file action */
  overwrite(path: Path, content: Buffer): void;
  
  /** Rename a file or directory action */
  rename(path: Path, to: Path): void;
  
  /** Delete a file or directory action */
  delete(path: Path): void;
  
  /** Optimize the action sequence by removing redundant operations */
  optimize(): void;
  
  /** Add an action to the list */
  push(action: Action): void;
  
  /** Get action at specific index */
  get(index: number): Action;
  
  /** Check if action exists in the list */
  has(action: Action): boolean;
  
  /** Find action matching predicate */
  find(predicate: (value: Action) => boolean): Action | null;
  
  /** Execute function for each action */
  forEach(fn: (value: Action, index: number, array: Action[]) => void, thisArg?: any): void;
  
  /** Number of actions in the list */
  readonly length: number;
  
  // Iterable implementation
  [Symbol.iterator](): Iterator<Action>;
}

Usage Examples:

import { ActionList, Action } from "@angular-devkit/schematics";

// Create and manage actions
function manageActions(): ActionList {
  const actions = new ActionList();
  
  // Add various actions
  actions.create('/new-file.txt', Buffer.from('Hello World'));
  actions.overwrite('/existing-file.txt', Buffer.from('Updated content'));
  actions.rename('/old-name.txt', '/new-name.txt');
  actions.delete('/unwanted-file.txt');
  
  // Optimize to remove redundant operations
  actions.optimize();
  
  return actions;
}

// Iterate through actions
function processActions(actions: ActionList): void {
  for (const action of actions) {
    switch (action.kind) {
      case 'c':
        console.log(`Create: ${action.path} (${action.content.length} bytes)`);
        break;
      case 'o':
        console.log(`Overwrite: ${action.path} (${action.content.length} bytes)`);
        break;
      case 'r':
        console.log(`Rename: ${action.path} -> ${action.to}`);
        break;
      case 'd':
        console.log(`Delete: ${action.path}`);
        break;
    }
  }
}

Action Optimization

The action optimization process removes redundant operations and consolidates related actions.

Optimization Rules:

  1. Create then Delete: Removes both actions
  2. Create then Overwrite: Consolidates to single Create with final content
  3. Overwrite then Overwrite: Consolidates to single Overwrite with final content
  4. Rename then Rename: Consolidates to single Rename from original to final path
  5. Delete then Create: Converts to Overwrite (if same path)
import { ActionList } from "@angular-devkit/schematics";

// Example of optimization effects
function demonstrateOptimization(): void {
  const actions = new ActionList();
  
  // Add redundant operations
  actions.create('/temp.txt', Buffer.from('Initial'));
  actions.overwrite('/temp.txt', Buffer.from('Updated'));
  actions.overwrite('/temp.txt', Buffer.from('Final'));
  
  console.log('Before optimization:', actions.length); // 3
  
  // Optimize - consolidates to single create with final content
  actions.optimize();
  
  console.log('After optimization:', actions.length); // 1
  
  const finalAction = actions.get(0);
  console.log('Final action:', finalAction.kind); // 'c'
  console.log('Final content:', finalAction.content.toString()); // 'Final'
}

Action Creation Utilities

Helper functions for creating specific action types.

/**
 * Create a file creation action
 */
function createAction(path: Path, content: Buffer): CreateFileAction;

/**
 * Create a file overwrite action
 */
function overwriteAction(path: Path, content: Buffer): OverwriteFileAction;

/**
 * Create a file rename action
 */
function renameAction(path: Path, to: Path): RenameFileAction;

/**
 * Create a file delete action
 */
function deleteAction(path: Path): DeleteFileAction;

Action Application

Actions can be applied to trees using the apply method.

/**
 * Apply an action to a tree with optional merge strategy
 */
Tree.prototype.apply = function(action: Action, strategy?: MergeStrategy): void {
  // Implementation applies the action to the tree
};

Usage Examples:

import { Tree, MergeStrategy } from "@angular-devkit/schematics";

function applyActionsToTree(tree: Tree): Tree {
  // Create actions
  const createAction = {
    id: 1,
    parent: 0,
    path: '/new-file.txt',
    kind: 'c' as const,
    content: Buffer.from('New file content')
  };
  
  const deleteAction = {
    id: 2,
    parent: 0,
    path: '/old-file.txt',
    kind: 'd' as const
  };
  
  // Apply actions to tree
  tree.apply(createAction, MergeStrategy.Default);
  tree.apply(deleteAction, MergeStrategy.Default);
  
  return tree;
}

Action Tracking and History

Trees maintain a history of all actions applied to them.

/**
 * Access the action history of a tree
 */
interface Tree {
  readonly actions: Action[];
}

Usage Examples:

import { Tree } from "@angular-devkit/schematics";

function analyzeTreeActions(tree: Tree): void {
  console.log(`Total actions: ${tree.actions.length}`);
  
  const actionCounts = tree.actions.reduce((counts, action) => {
    counts[action.kind] = (counts[action.kind] || 0) + 1;
    return counts;
  }, {} as Record<string, number>);
  
  console.log('Action breakdown:', {
    creates: actionCounts['c'] || 0,
    overwrites: actionCounts['o'] || 0,
    renames: actionCounts['r'] || 0,
    deletes: actionCounts['d'] || 0
  });
}

function findActionsByPath(tree: Tree, searchPath: string): Action[] {
  return tree.actions.filter(action => 
    action.path === searchPath || 
    (action.kind === 'r' && action.to === searchPath)
  );
}

Custom Action Processing

Advanced patterns for working with actions.

import { Action, ActionList } from "@angular-devkit/schematics";

// Custom action processor
class ActionProcessor {
  constructor(private actions: ActionList) {}
  
  // Get all file paths affected by actions
  getAffectedPaths(): Set<string> {
    const paths = new Set<string>();
    
    for (const action of this.actions) {
      paths.add(action.path);
      if (action.kind === 'r') {
        paths.add(action.to);
      }
    }
    
    return paths;
  }
  
  // Get actions that create or modify content
  getContentActions(): Action[] {
    return Array.from(this.actions).filter(
      (action): action is CreateFileAction | OverwriteFileAction =>
        action.kind === 'c' || action.kind === 'o'
    );
  }
  
  // Calculate total content size
  getTotalContentSize(): number {
    return this.getContentActions()
      .reduce((total, action) => total + action.content.length, 0);
  }
  
  // Group actions by directory
  groupByDirectory(): Map<string, Action[]> {
    const groups = new Map<string, Action[]>();
    
    for (const action of this.actions) {
      const dir = action.path.substring(0, action.path.lastIndexOf('/')) || '/';
      if (!groups.has(dir)) {
        groups.set(dir, []);
      }
      groups.get(dir)!.push(action);
    }
    
    return groups;
  }
}

Action Validation

Utilities for validating action sequences.

import { Action, ActionList } from "@angular-devkit/schematics";

class ActionValidator {
  static validate(actions: ActionList): ValidationResult {
    const errors: string[] = [];
    const warnings: string[] = [];
    
    // Check for conflicting operations
    const pathOperations = new Map<string, Action[]>();
    
    for (const action of actions) {
      const path = action.path;
      if (!pathOperations.has(path)) {
        pathOperations.set(path, []);
      }
      pathOperations.get(path)!.push(action);
    }
    
    for (const [path, ops] of pathOperations) {
      // Check for create after delete without intermediate operations
      const hasCreate = ops.some(op => op.kind === 'c');
      const hasDelete = ops.some(op => op.kind === 'd');
      
      if (hasCreate && hasDelete) {
        const createIndex = ops.findIndex(op => op.kind === 'c');
        const deleteIndex = ops.findIndex(op => op.kind === 'd');
        
        if (deleteIndex > createIndex) {
          warnings.push(`Path ${path}: Created then deleted`);
        }
      }
      
      // Check for multiple creates
      const creates = ops.filter(op => op.kind === 'c');
      if (creates.length > 1) {
        errors.push(`Path ${path}: Multiple create operations`);
      }
    }
    
    return {
      isValid: errors.length === 0,
      errors,
      warnings
    };
  }
}

interface ValidationResult {
  isValid: boolean;
  errors: string[];
  warnings: string[];
}

Integration with Trees

Actions are closely integrated with the Tree interface:

import { Tree, Action } from "@angular-devkit/schematics";

// Extension methods for working with tree actions
class TreeActionHelper {
  static getLatestContent(tree: Tree, path: string): Buffer | null {
    // Find the most recent content-affecting action for the path
    const actions = tree.actions
      .filter((action): action is CreateFileAction | OverwriteFileAction => 
        (action.kind === 'c' || action.kind === 'o') && action.path === path
      )
      .sort((a, b) => b.id - a.id); // Most recent first
    
    return actions.length > 0 ? actions[0].content : tree.read(path);
  }
  
  static hasBeenDeleted(tree: Tree, path: string): boolean {
    // Check if there's a delete action for this path
    return tree.actions.some(action => 
      action.kind === 'd' && action.path === path
    );
  }
  
  static getActionHistory(tree: Tree, path: string): Action[] {
    return tree.actions
      .filter(action => 
        action.path === path || 
        (action.kind === 'r' && action.to === path)
      )
      .sort((a, b) => a.id - b.id); // Chronological order
  }
}

Type Definitions

type Path = string & { __PRIVATE_DEVKIT_PATH: void };

type ActionKind = 'c' | 'o' | 'r' | 'd';

interface ActionBase {
  readonly id: number;
  readonly parent: number;
  readonly path: Path;
}

enum MergeStrategy {
  Default = 0,
  Error = 1,
  AllowOverwriteConflict = 2,
  AllowCreationConflict = 4,
  AllowDeleteConflict = 8,
  ContentOnly = 2,
  Overwrite = 14
}