ArrowScript Language Reference
ArrowScript is a stack-based expression language designed for Monk templates. It’s optimized for concise, readable one-liners rather than complex algorithms.
Language Overview
ArrowScript is embedded within MonkScript YAML templates using the <-
prefix. It executes on Monk’s control plane at runtime, enabling dynamic configuration and data transformation.
ArrowScript is:
- Stack-based: Values are pushed onto a stack and operators consume them
- Expression-oriented: Designed for data transformation and computation
- Template-friendly: Perfect for one-liner expressions in configuration files
- Flexible: Supports multiple calling conventions for readability
- Runtime-executed: Computed when templates are processed, not at parse time
ArrowScript is NOT:
- A general-purpose programming language
- Suitable for complex algorithms or control flow
- Designed for multi-line scripts (though possible)
Syntax Primer
Basic Types and Literals
ArrowScript supports several built-in types:
# Booleanstrue false
# Numbers42 # Integer3.14 # Float0xff # Hexadecimal (255)123n # BigInt (for very large numbers)
# Strings"hello world" # Regular string'single quotes' # Also valid
# String interpolation`Hello ${name}, you are ${age} years old!`
# Arrays[1, 2, 3]["apple", "banana", "cherry"][$var1, $var2]
# Objects{"name": "John", "age": 30}{key: $variable, "other-key": "value"}
# Variables$variable-name$user["name"] # Object member access$items[0] # Array index access
Calling Conventions
ArrowScript supports two main calling styles that can be mixed for readability:
1. Stack Form (Postfix)
Values are placed on the stack, then the operator is called:
# Basic stack operations5 3 add # Pushes 5, pushes 3, calls add → 8"hello" trim # Pushes "hello", calls trim → "hello"true false and # Pushes true, pushes false, calls and → false
# Chain operations naturally" hello " trim to-upper # Chain: trim then to-upper
2. Function Form (with arguments)
Operators can be called with parentheses like functions:
# Function-style callsadd(5, 3) # Equivalent to: 5 3 addjoin(["a", "b"], ",") # Equivalent to: ["a", "b"] "," joinor($value, "default") # Equivalent to: $value "default" or
3. Mixed Form (Hybrid)
You can combine both styles for optimal readability:
# Stack value + function arguments"hello world" replace("world", "ArrowScript") # Clean and readable$data get-member("user") or("guest") # Stack then function args
Variable Access
Variables use the $
prefix and support member access:
$username # Simple variable$user["name"] # Object property access$items[0] # Array index access$config["database"]["host"] # Nested access
String Interpolation
Use backticks for dynamic strings:
`Hello ${username}!``Score: ${score}/100 (${percentage}%)``API URL: https://${host}:${port}/api/v${version}`
Temporary Variables
Store intermediate results using set-tmp
:
# Store intermediate results$input trim set-tmp("clean-input")$clean-input empty? "default" $clean-input or
Function Definitions
Create reusable functions with arrow syntax:
# Define a function<- (name age) { `Hello ${name}, you are ${age} years old!` }
# Function with no parameters<- { random-uuid }
Common Patterns
Conditional logic:
$value "fallback" or # Stack form$value or("fallback") # Function form
Data transformation:
$data parse-json get-member("result") first # Chain operations$api-response or({}) get-in("user.name") # Safe access
String building:
mark "Hello " $name "!" concat-all # Multiple strings`Hello ${name}!` # Interpolation (cleaner)
Type safety:
$input to-string trim # Ensure string, then trim$score to-int or(0) # Safe number conversion
Basic Syntax Examples
Here are some quick examples to get you started:
"hello" trim # Trims whitespace from "hello"5 7 add # Adds 5 + 7["a", "b"] join(",") # Joins array with comma separator$config or({}) get-member("timeout") or(30) # Safe config access`User: ${username}` # String interpolation
Built-in Operators Reference
This section documents all the built-in operators available in ArrowScript, organized by category.
Stack & Variable Operations
const
Pushes a constant value onto the stack (internal use). Argument form only:
const(42) # Pushes 42 onto stack
var
Pushes the value of a variable onto the stack (internal use). Argument form only:
var("myvar") # Pushes value of myvar onto stack
dup
Duplicates the top value. Stack form only:
42 dup # Results in: 42 42
set
Sets a variable and leaves the value on the stack.
42 set("myvar") # Sets myvar = 42, leaves 42 on stack
set-tmp
Sets a temporary variable (local to the script) and leaves the value on the stack.
42 set-tmp("temp") # Sets temp = 42, leaves 42 on stack
pop-tmp
Sets a temporary variable but removes the value from the stack.
42 pop-tmp("temp") # Sets temp = 42, removes 42 from stack
unset
Removes a variable.
unset("myvar") # Removes myvar
mark
Sets a mark on the stack (used with concat-all
and similar operators).
mark # Sets a mark (used internally)
Math Operations
add
Adds two numbers. Stack form only:
5 3 add # Results in: 8
sub
Subtracts the second number from the first. Stack form only:
10 3 sub # Results in: 7
mult
Multiplies two numbers. Stack form only:
6 7 mult # Results in: 42
div
Divides the first number by the second. Stack form only:
15 3 div # Results in: 5
cmp
Compares two numbers for equality. Stack form only:
5 5 cmp # Results in: true5 3 cmp # Results in: false
Type Conversion
to-string
/ to-s
Converts any value to a string. Stack form only:
42 to-string # Results in: "42"true to-s # Results in: "true"
to-int
/ to-i
Converts a value to an integer. Stack form only, with optional base for strings:
"42" to-int # Results in: 42 (base 10)3.14 to-i # Results in: 3
# With base argument (for string parsing)"ff" to-int(16) # Results in: 255 (hex)"1010" to-i(2) # Results in: 10 (binary)
to-float
/ to-f
Converts a value to a float. Stack form only:
"3.14" to-float # Results in: 3.1442 to-f # Results in: 42.0
to-bigint
/ to-bi
Converts a value to a big integer. Stack form only, with optional base for strings:
"999999999999999999" to-bigint # Results in: 999999999999999999n
# With base argument (for string parsing)"ff" to-bigint(16) # Results in: 255n (hex)"1010" to-bi(2) # Results in: 10n (binary)
to-hex
Converts a number to hexadecimal string. Stack form only:
255 to-hex # Results in: "ff"
Boolean Logic
and
/ then
Logical AND operation. Supports both calling forms:
# Stack form: a b andtrue false and # Results in: falsetrue true then # Results in: true
# Argument form: a and(b)true and(false) # Results in: falsetrue then(true) # Results in: true
or
/ default
Logical OR operation. Supports both calling forms:
# Stack form: a b orfalse true or # Results in: true"" "fallback" default # Results in: "fallback"
# Argument form: a or(b)false or(true) # Results in: true"" default("fallback") # Results in: "fallback"
not
Logical NOT operation. Supports both calling forms:
# Stack form: a nottrue not # Results in: falsefalse not # Results in: true
# Argument form: not(a)not(true) # Results in: falsenot(false) # Results in: true
String Operations
concat
Combines two strings. Stack form only:
"hello" " world" concat # Results in: "hello world"
concat-all
Combines multiple strings (requires mark operator). Stack form only:
mark "a" "b" "c" concat-all # Results in: "abc"
trim
Removes whitespace from beginning and end of string. Stack form only:
" hello " trim # Results in: "hello"
split
Splits a string into an array. Supports both calling forms:
# With separator argument"a,b,c" split(",") # Results in: ["a", "b", "c"]
# Without separator (splits into characters)"hello" split # Results in: ["h", "e", "l", "l", "o"]
join
Joins an array of strings into one string. Supports both calling forms:
# With delimiter argument["a", "b", "c"] join(",") # Results in: "a,b,c"
# Without delimiter (concatenates directly)["hello", "world"] join # Results in: "helloworld"
replace
Replaces text in a string. Supports multiple calling forms:
# Two arguments form: string replace(old, new)"hello world" replace("world", "monk") # Results in: "hello monk"
# Mixed form: string old replace(new)"hello world" "world" replace("monk") # Results in: "hello monk"
Comparison & Testing
equal?
/ eq?
Tests if two values are equal.
5 5 equal? # Results in: true"a" "b" eq? # Results in: false
not-equal?
/ not-eq?
Tests if two values are not equal.
5 3 not-equal? # Results in: true
contains?
/ has?
Tests if a container contains an item.
"hello" "ell" contains? # Results in: true[1, 2, 3] 2 has? # Results in: true
empty?
Tests if a collection is empty.
[] empty? # Results in: true"" empty? # Results in: true[1] empty? # Results in: false
Error Handling
error?
Tests if a value is an error.
someValue error? # Results in: true/false
if-error
Handles error values.
someValue if-error # Returns alternative if someValue is an error
if-errorf
Handles error values with a function.
someValue if-errorf # Calls function if someValue is an error
Function Calls
call
Dynamically calls an operator by name with arguments.
"add" [5, 3] call # Calls the add operator with 5 and 3
callf
Calls a compiled function by ID (internal use).
2 callf # Calls function with ID 2
JSON Operations
parse-json
Parses a JSON string into a value.
'{"name": "John", "age": 30}' parse-json # Results in: object'[1, 2, 3]' parse-json # Results in: [1, 2, 3]
to-json
Converts a value to JSON string.
{"name": "John"} to-json # Results in: '{"name":"John"}'[1, 2, 3] to-json # Results in: '[1,2,3]'
Object & Array Operations
object
Creates an empty object.
object # Results in: {}
array
Creates an empty array.
array # Results in: []
assoc
Sets a property on an object.
{} "name" "John" assoc # Results in: {"name": "John"}
dissoc
Removes a property from an object.
{"name": "John", "age": 30} "age" dissoc # Results in: {"name": "John"}
append
Adds an item to an array.
[1, 2] 3 append # Results in: [1, 2, 3]
get-member
Gets a property from an object or array element.
{"name": "John"} get-member("name") # Results in: "John"[1, 2, 3] get-member(0) # Results in: 1
get-in
Gets a nested value using a path.
{"user": {"name": "John"}} get-in("user.name") # Results in: "John"
count
Returns the number of items in a collection.
[1, 2, 3] count # Results in: 3"hello" count # Results in: 5{"a": 1, "b": 2} count # Results in: 2
first
Gets the first element of an array.
[1, 2, 3] first # Results in: 1
last
Gets the last element of an array.
[1, 2, 3] last # Results in: 3
Random Operations
random-uuid
Generates a random UUID.
random-uuid # Results in: "550e8400-e29b-41d4-a716-446655440000"
rand
Generates a random integer.
rand # Results in: some random number
rand1
Generates a random integer from 0 to max.
10 rand1 # Results in: number between 0-9
rand2
Generates a random integer between min and max.
5 15 rand2 # Results in: number between 5-14
toss
Randomly returns 0 or 1 (coin flip).
toss # Results in: 0 or 1
Functional Operations
map
Applies a function block over each element in an array, returning a new array with the results. Supports multiple calling forms:
# Stack form: block array map<-{10 add} [1, 2, 3, 4] map# Results in: [11, 12, 13, 14]
# Function form: array map(block)[1, 2, 3, 4] map(<-{10 add})# Results in: [11, 12, 13, 14]
# Argument form: map(block, array)map(<-{10 add}, [1, 2, 3, 4])# Results in: [11, 12, 13, 14]
# Complex transformations["hello", "world"] map(<-{to-upper})# Results in: ["HELLO", "WORLD"]
# Chain with other operations[1, 2, 3] map(<-{to-string "!" concat}) join(" ")# Results in: "1! 2! 3!"
filter
Filters elements from an array using a predicate function block. Supports multiple calling forms:
# Stack form: predicate array filter<-{empty? not} ["", "hello", "", "world"] filter# Results in: ["hello", "world"] (non-empty strings)
# Function form: array filter(predicate)["test", "test2", "nope"] filter(<-{contains?("test")})# Results in: ["test", "test2"]
# Argument form: filter(predicate, array)filter(<-{contains?("test")}, ["test", "test2", "nope"])# Results in: ["test", "test2"]
# Filter objects by property[{"name": "Alice", "active": true}, {"name": "Bob", "active": false}] filter(<-{get-member("active")})# Results in: [{"name": "Alice", "active": true}] (truthy active values)
# Chain with processing$users filter(<-{get-member("active") equal?(true)}) map(<-{get-member("name")})# Results in: array of active user names
reduce
Reduces an array to a single value by applying a function block cumulatively. Supports multiple calling forms:
# Stack form: block array reduce<-{add} [1, 2, 3, 4] reduce# Results in: 10
# Function form: array reduce(block)[1, 2, 3, 4] reduce(<-{add})# Results in: 10
# Argument form: reduce(block, array)reduce(<-{add}, [1, 2, 3, 4])# Results in: 10
# Named parameters for clarity[1, 2, 3, 4] reduce(<-(accumulator current){$accumulator $current add})# Results in: 10
# String concatenation["hello", " ", "world"] reduce(<-{concat})# Results in: "hello world"
# Build a summary string["apple", "banana", "cherry"] reduce(<-(acc item){$acc ", " $item concat concat})# Results in: "apple, banana, cherry"
# Single element arrays return that element[42] reduce(<-{add})# Results in: 42
Usage Notes:
- All three operators work with nil arrays (return nil)
reduce
requires at least one element in the array- Function blocks must maintain proper stack discipline (push exactly one result)
- Named parameters can be used in function blocks for better readability
Extended Operators
These operators provide additional functionality for specific use cases in Monk templates.
HTTP Operations
http-get
Makes an HTTP GET request to a URL. Argument form only:
# Basic GET requesthttp-get("https://api.example.com/users")# Results in: response body as string
# With custom headershttp-get("https://api.example.com/data", { "headers": { "Authorization": "Bearer token123", "Content-Type": "application/json" }})
# Chained with JSON parsinghttp-get("https://jsonplaceholder.typicode.com/users/1") parse-json get-member("name")# Results in: extracted name from JSON response
http-post
Makes an HTTP POST request with a request body. Mixed form:
# Basic POST with JSON body'{"name": "John", "age": 30}' http-post("https://api.example.com/users")# Results in: response body as string
# POST with custom headers'{"message": "Hello"}' http-post("https://api.example.com/messages", { "headers": { "Authorization": "Bearer token123", "Content-Type": "application/json" } })
# Build request body dynamically{"name": $username, "email": $email} to-json http-post("https://api.example.com/signup")# Results in: API response
URL & Network Operations
tld-plus-one
Extracts the effective top-level domain plus one segment from a hostname. Stack form only:
# Extract domain from subdomain"api.example.com" tld-plus-one# Results in: "example.com"
# Handle complex TLDs"something.foo.co.uk" tld-plus-one# Results in: "foo.co.uk"
# Chain with other operations$request-url parse-json get-member("host") tld-plus-one set-tmp("base-domain")# Results in: extracted base domain
Secret Management
secret
Retrieves a secret value from the encrypted secret store. Supports both calling forms:
# Argument formsecret("database-password")# Results in: decrypted secret value
# Stack form"api-key" secret# Results in: decrypted secret value
# Chain with other operationssecret("jwt-secret") set-tmp("jwt")$jwt empty? then("default-secret") or($jwt)# Results in: secret value with fallback
# Use in string interpolation`Database URL: postgres://user:${secret("db-password")}@host/db`# Results in: connection string with secret
Metrics & Monitoring
metric
Retrieves metric values from the cluster or runnable. Argument form only:
# Get cluster-wide metricsmetric("node-count")# Results in: 3.0 (number of nodes)
metric("used-cpu-percent")# Results in: 65.5 (CPU usage percentage)
metric("used-memory-percent")# Results in: 42.8 (memory usage percentage)
# Get runnable-specific metricsmetric("current-replicas", {"runnable": "my-app"})# Results in: 2.0 (current replica count)
# Filter by node tagmetric("used-disk-percent", {"tag": "worker"})# Results in: 78.3 (disk usage on worker nodes)
# Container-specific metricsmetric("used-cpu-percent", { "runnable": "web-server", "container": "nginx"})# Results in: 23.5 (CPU usage for specific container)
# Use in conditional logicmetric("used-memory-percent") set-tmp("memory-usage")$memory-usage empty? then("No memory data") or("Memory data available")# Results in: status message based on data availability
Available metrics:
node-count
- Number of cluster nodesused-cpu-percent
- CPU usage percentageused-memory-percent
- Memory usage percentageused-disk-percent
- Disk usage percentagecurrent-replicas
- Current replica count
Options:
tag
- Filter by node tagrunnable
- Target specific runnable (defaults to current)container
- Target specific container within runnable
Network & IP Operations
ip-address-public
Returns the current node’s public IP address. Stack form only:
# Get current node's public IPip-address-public# Results in: "203.0.113.42"
# Use in configurationip-address-public set-tmp("node-ip")`Node IP: ${node-ip}`# Results in: "Node IP: 203.0.113.42"
# Chain with other operationsip-address-public split(".") first to-int# Results in: first octet as integer
peer-ip-address
Returns the public IP address of the peer running a specific runnable. Argument form only:
# Get IP of peer running a specific runnablepeer-ip-address("web-server/frontend")# Results in: "198.51.100.10" or nil if not found
# Use for service discoverypeer-ip-address("database/postgres") set-tmp("db-ip")`postgres://${user}:${pass}@${db-ip}:5432/mydb`# Results in: connection string with actual IP
# Chain with fallbackpeer-ip-address("api-gateway") or("localhost")# Results in: peer IP or localhost fallback
free-port
Finds an available port using various strategies. Argument form only:
# Get any free port from default range (3000-9999)free-port# Results in: 3142 (or any available port)
# Check if specific port is availablefree-port(8080)# Results in: 8080 (if available) or error
# Find free port in rangefree-port(8000, 8100)# Results in: first available port between 8000-8100
# Check specific ports in orderfree-port(3000, 3001, 8080, 9000)# Results in: first available port from the list
# Use in configurationfree-port(8080, 8081, 8082) set-tmp("app-port")`Server starting on port ${app-port}`# Results in: startup message with assigned port
Container Operations
exec
Executes a command inside a running container and returns the stdout. Argument form only:
# Execute simple commandexec("web-server", "whoami")# Results in: "root\n"
# Execute command with multiple argumentsexec("database", "psql", "-c", "SELECT version();")# Results in: PostgreSQL version output
# Chain with string processingexec("app", "cat", "/app/version.txt") trim set-tmp("app-version")`Application version: ${app-version}`# Results in: "Application version: 1.2.3"
# Execute and parse JSON outputexec("api", "curl", "-s", "localhost:8080/health") parse-json get-member("status")# Results in: extracted status from health check
container-name
Returns the full Docker container name for a container within the current runnable. Argument form only:
# Get full container namecontainer-name("web-server")# Results in: "my-app_web-server" (actual Docker container name)
# Use in logging or debuggingcontainer-name("database") set-tmp("db-container")`Connecting to container: ${db-container}`# Results in: "Connecting to container: my-app_database"
# Chain with other operationscontainer-name("worker") split("_") last# Results in: "worker" (short name extracted back)
container-domain-name
Returns the full Docker container name for a container in a specific runnable path. Argument form only:
# Get container name from specific runnablecontainer-domain-name("api-service", "web")# Results in: "api-service_web"
# Cross-runnable container referencecontainer-domain-name("shared/database", "postgres") set-tmp("db-name")`Database container: ${db-name}`# Results in: "Database container: shared-database_postgres"
# Use for container discoverycontainer-domain-name("monitoring/grafana", "grafana") set-tmp("grafana-container")# Results in: stored full container name for later use
get-hostname
/ get-container-ip
Returns the hostname or IP address of a container on the overlay network. Argument form only:
# Get container hostnameget-hostname("web-app", "frontend")# Results in: "web-app_frontend" (overlay network hostname)
# Get container IP (alias for get-hostname)get-container-ip("database", "postgres")# Results in: "database_postgres" or actual IP
# Use @ prefix to search in current runnable onlyget-hostname("@local-service", "api")# Results in: hostname for container in current runnable
# Chain with port for full addressget-hostname("api-gateway", "nginx") set-tmp("gateway-host")`http://${gateway-host}:80/api`# Results in: "http://api-gateway_nginx:80/api"
get-resolver-ip
Returns the IP address of the DNS resolver for the overlay network. Stack form only:
# Get DNS resolver IPget-resolver-ip# Results in: "172.18.0.1" (resolver IP address)
# Use in DNS configurationget-resolver-ip set-tmp("dns-ip")`nameserver ${dns-ip}`# Results in: "nameserver 172.18.0.1"
# Chain with other network operationsget-resolver-ip split(".") first concat(".0.0.0/8")# Results in: network range string like "172.0.0.0/8"
Service Operations
service-hostname
Returns the hostname for a named service. Argument form only:
# Get service hostnameservice-hostname("web-app", "api")# Results in: "web-app_api" or service-specific hostname
# Use for service discoveryservice-hostname("shared/database", "postgres") set-tmp("db-host")`postgres://${user}:${pass}@${db-host}:5432/mydb`# Results in: connection string with service hostname
# Cross-service communicationservice-hostname("@auth-service", "oauth") set-tmp("auth-endpoint")`OAuth endpoint: https://${auth-endpoint}/oauth/token`# Results in: OAuth endpoint URL
service-port
Returns the port number for a named service. Argument form only:
# Get service portservice-port("web-app", "api")# Results in: 8080 (service port number)
# Build complete service URLservice-hostname("api-gateway", "rest") set-tmp("api-host")service-port("api-gateway", "rest") set-tmp("api-port")`http://${api-host}:${api-port}/v1/users`# Results in: "http://api-gateway_rest:8080/v1/users"
# Use in configurationservice-port("database", "postgres") set-tmp("db-port")$db-port 5432 equal? then("Standard port") or("Custom port")# Results in: port type description
service-ip
/ service-address
Returns the IP address for a named service. Argument form only:
# Get service IP addressservice-ip("load-balancer", "nginx")# Results in: "10.0.1.42" (actual IP address)
# service-address is an alias for service-ipservice-address("cache", "redis")# Results in: IP address of Redis service
# Use for direct IP connectionsservice-ip("database", "postgres") set-tmp("db-ip")`Direct connection: ${db-ip}:5432`# Results in: "Direct connection: 10.0.1.15:5432"
# Fallback to hostname if IP not availableservice-ip("api", "web") or(service-hostname("api", "web"))# Results in: IP address or hostname as fallback
Connection Operations
connection
Returns the full connection string (hostname:port) for a named connection. Argument form only:
# Get complete connection stringconnection("database")# Results in: "postgres-db_postgres:5432"
# Use in application configurationconnection("cache") set-tmp("redis-conn")`REDIS_URL=redis://${redis-conn}/0`# Results in: "REDIS_URL=redis://cache_redis:6379/0"
# Chain with protocol for full URLconnection("api-gateway") set-tmp("gateway")`https://${gateway}/api/v1`# Results in: "https://api-gateway_nginx:443/api/v1"
connection-hostname
Returns the hostname portion of a named connection. Argument form only:
# Get connection hostnameconnection-hostname("database")# Results in: "postgres-db_postgres"
# Use for hostname-only configurationsconnection-hostname("message-queue") set-tmp("rabbitmq-host")`amqp://user:pass@${rabbitmq-host}/vhost`# Results in: "amqp://user:pass@message-queue_rabbitmq/vhost"
# Chain with DNS operationsconnection-hostname("web-service") split("_") first# Results in: runnable name extracted from hostname
connection-ip
/ connection-address
Returns the IP address of a named connection. Argument form only:
# Get connection IP addressconnection-ip("load-balancer")# Results in: "10.0.1.100"
# connection-address is an aliasconnection-address("database")# Results in: IP address of database connection
# Use for direct IP connectionsconnection-ip("metrics") set-tmp("grafana-ip")`http://${grafana-ip}:3000/dashboard`# Results in: "http://10.0.1.15:3000/dashboard"
# Fallback chain for robust connectionsconnection-ip("api") or(connection-hostname("api"))# Results in: IP address or hostname if IP unavailable
connection-port
Returns the port number of a named connection. Argument form only:
# Get connection portconnection-port("database")# Results in: 5432
# Use in port-specific configurationconnection-port("web-server") set-tmp("app-port")$app-port 443 equal? then("HTTPS") or("HTTP") set-tmp("protocol")`${protocol} server on port ${app-port}`# Results in: "HTTPS server on port 443"
# Build custom connection stringconnection-hostname("api") set-tmp("api-host")connection-port("api") set-tmp("api-port")`tcp://${api-host}:${api-port}`# Results in: "tcp://api-service_web:8080"
connection-target
Returns the target runnable path of a named connection. Argument form only:
# Get connection target pathconnection-target("external-db")# Results in: "shared/postgres"
# Use for cross-service discoveryconnection-target("auth-service") set-tmp("auth-runnable")`Connecting to authentication at ${auth-runnable}`# Results in: "Connecting to authentication at identity/oauth-server"
# Chain with other operationsconnection-target("monitoring") split("/") last# Results in: final segment of target path
Action Operations
act
Executes an action on a remote runnable and returns the result. Argument form only:
# Execute simple actionact("web-app/frontend/health-check")# Results in: action result (e.g., "healthy")
# Execute action with argumentsact("database/postgres/backup", "format", "sql", "compress", "true")# Results in: backup operation result
# Chain with result processingact("api/service/get-stats") parse-json get-member("requests_per_minute")# Results in: extracted metric from action result
# Use in conditional logicact("load-balancer/nginx/health") set-tmp("lb-status")$lb-status "healthy" equal? then("LB OK") or("LB DOWN")# Results in: status message based on action result
wait-for
Repeatedly executes an action until it returns a truthy value, with delay and max attempts. Argument form only:
# Wait for service to be ready (5 second delay, max 10 attempts)wait-for(5, 10, "database/postgres/ready-check")# Results in: truthy result when service is ready, or error after max attempts
# Wait for deployment with custom checkwait-for(3, 20, "web-app/frontend/deployment-status", "version", "1.2.3")# Results in: deployment status when version 1.2.3 is deployed
# Chain with fallback handlingwait-for(2, 5, "cache/redis/ping") error? "Cache unavailable" "Cache ready" or# Results in: status message based on wait result
Entity Operations
entity
/ get-entity
Retrieves the definition of an entity. Supports both calling forms:
# Get current entity definitionentity# Results in: current runnable's entity definition object
# Get specific entity by pathentity("web-app/frontend")# Results in: entity definition for specified path
# get-entity is deprecated aliasget-entity("database/postgres")# Results in: same as entity("database/postgres")
# Access entity propertiesentity("api-service") get-member("metadata") get-member("version")# Results in: version from entity metadata
# Use entity data in configurationentity get-member("name") set-tmp("entity-name")`Current entity: ${entity-name}`# Results in: "Current entity: my-web-app"
entity-state
/ get-entity-state
Retrieves the runtime state of an entity. Supports both calling forms:
# Get current entity stateentity-state# Results in: current entity's runtime state object
# Get specific entity stateentity-state("worker/processor")# Results in: runtime state for specified entity
# get-entity-state is deprecated aliasget-entity-state("monitoring/grafana")# Results in: same as entity-state("monitoring/grafana")
# Access state informationentity-state("database") get-member("containers") get-member("postgres") get-member("status")# Results in: container status from entity state
# Monitor entity healthentity-state get-member("health") set-tmp("health-status")$health-status "running" equal? then("Healthy") or("Unhealthy")# Results in: health status message
# Get replica informationentity-state("web-service") get-member("replicas") get-member("current")# Results in: current replica count
RPC Operations
rpc-call
Makes JSON-RPC calls to remote services. Mixed form:
# Basic JSON-RPC call"http://localhost:8080/rpc" rpc-call("add", 5, 3)# Results in: 8 (result from remote add method)
# Call with array arguments"http://api.example.com/rpc" rpc-call("count", ["foo", 7]) get-member("result")# Results in: extracted result from RPC response
# Unix socket RPC call"/tmp/service.sock" rpc-call("get_status")# Results in: status from Unix socket RPC service
# Chain with error handling"https://external-api.com/rpc" rpc-call("validate", $data) error? "RPC call failed" get-member("valid") or# Results in: validation result or error message
# Complex RPC with multiple arguments"http://calculator.service/rpc" rpc-call("compute", { "operation": "multiply", "operands": [42, 24]}) get-member("result")# Results in: 1008 (computation result)
# Use in service integration$api-endpoint rpc-call("get_user", $user-id) set-tmp("user-data")$user-data get-member("name") set-tmp("username")`Welcome, ${username}!`# Results in: "Welcome, John!" (using RPC-fetched username)
Supported transports:
- HTTP/HTTPS:
http://host:port/path
orhttps://host:port/path
- Unix sockets:
/path/to/socket
(no scheme prefix)
Usage pattern:
- Push URL onto stack (HTTP/HTTPS/Unix socket path)
- Call
rpc-call
with method name and arguments - Result is automatically converted to appropriate ArrowScript type
- Access response data using standard object/array operators
Practical Examples
This section provides real-world examples of ArrowScript usage in MonkScript templates.
Dynamic Configuration
variables: environment: "production" replicas: <- $environment "production" equal? then(3) else(1) log-level: <- $environment "development" equal? then("debug") else("info") db-password: <- secret("db-password") db-host: <- connection-hostname("database") database-url: <- `postgres://${db-user}:${db-password}@${db-host}:5432/myapp`
Service Discovery and Health Checks
variables: cache-host: <- connection-hostname("cache") cache-health-url: <- `http://${cache-host}:6379/ping` api-host: <- service-hostname("api-gateway", "nginx") api-endpoint: <- `https://${api-host}/v1` cache-connection: <- connection-address("cache") cache-config: <- `redis://${cache-connection}/0`
checks: readiness: code: | connection-address("database") http-get parse-json http-get($cache-health-url) parse-json and period: 10
Conditional Logic and Fallbacks
variables: port: <- free-port(8080, 8081, 8082) memory-usage: <- metric("used-memory-percent") memory-limit: <- $memory-usage 80 gt? then("2G") else("1G") backup-enabled: <- $environment "production" equal? $backup-schedule empty? not and database-env: <- `DATABASE_URL=${database-url}` log-env: <- `LOG_LEVEL=${log-level}` backup-env: <- `BACKUP_SCHEDULE=${backup-schedule}`
containers: app: memory: <- $memory-limit environment: - <- $database-env - <- $log-env - <- $backup-enabled then($backup-env) else("")
Data Processing and Transformation
variables: # Get cluster node information cluster-nodes: <- entity-state get-member("nodes") node-statuses: <- $cluster-nodes map(<-{get-member("status")}) healthy-nodes: <- $node-statuses filter(<-{"running" equal?}) count cluster-info: <- `Cluster: ${healthy-nodes} healthy nodes`
# Build service URLs (simplified - complex mapping needs separate variables) web-host: <- service-hostname("app", "web") web-port: <- service-port("app", "web") web-url: <- `http://${web-host}:${web-port}`
api-host: <- service-hostname("app", "api") api-port: <- service-port("app", "api") api-url: <- `http://${api-host}:${api-port}`
Error Handling and Validation
variables: # Port validation with fallback port-as-int: <- $user-port to-int validated-port: <- $port-as-int error? then(8080) else($port-as-int)
# Safe config loading with fallbacks config-response: <- http-get($config-url) default-config-json: <- `{"default": true}` safe-response: <- $config-response error? then($default-config-json) else($config-response) parsed-config: <- $safe-response parse-json app-config: <- $parsed-config get-member("app-config") final-config: <- $app-config or({"timeout": 30, "retries": 3})