Skip to content

Type-Driven Features

Floe’s compiler knows the full structure of your types at compile time. This powers features that would normally require runtime libraries in TypeScript — validation, test data generation, and more. Everything is generated as plain code with zero runtime dependencies.

In TypeScript, types are erased at compile time. Validating incoming JSON requires Zod or io-ts. Test fixtures require faker.js or hand-written factories. Every time you change a type, you update the schema and the factory too.

Floe’s compiler already has the type information. It generates validators and test data directly — always in sync because they come from the same source.

parse<T> validates unknown data against a Floe type at runtime. The compiler generates the checking code inline — no schema library needed.

// Validate JSON from an API
const user = json |> parse<User>?
// Validate with inline types
const point = data |> parse<{ x: number, y: number }>?
// Validate arrays
const items = raw |> parse<Array<Product>>?

parse<T> always returns Result<T, Error>. Use ? to unwrap or match to handle errors:

match data |> parse<User> {
Ok(user) -> Console.log(user.name),
Err(e) -> Console.error(e.message),
}

For parse<User>(json) where type User { name: string, age: number }, the compiler emits type checks inline:

(() => {
const __v = json;
if (typeof __v !== "object" || __v === null)
return { ok: false, error: new Error("expected object, got " + typeof __v) };
if (typeof (__v as any).name !== "string")
return { ok: false, error: new Error("field 'name': expected string, got " + ...) };
if (typeof (__v as any).age !== "number")
return { ok: false, error: new Error("field 'age': expected number, got " + ...) };
return { ok: true, value: __v as { name: string; age: number } };
})()

No runtime dependency. No schema definition to maintain. Change the type, the validation updates automatically.

TypeValidation
string, number, booleantypeof check
Record typesObject check + recursive field validation
Array<T>Array.isArray + element validation loop
Option<T>Allow undefined or validate inner type
Named typesObject structure check
// API response validation
async fn fetchUsers() -> Promise<Result<Array<User>, Error>> {
const response = await Http.get("/api/users")?
const data = await Http.json(response)?
data |> parse<Array<User>>
}
// Form input validation
fn validateForm(data: unknown) -> Result<ContactForm, Error> {
data |> parse<ContactForm>
}

mock<T> generates test data from a type definition. The compiler emits object literals directly — no faker.js, no test factories, no runtime cost.

type User {
id: string,
name: string,
age: number,
}
const testUser = mock<User>
// { id: "mock-id-1", name: "mock-name-2", age: 3 }

Override specific fields when you need control over certain values:

const admin = mock<User>(name: "Alice", age: 30)
// { id: "mock-id-1", name: "Alice", age: 30 }

Non-overridden fields are still auto-generated. This is useful when your test cares about specific values but not others.

TypeGenerated Value
string"mock-fieldname-N" (uses the field name for context)
numberSequential integers (1, 2, 3, …)
booleanAlternates true/false
Array<T>Array with 1 mock element
Record typesAll fields mocked recursively
UnionsFirst variant
Option<T>The inner value (not undefined)
String literal unionsFirst variant
NewtypesMock the inner type

mock<T> pairs naturally with Floe’s inline test blocks:

type Todo {
id: string,
text: string,
done: boolean,
}
fn toggleDone(todo: Todo) -> Todo {
Todo(..todo, done: !todo.done)
}
test "toggle flips done status" {
const todo = mock<Todo>(done: false)
const toggled = toggleDone(todo)
assert toggled.done == true
}
test "toggle preserves other fields" {
const todo = mock<Todo>
const toggled = toggleDone(todo)
assert toggled.id == todo.id
assert toggled.text == todo.text
}

mock<T> handles nested and complex types recursively:

type Order {
id: string,
items: Array<OrderItem>,
status: OrderStatus,
}
type OrderItem {
productId: string,
quantity: number,
}
type OrderStatus {
| Pending
| Shipped { trackingId: string }
| Delivered
}
const testOrder = mock<Order>
// {
// id: "mock-id-1",
// items: [{ productId: "mock-productId-2", quantity: 3 }],
// status: { tag: "Pending" }
// }

TaskTypeScriptFloe
Validate API dataZod, io-ts, or hand-written checksparse<T>
Generate test datafaker.js, factories, or hand-written objectsmock<T>
Keep in syncManual — update schema when type changesAutomatic — same source
Runtime costSchema library bundled in productionZero — compiled away

Floe supports inline test blocks that live alongside the code they test. Tests are type-checked with the rest of your code but stripped from production output.

Use the test keyword followed by a name and a block of assert statements:

fn add(a: number, b: number) -> number { a + b }
test "addition" {
assert add(1, 2) == 3
assert add(-1, 1) == 0
assert add(0, 0) == 0
}

assert takes any expression that evaluates to boolean. The compiler enforces this at compile time.

Tests live in the same file as the code they test:

type Validation {
| Valid { string }
| Empty
| TooShort
| TooLong
}
fn validate(input: string) -> Validation {
const len = input |> String.length
match len {
0 -> Empty,
1 -> TooShort,
_ -> match len > 100 {
true -> TooLong,
false -> Valid(input),
},
}
}
test "validation" {
assert validate("") == Empty
assert validate("a") == TooShort
assert validate("hello") == Valid("hello")
}
Terminal window
floe test src/ # all tests in a directory
floe test src/math.fl # tests in a specific file
CommandTest blocks
floe testCompiled and executed
floe checkType-checked, not executed
floe buildStripped from output
  • test is a contextual keyword — it only starts a test block when followed by a string literal
  • assert is only valid inside test blocks
  • Test blocks cannot be exported
  • Multiple test blocks per file are allowed