Heddle is a hybrid language designed to solve a core conflict in modern systems: the need to orchestrate complex, imperative business logic using a simple, functional, and auditable flow model.
It is not a general-purpose language. It is a high-level orchestration language that joins two paradigms:
- Imperative Logic: Individual tasks (
stepstatements) wrap imperative code (e.g., Python, Go, Rust) that is compiled to secure, sandboxed WebAssembly modules. - Functional Flow: A declarative pipeline syntax (
|) is used to define the flow of columnar data between these modules.
The primary objective is to create a simple, statically-typed syntax that is trivial for humans to audit, even when the logic is generated by LLMs. This syntax compiles down to a highly efficient, distributed, columnar runtime.
- Declarative Data-Flow: Workflows are defined as data-flow graphs. The
|operator defines a data dependency, not a sequential execution command. The Heddle runtime compiles this into an optimized Directed Acyclic Graph (DAG) for concurrent and distributed execution. - Secure, Sandboxed Logic: All imperative logic is encapsulated in a
step. The implementation (module_reference) points to a WebAssembly module. This provides a secure, high-performance, and portable sandbox, completely decoupling the orchestration layer from the host environment. - Human-Auditable Syntax: The syntax is intentionally simple. This is an engineering control. It ensures that all logic—from simple API calls to complex, multi-stage data transformations—remains clear and verifiable. This makes Heddle an ideal platform for safely reviewing and managing code generated by LLMs.
- First-Class Data Transformation: Data transformation is not a string-based operation. Heddle integrates PRQL (
(...)) as a first-class language construct, allowing for compile-time validation of relational logic. - Columnar, Static Typing: All data flow is statically typed via
schemadefinitions. The runtime is columnar-native, designed for high-performance vectorized (SIMD) operations on these defined data shapes.
Imports an external, sandboxed module containing step implementations and binds it to a local identifier.
import "fhub/http" as http
import "std/database" as db
Defines a static data contract. It specifies the columns and types of data. All step inputs and outputs are validated against a schema at compile-time.
- Primitive Types:
int,string,float,bool,timestamp - Complex Types: Nested type definitions (structs) are supported.
schema User = {
id: int,
username: string,
active: bool
}
schema DetailedUser = {
user_info: User,
last_login: timestamp
}
A step is the atomic unit of work in Heddle. It is a named wrapper around an imperative function (the "module reference"). It defines its input and output data contracts.
step[identifier][input_type]?: Optional. The schema of the data this step expects. If omitted, it takes no input from the pipeline.[output_type]: The schema of the data this step produces.= [module_reference]: The implementation (e.g., http.get, db.query).[dict]: A static configuration block passed to the module.
// A step with no input that produces data
step fetch_users -> User = http.get {
url: "[https://api.example.com/users](https://api.example.com/users)"
}
// A step that takes data, processes it, and returns data
step validate_user User -> User = my_logic.validate {
min_length: 4
}
A handler is a special type of step used for declarative error handling. It is a dedicated pipeline that executes only if the step it's attached to fails.
- It's
input_typemust match theinput_typeof the step it handles. - It's
output_typemust match theoutput_typeof the step it handles (allowing it to provide a default/fallback value) or a generic Error schema.
// A handler that logs the error and returns an empty User dataframe
handler log_and_swallow User -> User = error.log_and_return {
message: "Failed to process user, swallowing error.",
return_value: []
}
A workflow is the primary execution entry point. It defines a graph of step executions using a pipeline-based syntax.
- Global Error Handler: A workflow can define a global error handler using
? [handler_identifier]. - Pipelines (
|): The pipe operator|passes the output of the previous step as the input to the next step. - Error Handling (
?): The?operator attaches a local handler to a step. Ifmy_stepfails,my_handleris executed instead. Its output is then passed down the pipeline. - PRQL Blocks: PRQL code can be placed directly in a pipeline, enclosed in
(...). The data from the previous step is available as the input table.
workflow login_flow ? global_error_handler {
// A simple pipeline
fetch_users
| validate_user ? log_and_swallow // Attach local handler
| (
from input
filter active == true
select username
)
| log.info
}
Within a workflow, let binds the result of a pipeline to a named identifier. This allows for:
- Branching: Using a single data source for multiple, independent pipelines.
- Joining: Referencing the result of a previous pipeline in a PRQL block.
workflow process_users {
// 1. Fetch data once
let all_users = fetch_users
// 2. Branch A: Process active users
all_users
| (from input filter active == true)
| db.write_active
// 3. Branch B: Process inactive users
all_users
| (from input filter active == false)
| db.write_inactive
}
Heddle's configuration blocks and future dataframe literals use standard data primitives.
- Dictionaries:
{ key: "value", number: 123 } - Lists:
[ "a", "b", "c" ] - Pimitives:
string,number,bool,null
This example defines schemas, imports logic, and creates a workflow that fetches users, filters them with PRQL, and logs the result.
// 1. Imports
import "fhub/http" as http
import "std/log" as log
import "std/error" as error
// 2. Schemas
schema User = {
id: int,
username: string,
active: bool
}
schema Username = {
username: string
}
// 3. Steps
step fetch_all_users -> User = http.get {
url: "[https://api.example.com/users](https://api.example.com/users)"
}
step log_usernames Username -> Username = log.info {
message: "Processed users"
}
// 4. Error Handler
handler handle_fetch_error -> User = error.log_and_return {
message: "Failed to fetch users. Returning empty list.",
return_value: [] // Returns an empty User dataframe
}
// 5. Workflow
workflow get_active_users {
fetch_all_users ? handle_fetch_error
| (
from input
filter active == true
select username
)
| log_usernames
}
