Commands
cross.stream commands use Nushell
expressions to define reusable
operations that can be called on-demand with arguments. Unlike handlers which
maintain state between invocations, or generators which run continuously,
commands are stateless and execute independently each time they are called.
Defining Commands
To create a command, append a definition string with the topic
<command-name>.define
:
r#'{ # Required: Command closure run: {|frame| # frame.topic - always <command>.call # frame.hash - contains input content if present # frame.meta.args - contains call arguments let input = if ($frame.hash != null) { .cas $frame.hash } else { null } let n = $frame.meta.args.n 1..($n) | each {$"($in): ($input)"} }
# Optional: Module definitions modules: { "my-util": "export def format [x] { $\"formatted: ($x)\" }" }
# Optional: Control output frame behavior return_options: { suffix: ".output" # Output topic suffix (default: ".response") ttl: "head:1" # Keep only most recent frame }}'# | .append repeat.define
The return_options
field controls the suffix and TTL for the .response
frame
produced by the command. TTL only applies to this .response
frame—.error
events never expire.
The command definition requires:
run
: A closure that receives the call frame and can return a pipeline of results
All values produced by the closure’s output pipeline are collected into a single
.response
event automatically.
Calling Commands
Commands are called by appending to <command-name>.call
with input content and
arguments:
# Call the repeat command with input and args"foo" | .append repeat.call --meta {args: {n: 3}}
Lifecycle Events
Commands emit events to track their execution:
Event | Description |
---|---|
<command>.response | Collected result of the command pipeline |
<command>.error | Error occurred during command execution |
All events include:
command_id
: ID of the command definitionframe_id
: ID of this specific invocation
Error Handling
If a command encounters an error during execution, it will:
- Emit a
<command>.error
frame with:- The error message
- Reference to both command_id and frame_id
- Stop processing the current invocation
Unlike generators, commands do not automatically restart on error - each invocation is independent.
Modules
Commands can use custom Nushell modules:
r#'{ run: {|frame| my-math double ($frame.meta.args.number) }
modules: { "my-math": "export def double [x] { $x * 2 }" }}'# | .append calculator.define
This allows you to modularize your commands and reuse code across different commands.
Contexts
Command definitions and calls are scoped by context. Defining the same command name in two different contexts creates two independent commands. Calls are processed only when a matching definition exists in the same context; otherwise the call is ignored.
Built-in Store Commands
When the run
closure executes it can use several helper commands provided by
cross.stream:
.append
– append a new frame. Metadata you provide is merged withcommand_id
andframe_id
..cat
– read frames from the command’s context..head
– fetch the most recent frame for a topic in this context..cas
– read content from CAS by hash..get
– retrieve a frame by ID..remove
– delete a frame from the stream.
cat
and head
default to the command’s context, while append
accepts an
explicit context flag if you need to write elsewhere.
Key Differences
Feature | Commands | Handlers | Generators |
---|---|---|---|
State | Stateless | Stateful between calls | Stateless |
Execution | On-demand | Event-driven | Continuous |
Results | Streamed immediately | Batched on completion | Streamed |
Parallelism | Multiple parallel calls | Sequential processing | Single instance |
Error Handling | Per-invocation | Unregisters handler | Auto-restarts |
Modules | Supported | Supported | Not supported |