Documentation
¶
Overview ¶
Package runtime ships the primitive actions every .nflow pipeline starts from: runtime.const, runtime.noop, runtime.debug, runtime.fail, runtime.pick, runtime.wrap, runtime.with, runtime.env, runtime.uuid, runtime.call, runtime.dispatch_by_prefix, json.clean, runtime.sleep. This is a native bundle — always mounted by native.Bundles().
Typical use:
{ user_id: 42 } -> runtime.pick @{ field: "user_id" } -> runtime.wrap @{ key: "data" }
runtime.fail @{ kind: "Timeout", message: "upstream" } || runtime.const @{ value: "fallback" }
Index ¶
Constants ¶
const ID = "runtime"
Variables ¶
var Call = action.New("runtime.call", func(ctx context.Context, in any) (any, error) { m, ok := in.(map[string]any) if !ok { return nil, xerr.BadRequest("call: input must be an object carrying 'name' and optional 'payload'") } name := strings.TrimSpace(readStringArg(m, "name")) if name == "" { return nil, xerr.BadRequest(`call: 'name' is required (use: { name: "log.info", payload: { ... } } -> runtime.call)`) } resolver := contracts.ActionResolverFromContext(ctx) if resolver == nil { return nil, xerr.Internal("call: no action resolver in execution context") } target, ok := resolver.Action(name) if !ok { return nil, xerr.NotFound("call: target action " + name + " not found in registry") } payload, hasPayload := m["payload"] if !hasPayload || payload == nil { filtered := make(map[string]any, len(m)) for k, v := range m { if k != "name" { filtered[k] = v } } payload = filtered } return action.InvokeAny(ctx, target, payload) }).Description("Resolve and invoke another action by name at runtime"). Tag("base", "dynamic"). Build()
var Const = action.New("runtime.const", func(_ context.Context, in any) (any, error) { if s, ok := in.(string); ok { return coerceLiteral(s), nil } m, ok := in.(map[string]any) if !ok { return nil, xerr.BadRequest("const: expected object with 'value' key or raw scalar, got " + typeName(in)) } raw, ok := m["value"] if !ok { raw, ok = m["val"] } if !ok { return nil, xerr.BadRequest("const: missing required field 'value'") } return coerceLiteral(raw), nil }).Description("Return a fixed literal; numbers, bools, and JSON auto-parse"). Tag("base", "literal"). Build()
Const returns a fixed literal. Numbers, bools, JSON objects, JSON arrays, and JSON-quoted strings are auto-coerced from the string representation so the DSL stays readable: runtime.const @{ value: 42 } produces int64(42), not "42".
var Debug = action.New("runtime.debug", func(_ context.Context, in any) (any, error) { label := readStringArg(in, "label") suffix := "" if label != "" { suffix = " " + label } data, err := json.MarshalIndent(in, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "[debug%s] <unprintable %T: %v>\n", suffix, in, err) return in, nil } fmt.Fprintf(os.Stderr, "[debug%s] %s\n", suffix, string(data)) return in, nil }).Description("Print input as JSON to stderr, pass through unchanged"). Tag("base", "debug"). Build()
Debug prints the input as JSON to stderr and returns it unchanged. The optional @{ label: "..." } arg prefixes the printed line.
var DispatchByPrefix = action.New("runtime.dispatch_by_prefix", func(ctx context.Context, in any) (any, error) { m, ok := in.(map[string]any) if !ok { return nil, xerr.BadRequest("dispatch_by_prefix: input must be an object") } prefix := strings.TrimSpace(readStringArg(m, "prefix")) key := strings.TrimSpace(readStringArg(m, "key")) if prefix == "" || key == "" { return nil, xerr.BadRequest("dispatch_by_prefix: prefix and key are required") } name := prefix + "." + key resolver := contracts.ActionResolverFromContext(ctx) if resolver == nil { return nil, xerr.Internal("dispatch_by_prefix: no action resolver in execution context") } target, ok := resolver.Action(name) if !ok { return nil, xerr.NotFound(fmt.Sprintf("dispatch_by_prefix: action %q is not in the registry", name)) } payload := m["payload"] if payload == nil { payload = in } return action.InvokeAny(ctx, target, payload) }).Description("Resolve `prefix.key` against the registry and invoke it"). Tag("base", "dynamic"). Build()
var Env = action.New("runtime.env", func(_ context.Context, in any) (any, error) { name := "" required := false switch v := in.(type) { case string: name = strings.TrimSpace(v) case map[string]any: name = strings.TrimSpace(readStringArg(v, "name")) required = isTruthyArg(v, "required") } if name == "" { return nil, xerr.BadRequest(`env: name is required (use: env @{ name: "HOME" })`) } value, ok := os.LookupEnv(name) if !ok { if required { return nil, xerr.NotFound("env: " + name + " is not set") } return "", nil } return value, nil }).Description("Read an environment variable (required: true fails if unset)"). Tag("base", "runtime"). Build()
Env reads an environment variable. Accepts either a bare string (env "HOME") or an object with name and required fields.
var Fail = action.New("runtime.fail", func(_ context.Context, in any) (any, error) { m, _ := in.(map[string]any) msg := strings.TrimSpace(readStringArg(m, "message")) if msg == "" { msg = "fail: pipeline deliberately aborted by the fail action" } kind := strings.TrimSpace(readStringArg(m, "kind")) return nil, failError(kind, msg) }).Description("Always return an error; kind configurable (default Internal)"). Tag("base", "error"). Build()
Fail always returns an error. The @{ kind: "..." } arg selects the xerr kind; the default is Internal. It is the idiomatic way to simulate a specific failure for fallback pipelines.
var JSONClean = action.New("json.clean", func(_ context.Context, in any) (any, error) { raw := "" var source map[string]any switch v := in.(type) { case string: raw = v case map[string]any: if s, ok := v["content"].(string); ok { raw = s source = v } } if source == nil { return cleanJSONString(raw), nil } out := make(map[string]any, len(source)) maps.Copy(out, source) out["content"] = cleanJSONString(raw) return out, nil }).Description("Strip markdown fences and prose around a JSON body"). Tag("base", "json"). Build()
JSONClean strips the boilerplate that reasoning models habitually wrap around structured output: markdown code fences, "Here is the JSON" preambles, trailing commentary, and single-line fence forms.
Input: string, or map with a `content` field carrying a string. Output: string (the cleaned JSON body). When input is a map, the
cleaned body replaces `content` and the map is passed through otherwise unchanged.
var Noop = action.New("runtime.noop", func(_ context.Context, in any) (any, error) { return in, nil }).Description("Pass input through unchanged"). Tag("base", "identity"). Build()
Noop returns the input unchanged. It is the identity action used in tests, as a placeholder in pipeline composition, and as an explicit terminator for a stream.
var Pick = action.New("runtime.pick", func(_ context.Context, in any) (any, error) { m, ok := in.(map[string]any) if !ok { return nil, xerr.BadRequest("pick: input must be an object") } field := strings.TrimSpace(readStringArg(m, "field")) if field != "" { val, found := lookupDottedPath(m, field) if !found { return nil, xerr.NotFound("pick: field '" + field + "' not found") } return val, nil } if onlyVal, exists := m["only"]; exists { keys := parseKeyList(onlyVal) if len(keys) > 0 { filtered := make(map[string]any, len(keys)) for _, k := range keys { if val, found := lookupDottedPath(m, k); found { filtered[k] = val } } return filtered, nil } } if dropVal, exists := m["drop"]; exists { keys := parseKeyList(dropVal) if len(keys) > 0 { filtered := make(map[string]any, len(m)) for k, v := range m { if k != "drop" && !slices.Contains(keys, k) { filtered[k] = v } } return filtered, nil } } return nil, xerr.BadRequest("pick: must specify 'field', 'only', or 'drop'") }).Description("Extract field or apply allowlist/denylist to input keys"). Tag("base", "shape"). Build()
Pick extracts a single field or filters the map with an allowlist / denylist.
var Sleep = action.New("runtime.sleep", func(ctx context.Context, in any) (any, error) { ms := 1000 if m, ok := in.(map[string]any); ok { if val, exists := m["duration_ms"]; exists { switch v := val.(type) { case float64: ms = int(v) case int: ms = v } } } select { case <-time.After(time.Duration(ms) * time.Millisecond): case <-ctx.Done(): return nil, ctx.Err() } return in, nil }).Description("Pause execution for duration_ms (default 1000ms) and return input"). Tag("base", "simulate"). Build()
Sleep pauses execution for the specified milliseconds. It respects context cancellation, so if a pipeline times out or is canceled, the sleep aborts immediately instead of holding the goroutine.
var UUID = action.New("runtime.uuid", func(_ context.Context, in any) (any, error) { id, err := newUUIDv4() if err != nil { return nil, xerr.Internal("uuid: entropy source failed", err) } if key := strings.TrimSpace(readStringArg(in, "as")); key != "" { base := map[string]any{} if m, ok := in.(map[string]any); ok { for k, v := range m { if k == "as" { continue } base[k] = v } } base[key] = id return base, nil } return id, nil }).Description("Generate a UUID v4; returns a string, or merges under @{ as: ... }"). Tag("base", "runtime"). Build()
UUID generates a UUID v4. Without args it returns a bare string; with @{ as: "field" } it merges the UUID into the input map under that key.
var With = action.New("runtime.with", func(_ context.Context, in any) (any, error) { return in, nil }).Description("Merge @{...} args into the input map"). Tag("base", "shape"). Build()
With merges the @{...} args into the input map and passes the result downstream unchanged. Args are injected by the compiler before the handler runs, so the handler is a plain pass-through:
{ goal: "x", attempt: 1 } -> runtime.with @{ attempt: .attempt + 1 }
// → { goal: "x", attempt: 2 }
Called without @{...} it degenerates to noop. Semantics match C# record `with { ... }`: the input is the base, listed fields win.
var Wrap = action.New("runtime.wrap", func(_ context.Context, in any) (any, error) { m, ok := in.(map[string]any) if !ok { return nil, xerr.BadRequest("wrap: input must be an object carrying the `key` arg") } key := strings.TrimSpace(readStringArg(m, "key")) if key == "" { return nil, xerr.BadRequest(`wrap: key is required (use: wrap @{ key: "data" })`) } inner := make(map[string]any, len(m)) for k, v := range m { if k == "key" { continue } inner[k] = v } return map[string]any{key: inner}, nil }).Description("Wrap the input object under a named key"). Tag("base", "shape"). Build()
Wrap wraps the input object under a named key. The key is required: wrap @{ key: "data" }.
Functions ¶
Types ¶
This section is empty.