bijection/invoices.ts
nextDueDate returned and schedules
markOverdue. Nothing in this file mentions an interval.
How a wait works
dueis a read, not a poll. It is an internal query that returns the next boundary in epoch milliseconds, ornullwhen there is none. The rows it reads become the wait’s dependencies, so a committed write that changes its answer, such as a new invoice or a paid one, is what re-arms the wait. Between boundaries nothing runs.thenruns through the scheduler. When the boundary is reached, the engine schedulesthen, an internal mutation, in the same transaction that settles the wait. It then runs like any scheduled mutation, with the same retry behavior.- The wait survives restarts. It is stored in the database, like a scheduled function.
then so it checks what is actually due in its own transaction, as
markOverdue does, rather than trusting the instant that woke it. Another write
may have changed the data in between.
Declaring a wait
PassdefineWait an object with:
Each field takes a function reference, such as
internal.invoices.nextDueDate,
or its "module:function" name. Export the declaration from any module in your
bijection/ folder.
due and then must be different functions, and keys must be a third one.
When you deploy, each name must resolve to a function your deployment declares,
of the right kind, and internal. A deployment that names a missing or public
function is refused.
then is resolved again when the boundary arrives, against the code deployed
then. If a later deployment removed it, made it public or changed its kind, the
release is refused with WaitContinuationWithdrawn and the wait stays held
rather than running work you no longer declare.
One wait per key
A single declaration keeps one standing wait. To give each item its own boundary, add akeys query. It returns a list of stable string keys, and each
key becomes its own wait: its due runs in its own transaction and receives
{ key }, and so does then.
bijection/holds.ts
keysreturns at most 32 keys, each 1 to 128 bytes. A deployment holds at most 1024 standing waits in total.- One key’s reads never become another key’s dependencies, and a key whose
duethrows does not delay the others. - When
keysstops listing a key, that key’s wait is retired. Ifkeysitself throws, nothing is retired: a failure to list is not evidence that the items are gone. keysshould read routing only, never the protected data the waits guard.thenstill rechecks the item, because a key can be removed between the boundary and the continuation.
Limits
- A boundary is a JavaScript millisecond timestamp.
- The only condition a wait can express is “this instant has been reached”.
- A wait cannot be created at run time. Every wait comes from a deployed
defineWaitdeclaration, so a program cannot register a condition or a continuation the deployment does not declare.
Source barriers
A decision that reads data synced from an external source sometimes needs that data to be current or complete first. A source barrier states that requirement, and the read is refused when it does not hold, instead of returning a stale answer as if it were current. You pass a barrier toctx.sources.coverage, which reports what the engine can
prove about one installed source:
bijection/payouts.ts
When the barrier is not satisfied, the call throws an error with code
SourceBarrierUnsatisfied. The check runs in the same transaction as the reads
that follow it, and the coverage read is tracked like any other read, so a query
that was refused is re-evaluated when the source publishes.
Read on about synced sources in Integrations.