The Operation Context
An order above a configured limit is refused; approving one debits a wallet kept by another component; a voucher needs an id and an expiry time. Everything an operation needs from outside itself comes through its last argument, the context:
// components/store.orders/operations/approve.ts
import type { Context, State } from '../types/index.d.ts'
export async function transition (input: null, entry: State, context: Context) {
if (entry.total > context.configuration.limit)
return new Error('LIMIT_EXCEEDED')
const reply = await context.remote.billing.wallets.debit({
query: { id: entry.customer },
input: { amount: entry.total, ref: entry.id }
})
if (reply instanceof Error)
return reply
entry.status = 'approved'
return entry
} approve is the transition Create a Component writes for store.orders. limit is the setting Add Configuration declares,
and billing.wallets.debit the operation Refuse a Request writes: it takes an amount and a ref, what the money is for.
$ npm run restart
$ npx toa call store.orders.approve "{ query: { id: '5b1ad5d8a4c54e1dbb2a4a0b6e1f0c11' } }" It is the last argument whatever the operation’s type:
export async function transition (input: Input, entry: State, context: Context) { /* … */ }
export async function computation (input: Input, context: Context) { /* … */ } Events and guards are given a context too.
§Calls
| Where | ||
|---|---|---|
context.local.<operation>(request) | Calls an operation of the same component. | Call Operations and Select State |
context.remote.<namespace>.<name>.<operation>(request) | Calls an operation of another component; context.remote.<name>.<operation>(request) where it has no namespace. | Call Operations and Select State |
§Ids, time and randomness
context.id() | A new id, of the same form as an object’s. |
context.now() | Milliseconds since the epoch, as Date.now(). |
context.random() | A number in [0, 1), as Math.random(). |
Use these instead of Date.now(), Math.random() and id generators. An operation written
as a multi-step workflow is given the same answers when it is resumed; a
value taken elsewhere would differ between the two runs.
§Where it runs
| Where | ||
|---|---|---|
context.name | The name of the application, as in the Context. | The Context |
context.env | The environment it was deployed as. | Environments |
context.region | The rank of the region; 0 unless there are several. | Regions |
context.instance | The name of this process. | Keep Something in a Process |
Prefer configuration to branching on context.env.
§What extensions add
Some members are there for every component, others once the manifest declares the key.
| Manifest key | What it is for | Where | |
|---|---|---|---|
context.configuration | configuration: | Values set per environment. | Add Configuration, Configuration |
context.fetch() | — | HTTP requests to other systems. | Call an External Service |
context.delay(), context.delay.cancel() | cadence: | Calling an operation later. | Do Something Later |
context.schedule(), context.schedule.cancel() | cadence: | Calling an operation on a calendar of an item’s own. | Run Work on a Schedule |
context.state | state: | Something kept in the process. | Keep Something in a Process |
context.stash | stash: | A shared cache. | Stash |
context.atom | — | Locks and decisions shared by replicas. | Shared Decisions |
context.storages | storages: | Files. | Storages |
context.logs | — | Log entries. | Logs |
context.span | — | Spans of a trace. | Traces |
context.metrics | telemetry: | Metrics of your own. | Metrics |
context.aspects.<name>() | the extension’s key | What an extension of your own provides. | Own Extensions |
How a component declares an extension is in Extensions.
§Rules
Read from the context when you need a value; do not copy values out of it. The context object lives as long as the component, and what it holds can change: configuration may be updated while the component runs.
Reach the outside world through the context. Reading a file that ships with the component and using a pure library are fine. Opening connections, reading environment variables and keeping module-level state are not; see the rules for operation code.
Context in the model explains why there is one door.