Skip to content

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:

# Booleans
true false
# Numbers
42 # Integer
3.14 # Float
0xff # 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 operations
5 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 calls
add(5, 3) # Equivalent to: 5 3 add
join(["a", "b"], ",") # Equivalent to: ["a", "b"] "," join
or($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: true
5 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.14
42 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 and
true false and # Results in: false
true true then # Results in: true
# Argument form: a and(b)
true and(false) # Results in: false
true then(true) # Results in: true

or / default

Logical OR operation. Supports both calling forms:

# Stack form: a b or
false 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 not
true not # Results in: false
false not # Results in: true
# Argument form: not(a)
not(true) # Results in: false
not(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 request
http-get("https://api.example.com/users")
# Results in: response body as string
# With custom headers
http-get("https://api.example.com/data", {
"headers": {
"Authorization": "Bearer token123",
"Content-Type": "application/json"
}
})
# Chained with JSON parsing
http-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 form
secret("database-password")
# Results in: decrypted secret value
# Stack form
"api-key" secret
# Results in: decrypted secret value
# Chain with other operations
secret("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 metrics
metric("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 metrics
metric("current-replicas", {"runnable": "my-app"})
# Results in: 2.0 (current replica count)
# Filter by node tag
metric("used-disk-percent", {"tag": "worker"})
# Results in: 78.3 (disk usage on worker nodes)
# Container-specific metrics
metric("used-cpu-percent", {
"runnable": "web-server",
"container": "nginx"
})
# Results in: 23.5 (CPU usage for specific container)
# Use in conditional logic
metric("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 nodes
  • used-cpu-percent - CPU usage percentage
  • used-memory-percent - Memory usage percentage
  • used-disk-percent - Disk usage percentage
  • current-replicas - Current replica count

Options:

  • tag - Filter by node tag
  • runnable - 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 IP
ip-address-public
# Results in: "203.0.113.42"
# Use in configuration
ip-address-public set-tmp("node-ip")
`Node IP: ${node-ip}`
# Results in: "Node IP: 203.0.113.42"
# Chain with other operations
ip-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 runnable
peer-ip-address("web-server/frontend")
# Results in: "198.51.100.10" or nil if not found
# Use for service discovery
peer-ip-address("database/postgres") set-tmp("db-ip")
`postgres://${user}:${pass}@${db-ip}:5432/mydb`
# Results in: connection string with actual IP
# Chain with fallback
peer-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 available
free-port(8080)
# Results in: 8080 (if available) or error
# Find free port in range
free-port(8000, 8100)
# Results in: first available port between 8000-8100
# Check specific ports in order
free-port(3000, 3001, 8080, 9000)
# Results in: first available port from the list
# Use in configuration
free-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 command
exec("web-server", "whoami")
# Results in: "root\n"
# Execute command with multiple arguments
exec("database", "psql", "-c", "SELECT version();")
# Results in: PostgreSQL version output
# Chain with string processing
exec("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 output
exec("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 name
container-name("web-server")
# Results in: "my-app_web-server" (actual Docker container name)
# Use in logging or debugging
container-name("database") set-tmp("db-container")
`Connecting to container: ${db-container}`
# Results in: "Connecting to container: my-app_database"
# Chain with other operations
container-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 runnable
container-domain-name("api-service", "web")
# Results in: "api-service_web"
# Cross-runnable container reference
container-domain-name("shared/database", "postgres") set-tmp("db-name")
`Database container: ${db-name}`
# Results in: "Database container: shared-database_postgres"
# Use for container discovery
container-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 hostname
get-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 only
get-hostname("@local-service", "api")
# Results in: hostname for container in current runnable
# Chain with port for full address
get-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 IP
get-resolver-ip
# Results in: "172.18.0.1" (resolver IP address)
# Use in DNS configuration
get-resolver-ip set-tmp("dns-ip")
`nameserver ${dns-ip}`
# Results in: "nameserver 172.18.0.1"
# Chain with other network operations
get-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 hostname
service-hostname("web-app", "api")
# Results in: "web-app_api" or service-specific hostname
# Use for service discovery
service-hostname("shared/database", "postgres") set-tmp("db-host")
`postgres://${user}:${pass}@${db-host}:5432/mydb`
# Results in: connection string with service hostname
# Cross-service communication
service-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 port
service-port("web-app", "api")
# Results in: 8080 (service port number)
# Build complete service URL
service-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 configuration
service-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 address
service-ip("load-balancer", "nginx")
# Results in: "10.0.1.42" (actual IP address)
# service-address is an alias for service-ip
service-address("cache", "redis")
# Results in: IP address of Redis service
# Use for direct IP connections
service-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 available
service-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 string
connection("database")
# Results in: "postgres-db_postgres:5432"
# Use in application configuration
connection("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 URL
connection("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 hostname
connection-hostname("database")
# Results in: "postgres-db_postgres"
# Use for hostname-only configurations
connection-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 operations
connection-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 address
connection-ip("load-balancer")
# Results in: "10.0.1.100"
# connection-address is an alias
connection-address("database")
# Results in: IP address of database connection
# Use for direct IP connections
connection-ip("metrics") set-tmp("grafana-ip")
`http://${grafana-ip}:3000/dashboard`
# Results in: "http://10.0.1.15:3000/dashboard"
# Fallback chain for robust connections
connection-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 port
connection-port("database")
# Results in: 5432
# Use in port-specific configuration
connection-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 string
connection-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 path
connection-target("external-db")
# Results in: "shared/postgres"
# Use for cross-service discovery
connection-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 operations
connection-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 action
act("web-app/frontend/health-check")
# Results in: action result (e.g., "healthy")
# Execute action with arguments
act("database/postgres/backup", "format", "sql", "compress", "true")
# Results in: backup operation result
# Chain with result processing
act("api/service/get-stats")
parse-json
get-member("requests_per_minute")
# Results in: extracted metric from action result
# Use in conditional logic
act("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 check
wait-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 handling
wait-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 definition
entity
# Results in: current runnable's entity definition object
# Get specific entity by path
entity("web-app/frontend")
# Results in: entity definition for specified path
# get-entity is deprecated alias
get-entity("database/postgres")
# Results in: same as entity("database/postgres")
# Access entity properties
entity("api-service")
get-member("metadata")
get-member("version")
# Results in: version from entity metadata
# Use entity data in configuration
entity 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 state
entity-state
# Results in: current entity's runtime state object
# Get specific entity state
entity-state("worker/processor")
# Results in: runtime state for specified entity
# get-entity-state is deprecated alias
get-entity-state("monitoring/grafana")
# Results in: same as entity-state("monitoring/grafana")
# Access state information
entity-state("database")
get-member("containers")
get-member("postgres")
get-member("status")
# Results in: container status from entity state
# Monitor entity health
entity-state get-member("health") set-tmp("health-status")
$health-status "running" equal? then("Healthy") or("Unhealthy")
# Results in: health status message
# Get replica information
entity-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 or https://host:port/path
  • Unix sockets: /path/to/socket (no scheme prefix)

Usage pattern:

  1. Push URL onto stack (HTTP/HTTPS/Unix socket path)
  2. Call rpc-call with method name and arguments
  3. Result is automatically converted to appropriate ArrowScript type
  4. 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})