Documentation
¶
Overview ¶
Package dataflow is a deterministic node-graph execution engine: HTTP calls, filters, data transforms, database queries, and similar steps that don't need an LLM turn to run. It complements pkg/workflow (a multi-agent DAG where every node is a Prompt) rather than replacing it — a dataflow graph can invoke a pkg/workflow.Definition as a single "subworkflow" node (see builtin.go) when a step genuinely needs judgment, without either engine reimplementing the other's execution model.
Index ¶
- func AsString(v any) string
- func BoolParam(params map[string]any, name string, fallback bool) bool
- func IntParam(params map[string]any, name string, fallback int) int
- func RegisterBuiltins(registry *Registry)
- func ResolveParam(ctx context.Context, rt *Runtime, params map[string]any, key string, item Item, ...) (any, error)
- func ResolveValue(ctx context.Context, rt *Runtime, raw any, item Item, itemIndex int) (any, error)
- func StringListParam(params map[string]any, name string) []string
- func StringParam(params map[string]any, name, fallback string) string
- func Validate(def Definition) error
- type AgentCaller
- type Base
- type Definition
- type DisplayCondition
- type ExpressionEvaluator
- type Item
- type ItemSource
- type Node
- type NodeDescription
- type NodeExecutor
- type NodeProperty
- type NodePropertyOption
- type NodePropertyType
- type NodeResult
- type Options
- type Output
- type Registry
- type Result
- type Runtime
- type SecretResolver
- type SubworkflowRunner
- type TestableExecutor
- type ToolSpec
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AsString ¶
AsString coerces a resolved parameter value (a literal, or an expression's evaluation result) to a string: a string passes through unchanged, nil becomes "", anything else (a number or bool an expression evaluated to) is formatted with fmt.Sprint - the same loose coercion JS itself would apply putting a non-string into a string context.
func BoolParam ¶
BoolParam reads the boolean parameter name from params, or returns fallback when it is absent or not a boolean.
func IntParam ¶
IntParam reads the integer parameter name from params, accepting an int or a float64 (which is what JSON decoding produces), or returns fallback otherwise.
func RegisterBuiltins ¶
func RegisterBuiltins(registry *Registry)
RegisterBuiltins registers the "agent", "query", "tools" and "subworkflow" node types on registry — the node kinds that bridge into the LLM side (a single scoped agent turn, or a full pkg/workflow multi-agent chain) rather than running deterministic code. Callers still need to register whatever deterministic node types (http_request, filter, ...) their graph uses.
agent/query/tools together replace what used to be one catch-all "agent" node (automation-app-pages.md §37.9): agent is pure identity (which local persona), query is the task that actually runs against a connected agent's identity, and tools is a reusable, individually-toggleable group of capability nodes a query may call. This is a breaking change to the old single-node shape - its "prompt"/"tools" parameters moved to query.
func ResolveParam ¶
func ResolveParam(ctx context.Context, rt *Runtime, params map[string]any, key string, item Item, itemIndex int) (any, error)
ResolveParam is ResolveValue(ctx, rt, params[key], item, itemIndex) - the common case of resolving one top-level parameter.
func ResolveValue ¶
ResolveValue is dataflow's equivalent of n8n's getNodeParameter: a node's own Execute calls this (as many times as it wants, for whichever items it wants - Execute's own signature never changes) instead of reading a raw parameter directly, to get expression support for free. raw is returned unresolved when it isn't a string, doesn't start with "=" (mirrors n8n's own isExpression() convention, and this SDK's pre-existing "set" node's own hand-rolled version of the same idea), or when rt/rt.Expr is nil (expressions not wired up by the caller - not an error, just "off").
Bindings available inside the expression: $json (item), $itemIndex, $now/$today (native JS Date), $node(name) - a callable returning {json, all} for a node that has already executed (see Runtime.NodeOutput) - and $vars (the caller's organization-scoped named values, Tier 3.2 - see Runtime.Vars/VarsMap). Deliberately no $prevNode: that needs per-item upstream source (NodeResult.InputSource, Tier 1.3), which the engine only knows after a node returns, not during its call - exposing it would mean changing Execute's signature, which this design avoids. $node('Name') covers the actual need.
func StringListParam ¶
StringListParam reads a comma-separated PropString parameter as a trimmed, non-empty string slice - the same plain-string convention NodeProperty's own doc comment describes for a value only known at runtime per-tenant (an agent's available tool names), where a real multi-select has nowhere to source its options from statically. Returns nil (not an empty, non-nil slice) when the parameter is absent or blank, so callers can use its zero value to mean "no override" - see agentNode's use of this for its optional "tools" parameter.
func StringParam ¶
StringParam reads the string parameter name from params, or returns fallback when it is absent or not a string.
func Validate ¶
func Validate(def Definition) error
Validate checks graph structure only (unique node IDs, connections that target real nodes, no cycles, Tools/Agent references that are structurally sound) — it does not need a Registry, mirroring pkg/workflow.Validate. Per-node parameter validation, and the one Tools check that needs a node type's real metadata (a target can't be a Trigger or Logic node - see validateToolTargetTypes), happens in Run, once each node's executor is resolved.
Types ¶
type AgentCaller ¶
type AgentCaller interface {
Ask(ctx context.Context, agentSlug, prompt string, tools []string, graphTools []ToolSpec) (string, error)
}
AgentCaller runs a single prompt through a scoped agent turn — what the built-in "agent" node type (see builtin.go) delegates to. The concrete implementation (e.g. wrapping sdk.Client.Ask) lives with the caller, not here, so dataflow never imports pkg/sdk directly.
agentSlug and tools are both optional per-node overrides of the job's own default agent/tool set - empty/nil means "behave exactly as before this parameter existed" (reuse the job's shared session, full conversation continuity across nodes). A non-default value asks the implementation for an isolated, freshly-scoped turn instead (e.g. sdk.Client.Ask's own create-register-submit-close session), since a node explicitly asking for a different agent or tool set is asking to delegate to something other than "continue the same conversation." tools is a list of tool *names* already available to resolve, not new tool definitions - see StringListParam, which is how agentNode reads its own "tools" property.
graphTools is this node's own Tools resolved into real, invocable ToolSpecs by BuildAgentTools (see tool_adapter.go) - nil/empty for every agent node that sets no Tools, which is every graph saved before this parameter existed. An implementation with no way to register a dynamic tool for this one turn should fail loudly on a non-empty graphTools rather than silently running the turn without them - the same posture already established for tools above.
type Base ¶
type Base struct {
// contains filtered or unexported fields
}
Base provides the boilerplate most NodeExecutor implementations share — embed it and override Execute (and ValidateParameters when a node has required parameters).
func NewBase ¶
func NewBase(description NodeDescription) Base
NewBase creates the Base of a node executor from its description, so that the executor only has to implement Execute (and ValidateParameters when a node has required parameters).
func (Base) Description ¶
func (b Base) Description() NodeDescription
Description returns the description the Base was created with.
type Definition ¶
type Definition struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Nodes []Node `json:"nodes" yaml:"nodes"`
// PinnedData freezes a node's output to a fixed set of items instead of
// actually running it - Run checks this (keyed by node ID) before
// invoking a node's own Execute, mirroring n8n's IPinData. Kept
// separate from each Node's own Parameters, since "frozen test output"
// is a different concept from real node configuration - a pinned
// node's ValidateParameters/Execute (and even its registry lookup) are
// never called at all, so a graph can be tested with a node whose real
// config is incomplete, or whose type isn't implemented yet.
PinnedData map[string][]Item `json:"pinned_data,omitempty" yaml:"pinned_data,omitempty"`
}
Definition is a node graph: which node types run, their parameters, and how each node's output routes to downstream nodes.
type DisplayCondition ¶
DisplayCondition gates a property's visibility on another property's current value — the simplest useful subset of n8n's displayOptions.show (no hide, no operators beyond equality-in-a-set), sufficient for every current node's conditional fields (e.g. a database node's operation picker gating which of query/args apply).
type ExpressionEvaluator ¶
type ExpressionEvaluator interface {
Eval(ctx context.Context, source string, bindings map[string]any) (any, error)
}
ExpressionEvaluator is satisfied by *pkg/dataflow/expr.Pool as-is (no changes needed there) - an interface here rather than a hard dependency, the same "compose through an interface" split Runtime already uses for Secrets/Agent/Subworkflow, so core dataflow never needs to import the expr package.
type Item ¶
Item is one record flowing through the graph — the dataflow analog of a single JSON object. A node's input/output is always a slice of Item, never a single bare value, so merge/fan-out behave uniformly across node types.
type ItemSource ¶
ItemSource identifies where one received input item came from - which upstream node produced it and on which output port. A zero value (Node == "") means the item came from this run's own seed input (Run's `input` parameter), not from another node.
type Node ¶
type Node struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Connections map[string][]string `json:"connections,omitempty" yaml:"connections,omitempty"`
// RetryOnFail/MaxTries/WaitBetweenTriesMS/OnError (Tier 3.1) are real
// production behavior - not test/dev overrides like PinnedData - so
// they're siblings of Parameters/Connections here rather than a
// separate top-level map, mirroring n8n's own INode shape exactly.
// RetryOnFail off (the default, and every graph saved before this field
// existed) means Execute is called exactly once, today's existing
// behavior. MaxTries is meaningful only when RetryOnFail is true, and
// clamped 2-5 by the engine; WaitBetweenTriesMS is clamped 0-5000.
RetryOnFail bool `json:"retry_on_fail,omitempty" yaml:"retry_on_fail,omitempty"`
MaxTries int `json:"max_tries,omitempty" yaml:"max_tries,omitempty"`
WaitBetweenTriesMS int `json:"wait_between_tries_ms,omitempty" yaml:"wait_between_tries_ms,omitempty"`
// OnError decides what happens once every retry attempt (or the single
// attempt, if RetryOnFail is off) has failed: "" (unset, the default -
// today's existing behavior: NodeResult.Success=false, downstream
// dependents skipped), "continueRegularOutput" (proceed with empty
// output as if nothing happened), or "continueErrorOutput" (route a
// single synthesized {"error": message} item onto a new "error" port,
// letting a downstream branch handle the failure explicitly). Retry and
// OnError are independent: a node can continue-on-error without ever
// retrying, or retry a few times and still stop the run.
OnError string `json:"on_error,omitempty" yaml:"on_error,omitempty"`
// Tools lists the IDs of other nodes this node (which must be type
// "query" or "tools" - enforced by Validate) may invoke as LLM-callable
// tools during its turn, in addition to (never instead of) whatever it's
// wired to via Connections. A node named here that has no real
// Connections of its own stays dormant - the engine never schedules it
// eagerly (see topologicalLevels) - and only runs when the query's LLM
// actually calls it (see ResolveTools). A node that's both wired into
// Connections *and* named in some query's Tools keeps running in its
// normal sequential position too - dual-use, purely additive
// (automation-app-pages.md §37.9). A target of type "tools" is expanded
// recursively rather than called directly - see ResolveTools - letting
// several "query" nodes share the same reusable, individually-toggleable
// tool group instead of each repeating the same list of IDs.
Tools []string `json:"tools,omitempty" yaml:"tools,omitempty"`
// Agent is the ID of the "agent"-type node providing this "query" node's
// persona - required on every "query" node (enforced by Validate), never
// set on any other type. A plain ID reference rather than a Connections
// edge, same reasoning as Tools above: it's a structural relationship
// ("which identity do I run as"), not data flow, so the referenced agent
// node is looked up directly rather than needing to run/be scheduled at
// all.
Agent string `json:"agent,omitempty" yaml:"agent,omitempty"`
}
Node is one step in the graph. Type selects the NodeExecutor (registered by name in a Registry, e.g. "http_request", "if", "agent"). Connections maps an output port name to the IDs of nodes that receive items emitted on that port — most node types only ever emit on "main"; conditional nodes (if/switch) emit on named ports like "true"/"false" instead.
type NodeDescription ¶
type NodeDescription struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Properties []NodeProperty `json:"properties,omitempty"`
// IsTrigger marks a node type that can act as a graph's entry point (has
// no meaningful input - Run already seeds every node with no
// predecessors, trigger or not) and carries its own activation config
// (e.g. a schedule) rather than transforming upstream data. An authoring
// UI uses this to render trigger nodes distinctly (no input handle,
// grouped separately) without hardcoding a type-name list.
IsTrigger bool `json:"isTrigger,omitempty"`
// IsTestable marks a node type whose executor implements
// TestableExecutor — an authoring UI uses this to show a "Test
// connection" affordance without hardcoding a type-name list. Set by
// Registry.Types() via a type assertion, never by a node's own
// Description() implementation.
IsTestable bool `json:"isTestable,omitempty"`
}
NodeDescription is metadata about a node type, for catalogs/authoring UIs (an agent building a Definition can be handed a list of these).
type NodeExecutor ¶
type NodeExecutor interface {
Execute(ctx context.Context, rt *Runtime, input []Item, params map[string]any) (Output, error)
Description() NodeDescription
ValidateParameters(params map[string]any) error
}
NodeExecutor is what every node type implements — a thin adapter between the graph engine and one deterministic operation (an HTTP call, a filter, a database query, ...). Implementations should not hold per-run state; Runtime carries whatever per-run dependencies (secrets, callers) a node needs.
type NodeProperty ¶
type NodeProperty struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Type NodePropertyType `json:"type"`
Required bool `json:"required,omitempty"`
Default any `json:"default,omitempty"`
Placeholder string `json:"placeholder,omitempty"`
Description string `json:"description,omitempty"`
Options []NodePropertyOption `json:"options,omitempty"`
DisplayIf *DisplayCondition `json:"displayIf,omitempty"`
}
NodeProperty describes one parameter field for an authoring UI's form renderer. Options a node can only resolve at runtime (a tenant's connector accounts, an agent's configured personas) have no place to live here — Description() is a stateless method — so such a field stays a plain string property; the UI can't render a dynamic dropdown for it.
type NodePropertyOption ¶
NodePropertyOption is one choice offered for a node property that is picked from a list: the label shown and the value stored.
type NodePropertyType ¶
type NodePropertyType string
NodePropertyType is the set of parameter field widgets an authoring UI needs to render a node's Properties as a real form instead of raw JSON. Deliberately a small, flat subset — not a port of n8n's ~20-type INodeProperties system, which exists to serve thousands of third-party nodes. A node whose parameters don't fit this shape (a nested sub-workflow definition, a free-form key/value map with user-defined keys) should leave Properties empty rather than force a bad fit; the authoring UI falls back to raw JSON for those, which is correct, not a shortfall.
const ( PropString NodePropertyType = "string" PropText NodePropertyType = "text" // multiline string PropNumber NodePropertyType = "number" PropBoolean NodePropertyType = "boolean" PropOptions NodePropertyType = "options" // single-select dropdown, static Options list PropJSON NodePropertyType = "json" // free-form object/array, still authored as JSON text PropSecretRef NodePropertyType = "secretRef" // a named secret's value is resolved by the caller, never authored inline - see dataflow.SecretResolver )
type NodeResult ¶
type NodeResult struct {
ID string `json:"id"`
Type string `json:"type"`
Success bool `json:"success"`
Skipped bool `json:"skipped,omitempty"`
// Pinned is true when this result came from Definition.PinnedData
// rather than a real Execute call - lets a trace/UI consumer tell
// "this node actually ran" apart from "this used frozen test data".
Pinned bool `json:"pinned,omitempty"`
// Continued is true when this node failed (Success=false) but its own
// OnError setting (Tier 3.1) let the run proceed anyway rather than
// stopping - kept distinct from Success so a trace/UI consumer can
// still tell "this genuinely failed" apart from "this failed but the
// run kept going", the same distinction n8n's own execution view makes.
// Run's own downstream-skip/routing logic treats Continued the same as
// a real success (see engine.go).
Continued bool `json:"continued,omitempty"`
// Attempts is how many times Execute was actually called for this node
// - 1 unless RetryOnFail was set and at least one attempt failed.
Attempts int `json:"attempts,omitempty"`
// Input/InputSource are exactly what this node received and, index for
// index, where each item came from - populated by Run's own scheduling
// loop (see engine.go), never by the node's own Execute, so every node
// type gets this for free with no interface change.
Input []Item `json:"input,omitempty"`
InputSource []ItemSource `json:"input_source,omitempty"`
// Output is the same single-port view every existing consumer already
// reads (unchanged) - OutputByPort is the real per-port breakdown Output
// collapses away (see executeNode).
Output []Item `json:"output,omitempty"`
OutputByPort map[string][]Item `json:"output_by_port,omitempty"`
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
Duration time.Duration `json:"duration"`
}
NodeResult is the recorded outcome of one node's execution within a Run.
type Options ¶
type Options struct {
MaxParallel int
}
Options tunes Run's execution. MaxParallel caps how many nodes within one level run concurrently; 0 defaults to 4 (mirrors pkg/workflow.Options).
type Output ¶
Output is what a node execution produces, split by output port. Use Main(items) for the common single-port case.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps node type names to their executors. A Run needs a Registry covering every Type referenced by the Definition being executed.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry creates an empty registry of node executors, safe for concurrent use.
func (*Registry) Get ¶
func (r *Registry) Get(nodeType string) (NodeExecutor, error)
Get returns the executor registered for nodeType, or an error when there is none.
func (*Registry) Register ¶
func (r *Registry) Register(nodeType string, executor NodeExecutor)
Register adds executor for the node type nodeType, replacing the one that was registered for it.
func (*Registry) Types ¶
func (r *Registry) Types() []NodeDescription
Types lists every registered node type's description, for authoring surfaces (e.g. a tool the Inbox Agent uses to see what's available).
type Result ¶
type Result struct {
Name string `json:"name"`
Success bool `json:"success"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
Duration time.Duration `json:"duration"`
Results map[string]NodeResult `json:"results"`
// Order is execution order, in the same level-batched order Run used —
// mirrors pkg/workflow.Result.Order.
Order []string `json:"order"`
}
Result is the outcome of a full graph Run.
func Run ¶
func Run(ctx context.Context, def Definition, registry *Registry, rt *Runtime, input []Item, opts Options) (Result, error)
Run executes def against registry, seeding every node with no predecessors with input, and returns once every reachable node has run (or been skipped because a predecessor failed).
type Runtime ¶
type Runtime struct {
Secrets SecretResolver
Agent AgentCaller
Subworkflow SubworkflowRunner
// Expr enables expression resolution (ResolveParam/ResolveValue) for any
// node that opts in - nil (the default for a caller that doesn't set it,
// and for every existing Runtime{} literal/nil Runtime already in use)
// means expressions are off, not an error; nodes calling ResolveParam
// against a nil-Expr Runtime just get their literal values back
// unresolved.
Expr ExpressionEvaluator
// Vars is the caller's own organization-scoped named values (Tier 3.2's
// "Variables"), exposed to expressions as $vars.NAME - see VarsMap.
// Unlike Secrets, this is a plain map rather than a lazy resolver
// interface: Variables aren't secret, so the caller resolves the whole
// set once up front (e.g. before a run starts) instead of one name at a
// time per access. nil (the default) means no variables are configured,
// not an error - $vars.ANYTHING then just resolves to undefined.
Vars map[string]string
// contains filtered or unexported fields
}
Runtime carries the per-run dependencies node executors need, injected by the caller (seshat-backend/seshat-server for multi-tenant secret resolution, internal/automation for the agent/subworkflow callers) rather than owned by this package — dataflow itself never stores a credential or holds an sdk.Client. This is the same "compose through an interface, don't couple bounded contexts" split already used for iam/automation in seshat-ai's seshat-server.
func (*Runtime) NodeAccessor ¶
NodeAccessor returns the Go closure bound as the JS-callable $node(name) function - exported so a node type building its own bindings map (e.g. filter/if/switch, which evaluate a whole-field expression rather than going through ResolveValue) can offer $node too without duplicating this logic. A nil rt still returns a working (always-empty) accessor so ResolveValue's own nil-rt short-circuit is the only nil check callers need.
func (*Runtime) NodeOutput ¶
NodeOutput returns the named node's Output ("main"-collapsed, same shape as NodeResult.Output) once Run has recorded it - only for a node that has actually finished executing, mirroring n8n's own $('NodeName') execution- order requirement. ok is false before that (including for a name that will never exist). Safe to call from concurrently-running node executions - Run itself is the only writer, under its own lock.
func (*Runtime) VarsMap ¶
VarsMap returns rt.Vars, or an empty (never nil) map for a nil Runtime or a Runtime with no Vars set - so a binding map can always assign rt.VarsMap() to "$vars" without a separate nil check, mirroring NodeAccessor's own nil-safety. Exported so a node type building its own bindings map (e.g. filter/if/switch, see nodes/logic.go) can offer $vars too without duplicating this logic.
type SecretResolver ¶
SecretResolver resolves an opaque reference (e.g. a ConnectorAccount ID, or a provider-setting ID) to the secret value a node needs — a database password, an API key, an OAuth token. nil is valid: a graph with no node that needs a secret never calls it.
type SubworkflowRunner ¶
type SubworkflowRunner interface {
Run(ctx context.Context, def workflow.Definition) (workflow.Result, error)
}
SubworkflowRunner executes a pkg/workflow.Definition — what the built-in "subworkflow" node type delegates to, for a step that genuinely needs a multi-agent chain (triage -> draft -> verify) rather than a single prompt.
type TestableExecutor ¶
type TestableExecutor interface {
TestConnection(ctx context.Context, rt *Runtime, params map[string]any) error
}
TestableExecutor is an optional capability a node type may implement to verify its own secretRef-based connection (a SQL Ping, a Redis PING, a Mongo Ping, an Elasticsearch root GET) without requiring the rest of its parameters (query, key, operation, ...) to be filled in or valid — the Go-idiomatic equivalent of n8n's per-node testedBy fallback, since a dataflow secret carries no "credential type" to hang a declarative ICredentialType.test off of (see DataflowSecret in seshat-server: an opaque name/value pair, nothing more). A node type that doesn't implement this simply has no "Test connection" affordance — Registry.Types() sets NodeDescription.IsTestable via a type assertion against this interface, so nothing needs a hardcoded type-name list anywhere.
type ToolSpec ¶
type ToolSpec struct {
// Name is the target node's own ID - stable, unique within the graph,
// and already meaningful to whoever built it (unlike a synthesized
// name), so it doubles as the tool's name the LLM sees.
Name string
Description string
// Schema is a JSON-schema object (the same shape sdk.Tool's
// InputSchema/schema.FromMap expects: {"type":"object","properties":
// {...},"required":[...]}), built from the target node's own
// NodeDescription.Properties.
Schema map[string]any
// Invoke runs the target node for real with args overriding its own
// static Parameters (LLM-supplied keys win), and returns its output
// serialized as JSON text - the same "records, not a single scalar"
// shape every other dataflow node produces.
Invoke func(ctx context.Context, args map[string]any) (string, error)
}
ToolSpec is one node exposed as an LLM-callable tool - the plain-data bridge between a graph's Tools reference and a real registrable tool (e.g. pkg/sdk.Tool). Deliberately has no dependency on pkg/sdk or internal/tools - dataflow stays a deterministic engine package; turning a ToolSpec into a real sdk.Tool (internal/tools/registry.NewBuilder(...)) happens at the AgentCaller implementation's own call site, mirroring how internal/tools/system/mcp/wrapper.go wraps an external MCP tool schema.
func ResolveTools ¶
func ResolveTools(def Definition, registry *Registry, rt *Runtime, nodeID string) ([]ToolSpec, error)
ResolveTools resolves nodeID's own Tools list into real, invocable ToolSpecs - called by queryNode.Execute (see builtin.go) before asking rt.Agent. Returns nil (not an error) for a node with no Tools set, the common case for a query with no tools at all.
A "tools"-type target is expanded recursively instead of becoming a ToolSpec itself (its own Tools list's members do, minus whatever its "disabled" parameter names) - this is how several "query" nodes share one reusable, individually-toggleable capability group instead of each repeating the same list of IDs (automation-app-pages.md §37.9).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package expr evaluates JS boolean/value expressions (n8n's "{{ $json.x }}" convention, already used by seshat-backend's inbox event filter) for dataflow nodes that need per-item logic (filter/if/set/switch) — unlike event_filter.go's one-VM-per-call approach (fine for a single check per inbound message), a dataflow node may evaluate the same expression against many items in one run, so this pool reuses goja runtimes and caches compiled bytecode across evaluations.
|
Package expr evaluates JS boolean/value expressions (n8n's "{{ $json.x }}" convention, already used by seshat-backend's inbox event filter) for dataflow nodes that need per-item logic (filter/if/set/switch) — unlike event_filter.go's one-VM-per-call approach (fine for a single check per inbound message), a dataflow node may evaluate the same expression against many items in one run, so this pool reuses goja runtimes and caches compiled bytecode across evaluations. |
|
Package nodes provides the generic, deterministic node types every dataflow graph can use regardless of tenant/product (http_request, filter, if, switch, set, merge, wait) — no credentials, no multi-tenant concerns.
|
Package nodes provides the generic, deterministic node types every dataflow graph can use regardless of tenant/product (http_request, filter, if, switch, set, merge, wait) — no credentials, no multi-tenant concerns. |
|
database
Package database provides deterministic node executors for querying external databases from a dataflow graph — Postgres, MySQL, SQLite (pkg/database/sql, three thin drivers sharing one executor), Redis, and MongoDB (native drivers), and Elasticsearch (its own REST API, no client SDK needed).
|
Package database provides deterministic node executors for querying external databases from a dataflow graph — Postgres, MySQL, SQLite (pkg/database/sql, three thin drivers sharing one executor), Redis, and MongoDB (native drivers), and Elasticsearch (its own REST API, no client SDK needed). |