Skip to main content

Operation Registry

The Operation Registry is the core of Conductor’s plugin system. It’s a global singleton that stores all operations (built-in and custom) and makes them available across all contexts.

Key Concepts

Universal Operations

Once an operation is registered, it works everywhere:
  • ✅ Ensembles
  • ✅ Pages
  • ✅ Forms
  • ✅ APIs
  • ✅ Webhooks
No context-specific code needed!

Singleton Pattern

The registry is a singleton - there’s only one instance per Worker:
import { getOperationRegistry } from '@ensemble-edge/conductor'

const registry = getOperationRegistry()
// Always returns the same instance

Built-in Operations

Conductor includes these built-in operations:

fetch-data

Fetch data from various sources:
operations:
  - operation: fetch-data
    config:
      source: payload    # payload, d1, kv, r2, api
      collection: users
      query:
        active: true

transform

Declarative data transformations - return literal values, apply modifiers (pick/omit/defaults), or merge data:
agents:
  - name: mock-data
    operation: transform
    config:
      value:
        - { id: 1, name: "Alice" }
        - { id: 2, name: "Bob" }

  - name: clean-user
    operation: transform
    config:
      input: ${fetch-user.output}
      omit: [password, secret]
      defaults:
        status: "active"

  - name: combine
    operation: transform
    config:
      merge:
        - ${agent1.output}
        - ${agent2.output}
The transform operation is a first-class agent operation. Expression interpolation (${...}) is resolved by the runtime before the agent executes. See the Transform Agent documentation for full details.

custom-code

Execute custom JavaScript:
operations:
  - operation: custom-code
    config:
      code: "return { result: input.value * 2 }"
      input: ${previous.output}

Registering Operations

Basic Registration

import { getOperationRegistry, type OperationHandler } from '@ensemble-edge/conductor'

const registry = getOperationRegistry()

const handler: OperationHandler = {
  async execute(operation, context) {
    const { config } = operation
    // Perform operation logic
    return { result: 'success' }
  }
}

registry.register('custom:op', handler)

With Metadata

registry.register('custom:op', handler, {
  name: 'custom:op',
  description: 'Custom operation description',
  version: '1.0.0',
  author: '@conductor/custom',
  contexts: ['page', 'form'],  // or ['all']
  tags: ['custom', 'data'],
  inputs: {
    foo: 'string',
    bar: 'number'
  },
  outputs: {
    result: 'any'
  }
})

Operation Context

Operations receive a context object:
interface OperationContext {
  request?: Request                  // HTTP request (if available)
  env: ConductorEnv                 // Environment bindings
  ctx: ExecutionContext             // Cloudflare Workers context
  params?: Record<string, string>   // URL/route parameters
  query?: Record<string, string>    // Query string parameters
  headers?: Record<string, string>  // Request headers
  data?: Record<string, any>        // Input data
  contextType: 'ensemble' | 'page' | 'form' | 'api' | 'webhook'
}
Example:
const handler: OperationHandler = {
  async execute(operation, context) {
    // Access environment bindings
    const db = context.env.DB

    // Access request data
    const userId = context.params?.userId

    // Check context type
    if (context.contextType === 'page') {
      // Page-specific logic
    }

    return { success: true }
  }
}

Executing Operations

Direct Execution

const result = await registry.execute({
  operation: 'custom:op',
  config: {
    foo: 'bar',
    baz: 123
  }
}, context)

With Custom Handler

Override the registered handler for a specific invocation:
const result = await registry.execute({
  operation: 'any:op',
  config: {},
  handler: async (context) => {
    return { custom: true }
  }
}, context)

Discovery API

List All Operations

const all = registry.list()
// ['fetch-data', 'transform', 'custom-code', 'plasmic:render', ...]

List by Context

const pageOps = registry.listByContext('page')
// Only operations that work in page context

List by Tag

const uiOps = registry.listByTag('ui')
// Only operations tagged with 'ui'

Check if Operation Exists

if (registry.has('plasmic:render')) {
  // Operation is registered
}

Get Metadata

const meta = registry.getMetadata('plasmic:render')
console.log(meta.description)  // "Render Plasmic component"
console.log(meta.version)      // "1.0.0"
console.log(meta.contexts)     // ['page', 'form']
console.log(meta.tags)         // ['ui', 'visual', 'render']

Context-Aware Operations

Operations can check their execution context:
const handler: OperationHandler = {
  async execute(operation, context) {
    switch (context.contextType) {
      case 'page':
        return await handlePageContext(operation, context)
      case 'api':
        return await handleApiContext(operation, context)
      case 'ensemble':
        return await handleEnsembleContext(operation, context)
      default:
        throw new Error(`Unsupported context: ${context.contextType}`)
    }
  }
}

registry.register('context:aware', handler, {
  name: 'context:aware',
  description: 'Context-aware operation',
  contexts: ['page', 'api', 'ensemble']  // Specify supported contexts
})

Metadata Schema

Complete metadata interface:
interface OperationMetadata {
  name: string                    // Required: operation identifier
  description: string             // Required: human-readable description
  version?: string                // Semver version
  author?: string                 // Plugin/package name
  contexts?: Array<               // Supported contexts
    'ensemble' | 'page' | 'form' | 'api' | 'webhook' | 'all'
  >
  inputs?: Record<string, any>    // Input schema (for docs/validation)
  outputs?: Record<string, any>   // Output schema (for docs/validation)
  tags?: string[]                 // Tags for categorization
}

Testing Operations

import { describe, it, expect, beforeEach } from 'vitest'
import { getOperationRegistry } from '@ensemble-edge/conductor'

describe('Custom Operation', () => {
  let registry

  beforeEach(() => {
    registry = getOperationRegistry()
    registry.reset() // Reset to clean state
  })

  it('should register and execute', async () => {
    const handler = {
      async execute(operation, context) {
        return { result: 'test' }
      }
    }

    registry.register('test:op', handler)

    const result = await registry.execute({
      operation: 'test:op',
      config: {}
    }, mockContext)

    expect(result.result).toBe('test')
  })
})

Best Practices

Naming Convention: Use namespace:operation format
// Good
'plasmic:render'
'unkey:validate'
'stripe:charge'

// Avoid
'render'
'validate'
'charge'
Metadata is Documentation: Include comprehensive metadata
registry.register('op', handler, {
  name: 'op',
  description: 'Clear, concise description',
  version: '1.0.0',
  author: '@conductor/plugin',
  contexts: ['page'],  // Be explicit
  tags: ['ui', 'render'],  // Aid discovery
})
Error Handling: Throw meaningful errors
const handler: OperationHandler = {
  async execute(operation, context) {
    if (!operation.config.required) {
      throw new Error('[custom:op] Missing required config: required')
    }
    // ... operation logic
  }
}
Type Safety: Use TypeScript
interface CustomOpConfig {
  required: string
  optional?: number
}

const handler: OperationHandler = {
  async execute(operation, context) {
    const config = operation.config as CustomOpConfig
    // Typed access to config
  }
}

Management API

Unregister Operation

registry.unregister('custom:op')

Clear All Operations

registry.clear()
// Warning: Removes built-in operations too!

Reset to Initial State

registry.reset()
// Clears all and re-registers built-ins

Next Steps