Sub-operations

Operations can call other operations as steps, enabling composability and code reuse.

Basic Usage

Use the sub: key to call another operation:

dyngle:
  operations:
    greet:
      - echo "Hello!"
    
    greet-twice:
      steps:
        - sub: greet
        - sub: greet

Passing Arguments

Sub-operations can accept arguments using the args: key:

dyngle:
  operations:
    greet-person:
      expressions:
        person: "args[0]"
      steps:
        - echo "Hello, {{person}}!"
    
    greet-team:
      steps:
        - sub: greet-person
          args: ['Alice']
        - sub: greet-person
          args: ['Bob']

Scoping Rules

Sub-operations follow clear scoping rules that separate declared values from live data:

Declared Values (Locally Scoped)

Values and expressions declared via values: or expressions: keys are local to each operation:

  • A parent operation's declared values are NOT visible to child sub-operations
  • A child sub-operation's declared values do NOT leak to the parent operation
  • Each operation only sees its own declared values plus global declared values

Live Data (Globally Shared)

Data assigned via the => operator persists across all operations:

  • Live data populated by a sub-operation IS available to the parent after the sub-operation completes
  • This allows operations to communicate results through shared mutable state

Example

dyngle:
  values:
    declared-val: global
  
  operations:
    child:
      values:
        declared-val: child-local
      steps:
        - echo {{declared-val}}  # Outputs "child-local"
        - echo "result" => live-data
    
    parent:
      steps:
        - echo {{declared-val}}  # Outputs "global"
        - sub: child
        - echo {{declared-val}}  # Still outputs "global"
        - echo {{live-data}}     # Outputs "result" (persisted from child)

Return Values from Sub-operations

When a sub-operation specifies a return: key, the parent operation can capture the return value:

dyngle:
  operations:
    get-temperature:
      return: temp
      steps:
        - curl -s "https://api.example.com/weather" => data
        - data -> jq -r '.temperature' => temp
    
    weather-report:
      steps:
        - sub: get-temperature
          => temperature
        - echo "Current temperature: {{temperature}} degrees"

Composition Patterns

Build Pipeline

dyngle:
  operations:
    install-deps:
      - npm install
    
    compile:
      - npm run build
    
    test:
      - npm test
    
    build:
      description: Full build pipeline
      steps:
        - sub: install-deps
        - sub: compile
        - sub: test

Reusable Components

dyngle:
  operations:
    setup-env:
      access: private
      steps:
        - echo "Setting up environment..."
        - export NODE_ENV=production
    
    deploy-frontend:
      steps:
        - sub: setup-env
        - npm run deploy:frontend
    
    deploy-backend:
      steps:
        - sub: setup-env
        - npm run deploy:backend

Data Processing Chain

dyngle:
  operations:
    fetch-data:
      return: raw-data
      steps:
        - curl -s "https://api.example.com/data" => raw-data
    
    transform-data:
      expressions:
        input: "args[0]"
      return: transformed
      steps:
        - input -> jq '.items' => transformed
    
    process-all:
      return: result
      steps:
        - sub: fetch-data
          => data
        - sub: transform-data
          args: [data]
          => result

Best Practices

Use Private Operations for Helpers

Mark helper operations as private to prevent direct execution:

dyngle:
  operations:
    deploy:
      steps:
        - sub: validate
        - sub: build
        - sub: upload
    
    validate:
      access: private
      steps:
        - echo "Validating configuration..."
    
    build:
      access: private
      steps:
        - npm run build
    
    upload:
      access: private
      steps:
        - aws s3 sync ./dist s3://my-bucket/

Use Private Operations for Secrets

Private operations are particularly useful for operations that generate or fetch secrets:

dyngle:
  operations:
    get-api-token:
      access: private
      return: token
      steps:
        - aws secretsmanager get-secret-value --secret-id api-token => secret
        - secret -> jq -r '.SecretString' => token
    
    call-api:
      description: Make authenticated API call
      steps:
        - sub: get-api-token
          => token
        - curl -H "Authorization: Bearer {{token}}" https://api.example.com/data

This prevents accidental exposure of the token operation via dyngle run or the MCP server.

Share Data via Return Values

Instead of relying on implicit live data sharing, prefer explicit return values:

Good:

dyngle:
  operations:
    get-version:
      return: version
      steps:
        - cat package.json => pkg
        - pkg -> jq -r '.version' => version
    
    tag-release:
      steps:
        - sub: get-version
          => ver
        - git tag "v{{ver}}"

Less clear:

dyngle:
  operations:
    get-version:
      steps:
        - cat package.json => pkg
        - pkg -> jq -r '.version' => version
    
    tag-release:
      steps:
        - sub: get-version
        - git tag "v{{version}}"  # Implicit dependency

Next Steps