Step Function (:function)
Quick Answer
Use this reference for the :function step schema, deterministic transform rules, and allowed helper/interop surface.
Use for sandboxed transforms.
Canonical Shape
Core fields:
| Field | Type | Required | Notes |
|---|---|---|---|
:type | keyword | Yes | Use :function (:code alias is deprecated) |
:input | map | No | Input payload map; omitted means {} |
:code | form/string | Yes* | Inline function body |
:ref | keyword | Yes* | Reference top-level :functions entry |
:load | vector | No | Input keys to load from blob refs before execution |
:persist | map | No | Persist function output as retained :blob, :kv, or :table |
* Provide exactly one of :code or :ref.
Limits And Behavior
- Prefer
:reffor reuse and readability. - Keep reusable logic in top-level
:functions; keep step-level:flowfocused on orchestration. - Function steps support common output persistence with
:persist; top-level:functions
entries are reusable code definitions and do not persist by themselves. - Function persists use the retained/default path today. For temporary large HTTP
responses, persist the producing:httpstep as ephemeral, pass the ref, and
add:loadonly where hydrated content is needed. - Function code always receives one input map argument. If
:inputis omitted,
that argument starts as{}and still includes runtime context keys such as
:step/ctxand:flow/ctx. - Keep functions deterministic; avoid time, randomness, and I/O.
- If
:flowvalidation says it cannot resolveLong/parseLongor similar parser/helper calls,
move that logic into a:functionstep or top-level:functionsentry. Regular flow-body SCI
does not expose Java interop or the helper namespace documented here. - Safe helpers are exposed under
breyta.sandbox(preferred; pure/deterministic):base64-encode(string|bytes) -> string(Base64)base64-decode(string|bytes) -> string(UTF-8)base64-decode-bytes(string|bytes) -> byteshex-encode(string|bytes) -> stringhex-decode(string) -> string(UTF-8)hex-decode-bytes(string) -> bytessha256-hex(string|bytes) -> string(hex digest)hmac-sha256-hex(key string|bytes, value string|bytes) -> string(hex digest)uuid-from(string) -> uuiduuid-from-bytes(string|bytes) -> uuidparse-instant(string) -> java.time.Instant(ISO-8601)format-instant(Instant) -> string(ISO-8601)format-instant-pattern(Instant, pattern) -> string(UTC)instant->epoch-ms(Instant) -> longepoch-ms->instant(long) -> Instantduration-between(Instant, Instant) -> Durationtruncate-instant(Instant, unit) -> Instant(unit::seconds|:minutes|:hours|:days)instant-plus(Instant, amount, unit) -> Instant(unit::millis|:seconds|:minutes|:hours|:days)instant-minus(Instant, amount, unit) -> Instanturl-encode(string) -> string(UTF-8)url-decode(string) -> string(UTF-8)
- Safe JSON helpers are exposed under
json:json/parse(string|bytes) -> datawith safe identifier-like JSON object keys keywordized
by default; it takes exactly one argument, so do not pass parser option flagsjson/write-str(data) -> string- keys outside the conservative safety policy remain strings to avoid unbounded keyword interning
- Limited Java interop is also allowed in
:functioncode (small allowlist):java.time.*,
java.time.format.DateTimeFormatter,java.time.temporal.{ChronoUnit,TemporalAdjusters},
java.util.{UUID,Base64},java.math.{BigInteger,BigDecimal}. Preferbreyta.sandbox. - Use the documented
json/*helpers for JSON parsing and writing; lower-level parser
namespaces are not part of the supported sandbox surface.
Canonical Example
;; In the flow definition:
;; :functions [{:id :normalize
;; :language :clojure
;; :code "(fn [input]\n;; {:value (str (:value input))})"}]
(flow/step :function :normalize
{:ref :normalize
:input {:value 42}})
No-input helper:
(flow/step :function :build-static-report-id
{:code '(fn [_] "daily-ops-report")})
JSON parsing example:
(flow/step :function :parse-payload
{:code "(fn [input]
(let [payload (json/parse (:body input))]
{:event (:event payload)
:id (get-in payload [:data :id])}))"
:input {:body body-text}})
Persist transformed rows as a table resource:
(flow/step :function :persist-enriched-rows
{:code '(fn [input] (:rows input))
:input {:rows enriched-rows}
:persist {:type :table
:table "enriched_todos"
:write-mode :append}})