Metadata
Metadata in Hot uses the meta keyword to attach information to functions, types, and namespaces. This powers documentation, testing, event handling, scheduling, and more.
Syntax
Metadata comes in two forms:
Map Form
Use meta {...} for key-value metadata:
greet-meta
meta {doc: "Greets a user by name"}
fn (name: Str): Str {
`Hello, ${name}!`
}
Multiple fields:
process-meta
meta {
doc: "Process incoming data",
core: true
}
fn (data: Any): Any {
data
}
Vector Form
Use meta [...] for simple tags:
test-greet-demo
meta ["test"]
fn () {
assert-eq(greet-meta("World"), "Hello, World!")
}
Documentation
The doc field provides documentation for functions and types:
add-demo
meta {doc: "Add two numbers together"}
fn (a: Int, b: Int): Int {
::hot::math/add(a, b)
}
User
meta {doc: "Represents a user in the system"}
type {
name: Str,
email: Str
}
Documentation is displayed in the Hot App dashboard and used by tooling.
Core Functions
The core: true metadata marks functions and types as globally available without namespace qualification:
// In ::hot::math namespace
add
meta {core: true, doc: "Add two numbers"}
fn (a: Int, b: Int): Int {
// ...
}
Now add can be called from any namespace without the ::hot::math/ prefix:
::myapp::calculator ns
// No need for ::hot::math/add
result add(1, 2)
Making Your Functions Core
You can mark your own functions as core too:
::myapp::utils ns
// This function will be available everywhere in your project
format-currency
meta {core: true, doc: "Format a number as currency"}
fn (amount: Dec): Str {
`$${amount}`
}
::myapp::orders ns
// Use without namespace prefix
total format-currency(99.99)
This is useful for utility functions you use throughout your codebase.
Test Functions
Mark functions as tests with meta ["test"]:
test-add-demo
meta ["test"]
fn () {
assert-eq(add-demo(1, 2), 3)
}
test-greet-check
meta ["test"]
fn () {
result greet-meta("World")
assert(starts-with(result, "Hello"))
}
Run tests with hot test.
Event Handlers
The on-event field registers a function as an event handler:
send-welcome-email
meta {on-event: "user:created"}
fn (event) {
::email/send({
to: event.data.email,
subject: "Welcome!",
body: `Welcome, ${event.data.name}!`
})
}
When a user:created event is sent, this handler runs automatically.
Scheduled Functions
The schedule field runs functions on a schedule:
cleanup-old-sessions
meta {schedule: "@daily"}
fn (event) {
::db/delete-expired-sessions()
}
send-heartbeat
meta {schedule: "every 30 seconds"}
fn (event) {
::monitoring/ping()
}
generate-report
meta {schedule: "every 1 hour"}
fn (event) {
::reports/generate-hourly()
}
Schedule formats:
"@daily","@hourly","@weekly""every N seconds","every N minutes","every N hours"
Retry Configuration
Event handlers and scheduled functions can automatically retry on failure using the retry field:
Simple Format
Just specify the number of retry attempts (uses default 1 second delay):
process-payment
meta {on-event: "payment:process", retry: 3}
fn (event) {
// Will retry up to 3 times on failure
charge-card(event.data)
}
Full Format
Specify custom attempts and delay:
sync-external-data
meta {
schedule: "@hourly",
retry: {
attempts: 5,
delay: 10000
}
}
fn (event) {
// Will retry up to 5 times with 10 second delays
fetch-and-sync()
}
Retry Fields
| Field | Description | Default |
|---|---|---|
attempts (or simple number) | Maximum retry attempts (1-10) | 0 (no retries) |
delay | Delay between retries in milliseconds | 1000 (1 second) |
See Scheduler Retries for more details on retry behavior.
Context Requirements
The ctx field declares context variables (secrets and configuration) that a namespace requires:
::myapp::api ns
meta {ctx: {
"openai.api.key": {required: true},
"rate.limit": {required: false, default: 1000, secret: false}
}}
Per-Key Properties
| Property | Type | Default | Description |
|---|---|---|---|
required | bool | true | Must be provided at runtime |
default | any | none | Value if not provided (implies required: false) |
secret | bool | true | If true, value will be masked in call logs |
Examples
Required secret (most common):
meta {ctx: {"anthropic.api.key": {required: true}}}
Optional with default (non-secret):
meta {ctx: {"rate.limit": {default: 60, secret: false}}}
Multiple keys:
meta {ctx: {
"aws.access_key_id": {required: true},
"aws.secret_access_key": {required: true},
"aws.region": {required: false, default: "us-east-1", secret: false}
}}
Secret Masking
By default, all context values are considered secrets (secret: true). When a function calls ::hot::ctx/get to retrieve a secret, the return value is masked as "<secret>" in the call database to prevent accidental exposure.
Mark a value as secret: false if it's safe to log (like configuration values, rate limits, etc.).
Runtime Functions
Use these functions to access context values at runtime:
// Get a context value
api-key ::hot::ctx/get("openai.api.key")
// Set a context value
::hot::ctx/set("my.config", "value")
// Set a secret value (explicitly marks as secret for masking)
::hot::ctx/set-secret("api.token", token-value)
Namespace Metadata
You can also attach metadata to namespaces:
::myapp::test::users meta ["test"] ns
// All functions in this namespace are test-related
Combining Metadata
Combine multiple metadata fields in one map:
process-order
meta {
doc: "Process an incoming order",
on-event: "order:created",
core: true
}
fn (event) {
// ...
}
Summary
| Metadata | Purpose |
|---|---|
doc: "..." | Documentation |
core: true | Globally available without namespace |
meta ["test"] | Mark as test function |
on-event: "name" | Event handler |
schedule: "..." | Scheduled execution |
retry: N or retry: {...} | Automatic retry on failure |
ctx: {...} | Declare required context variables (secrets) |