Skip to content

Extending Mycel

When built-in features aren't enough, Mycel can be extended with custom logic via WebAssembly (WASM). Three extension points are available: validators, custom functions, and plugins.

Validators

Custom validators add field-level validation rules to type definitions. Three types are supported: regex, cel, and wasm.

Regex Validator

validator "cuit" {
  type    = "regex"
  pattern = "^[0-9]{2}-[0-9]{8}-[0-9]$"
  message = "Must be a valid CUIT (XX-XXXXXXXX-X)"
}

CEL Validator

validator "company_email" {
  type    = "cel"
  expr    = "value.endsWith('@company.com')"
  message = "Must use a company email address"
}

CEL validators receive value (the field value) and must return a boolean.

WASM Validator

For complex validation logic that can't be expressed in CEL:

validator "luhn_check" {
  type       = "wasm"
  wasm       = "./wasm/validators.wasm"
  entrypoint = "validate_credit_card"
  message    = "Invalid credit card number"
}

Using Validators in Types

type "invoice" {
  number       = string { validate = "luhn_check" }
  supplier_tax = string { validate = "cuit" }
  contact      = string { validate = "company_email" }
}

Custom Functions (WASM)

WASM functions extend the CEL transform engine with custom logic. Write in Rust, Go/TinyGo, C, C++, AssemblyScript, or Zig.

functions "pricing" {
  wasm    = "./wasm/pricing.wasm"
  exports = ["calculate_price", "apply_discount"]
}

Then use in any transform:

transform {
  total    = "calculate_price(input.items)"
  adjusted = "apply_discount(output.total, input.coupon_code)"
}

WASM Interface

Functions must implement the standard Mycel WASM interface:

Memory management (required by host):

alloc(size: i32) -> i32
free(ptr: i32, size: i32)

Function exports:

# Input/output via shared memory: host writes JSON input at ptr, reads JSON result
function_name(ptr: i32, len: i32) -> i32  # returns result pointer

See WASM Documentation for complete interface spec and language-specific examples.

Mocks

Mocks provide test data without connecting to real services. Place JSON files in mocks/ following the naming convention, then enable with CLI flags.

Directory Structure

mocks/
├── db/
│   ├── users.json           # Mock for connector "db", target "users"
│   └── products.json
└── external_api/
    ├── GET_users.json        # Mock for GET /users
    └── POST_orders.json      # Mock for POST /orders

Mock Data Format

[
  {
    "id": "mock-id-1",
    "email": "alice@example.com",
    "name": "Alice"
  },
  {
    "id": "mock-id-2",
    "email": "bob@example.com",
    "name": "Bob"
  }
]

Enabling Mocks

# Mock all connectors
mycel start --config ./my-service

# Mock specific connectors only
mycel start --mock=db --mock=external_api

# Mock all except specific connectors
mycel start --no-mock=stripe

How Mocks Work

When a mock is enabled for a connector, all reads from that connector return mock data. Writes are silently discarded. This lets you test transforms and flow logic without real database or API access.

Plugins

Plugins add new connector types to Mycel via WASM modules. Useful for integrating systems not natively supported.

plugin "salesforce" {
  source  = "github.com/acme/mycel-salesforce"
  version = "^1.0"
}

After declaring the plugin, use its connector like any built-in connector:

connector "sf" {
  type         = "salesforce"
  instance_url = env("SF_INSTANCE_URL")
  token        = env("SF_TOKEN")
}

flow "sync_contacts" {
  from {
    connector = "api"
    operation = "POST /sync"
  }
  to {
    connector = "sf"
    operation = "upsert_contact"
  }
}

Plugin Sources

Format Example
GitHub github.com/org/repo
GitLab gitlab.com/org/repo
Local path ./plugins/my-plugin
Any git URL https://git.internal.com/repo

Version Constraints

Constraint Meaning
"^1.0" Compatible with 1.x (>= 1.0, < 2.0)
"~2.0" Patch-level updates (>= 2.0, < 2.1)
">= 1.0, < 3.0" Explicit range
"latest" Latest release

Plugin Management

mycel plugin install             # Install all plugins from config
mycel plugin list                # Show installed plugins
mycel plugin remove salesforce   # Remove a plugin
mycel plugin update              # Update all plugins

Plugins are cached in mycel_plugins/ (add to .gitignore). Reproducible builds via plugins.lock.

Plugin Manifest

Plugin authors create a plugin.mycel file:

plugin {
  name    = "salesforce"
  version = "1.0.0"
}

provides {
  connector "salesforce" {
    wasm = "connector.wasm"
  }

  validator "sf_id" {
    wasm       = "validators.wasm"
    entrypoint = "validate_sf_id"
    message    = "Invalid Salesforce ID"
  }

  sanitizer "pii_filter" {
    wasm       = "sanitizers.wasm"
    entrypoint = "filter_pii"
    apply_to   = ["flows/api/*"]
    fields     = ["email", "phone"]
  }
}

Aspects

Aspects (cross-cutting concerns applied across flows by pattern matching) are a core concept, not a runtime extension — they're fully declarative, with no WASM or external code involved. Their documentation now lives in Core Concepts → Aspects.

See Also