Light Dark

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

FieldDescriptionDefault
attempts (or simple number)Maximum retry attempts (1-10)0 (no retries)
delayDelay between retries in milliseconds1000 (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

PropertyTypeDefaultDescription
requiredbooltrueMust be provided at runtime
defaultanynoneValue if not provided (implies required: false)
secretbooltrueIf 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

MetadataPurpose
doc: "..."Documentation
core: trueGlobally 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)