spyweb injects several global helper functions into the Lua environment for networking,
notifications, logging, and storage.
Storage & Persistence
SpyWeb supports state management through both Runtime Memory (transient)
and an Embedded Database (persistent). Memory is fast but resets on
reload; the database survives restarts.
💡 Race Condition Note: Both Runtime Memory and
Job-Local Storage are safe by default. Hooks for a single job are
execution-locked (sequential), so race conditions are impossible within a single job.
Use global_store_incr strictly when you need to mutate state shared across
multiple concurrent jobs.
| Function |
Description |
store_set(key, value) |
Save a string (prefixed with job name) |
store_get(key) |
Retrieve a string or nil |
store_delete(key) |
Remove a key |
Example: Job-Scoped Counter
local c = tonumber(store_get("count") or "0")
store_set("count", tostring(c + 1))
| Function |
Description |
global_store_set(key, value) |
Save a string (shared across all jobs) |
global_store_get(key) |
Retrieve a shared string or nil |
global_store_delete(key) |
Remove a shared key |
global_store_incr(k, def, delta) |
Atomic shared increment across all jobs |
Example: Atomic Shared Logic
-- WRONG APPROACH (race condition prone)
local v = tonumber(global_store_get("visits") or "0")
global_store_set("visits", tostring(v + 1))
-- CORRECT APPROACH (race condition safe)
global_store_incr("visits", 0, 1)
⚠️ Resets on hot-reload or restart. Use for transient state
only.
Standard Lua variables persist in memory as long as the job is
active. Because each job runs in its own Isolated VM, these
variables are naturally job-scoped and cannot be accessed by other jobs.
SpyWeb also maintains a runtime-owned global,
last_fetch, which is overwritten on every fetch attempt and
persists until the next fetch. It reflects the last successful or failed fetch
snapshot for that job VM.
Example: Simple Memory Counter
-- This resets to 1 if you edit hooks.lua or restart SpyWeb
visit_count = (visit_count or 0) + 1
log("Session visit: " .. visit_count)
Example: Inspect the last fetch in a
later hook
function before_webhook(payload)
if last_fetch and last_fetch.ok and last_fetch.body then
local count = 0
for _ in last_fetch.body:gmatch("promo") do
count = count + 1
end
payload.last_fetch_promo_hits = count
end
return payload
end
http_get(url, [headers])
Performs a synchronous HTTP GET request. The headers argument is an optional
Lua table containing key-value pairs for request headers.
local html = http_get("https://api.example.com", {
["Authorization"] = "Bearer token123",
["X-Custom-Header"] = "my-value"
})
http_post(url, body, [headers])
Performs a synchronous HTTP POST request. By default, it sets the
Content-Type to application/x-www-form-urlencoded unless
overridden in the headers argument.
local response = http_post("https://api.example.com", '{"foo":"bar"}', {
["Content-Type"] = "application/json",
["Accept"] = "application/json"
})
json_encode(table)
Converts a Lua table or value into a JSON string. Handles nested tables, arrays,
booleans, and nulls automatically using Rust's high-performance serde_json
engine.
local payload = {
url = "https://example.com",
options = { waitUntil = "networkidle2" }
}
local body = json_encode(payload)
local resp = http_post("https://api.render.com", body, {["Content-Type"]="application/json"})
json_decode(string)
Parses a JSON string into a native Lua table. Returns nil and logs an error
if the JSON is malformed.
local data = json_decode('{"status": "ok", "count": 42}')
log("Status is: " .. data.status)
notify(title, body, [timeout_ms])
Sends a desktop notification immediately from Lua. This is a thin wrapper around
SpyWeb's native notification sender and can be used from any hook, including error
paths that never reach the normal notify stage.
Headless environments: desktop notifications are typically unavailable
on servers, cloud hosts, containers, and other headless environments. In those cases
notify(...) silently skips the notification and SpyWeb logs the underlying
OS/backend error.
function after_fetch(fetch_result)
if not fetch_result.ok then
notify("Network error", fetch_result.error, 10000)
return nil
end
return fetch_result
end
log(message)
Appends a message to hook.log inside the job's directory. This is the
recommended way to debug hooks without using external libraries.
function after_extract(items)
log("Extracted " .. #items .. " items from " .. request.url)
return items
end