Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Weft The book

Triggers and routes

A trigger is how a program starts without you. A message arriving, a schedule coming round, a form somebody fills in, a request hitting an address.

ask = TelegramReceiveMessage { account: telegram.access }

Wire it like anything else. What comes out of it is the event.

Nothing listens until you run weft activate. Go and read the three lifecycles for why that is its own step.

What a run started by a trigger covers

Everything downstream of the trigger that fired, plus whatever that work needs upstream, stopping the walk when it reaches another trigger.

Only the trigger that actually fired gets the event. Any others in the project close their outputs, so paths that needed them skip.

Two triggers can share the steps in the middle without their runs becoming one.

A trigger’s inputs are frozen

Whatever fed a trigger’s ports when you activated is what it uses on every firing. The nodes upstream do not run again per event.

Everything upstream of every trigger runs at activation as one program, so two steps that do not depend on each other run at the same time. Setup that several triggers share, like the schema all their tables live in, is written once and wired to each of them; two copies of create extension if not exists racing each other is how one of them fails on a duplicate key.

So after changing one, weft resync. Until then your listener carries on with what it registered. weft tells you when a bake is stale:

the code changed since trigger 'inbound' was last prepared, so its bake is
stale. Prepare it again: `weft bake` does it without listening, `weft activate`
does it and listens

Answering on a URL

Route claims an address and starts a run when somebody calls it.

post = Route -> (body: JsonDict, photo: File) {
  path: "cards"
  method: "POST"
}

reply = Reply {
  body: { "id": saved.id }
}

The caller is held open while your program works, and Reply is what answers them. For streaming an answer as it is produced, and for the Rust side, go and read talking to a live caller.

A path can capture: cards/{id} puts id where your node can read it.

Two routes that one call could reach, where neither is more specific, is an error. cards/count beside cards/{id} is fine, because the first is more specific. Two spellings that genuinely overlap are the route-overlap error, because a call arriving would have no defined answer.

The address is known before you activate: <dispatcher>/connect/<tenant>/<path>, where the tenant is local on your own machine.

If a page asks a route for something every few seconds (a status, a count), set recorded: false on the Route, or those calls fill weft executions with hundreds of runs. A run that succeeds or is cancelled then leaves nothing behind except what it cost. A run that fails is written down whole after the fact, so it lists and inspects exactly like a recorded one. The steps of an unrecorded run live in the worker’s memory while it runs, which has two consequences: it cannot wait (a timer, a form, anything that parks the run fails at the call naming recorded), and it is lost without a trace if the worker dies mid-call, since running it again could repeat what it had already done. With outlivesCaller on as well, it keeps running after the caller leaves, still in memory and still unable to wait.

Answering on a socket

Socket is the same idea for a WebSocket: the caller connects, your program runs, and the two talk until one of them stops.

Which triggers need a public address

Only the ones a provider pushes to.

The triggerNeeds a public address?
A timer or a scheduleNo
Something weft pollsNo
A socket weft dials out toNo
A form somebody fills inNo
A provider pushing events at youYes
A Route or Socket that strangers callYes

./setup.sh --public-url opens one. Go and read a public address.

What a trigger cannot do

Why
Be wired from another triggerA trigger’s inputs are frozen at setup, so another trigger’s output could never reach it
Have an infra node downstreamProvisioning happens before any event exists
Sit inside a loopIt registers once for the project, not once per item

All three are compile errors, named trigger-into-trigger, trigger-into-infra and trigger-in-loop.

Firing one by hand

While you are building, you do not want to expose anything:

weft bake
weft run --fire inbound='{"chatId":"123","text":"hello"}'

weft bake prepares every trigger’s settings and starts no listeners. --fire then runs exactly one, with an event you typed.

The payload is checked against what that trigger declared it fires with, both ways: a missing field is refused, and so is one you invented.

For writing a trigger of your own, go and read writing a trigger.