|
|
|
|
@@ -148,6 +148,7 @@ api.registerEventActions([{
|
|
|
|
|
async perform({ runId, stepId, idempotencyKey, scope, params, actor, verify }) {},
|
|
|
|
|
async revert({ runId, resources, idempotencyKey }) {}, // required iff 'ledger'
|
|
|
|
|
async reconcile({ runId, resources }) {}, // optional
|
|
|
|
|
async progress({ runId, stepId, idempotencyKey, params, resources, viewer }) {}, // optional, 1.12.0
|
|
|
|
|
}])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
@@ -166,6 +167,20 @@ second code path is a dry run of the second path. Validate everything you can
|
|
|
|
|
reach without writing, then stop. Answering `{ ok: true }` unconditionally makes
|
|
|
|
|
the dry run worthless in the one situation it exists for.
|
|
|
|
|
|
|
|
|
|
**`progress` is read by strangers, so it answers for one reader** (MODULE_API 1.12.0).
|
|
|
|
|
While a run is live, core asks it how its step stands and puts the answer on the
|
|
|
|
|
event's public page and in the app.
|
|
|
|
|
|
|
|
|
|
- **The answer.** `{ label, left, of }` ("Beacons: 3 of 8 lit") or `{ label, percent }`
|
|
|
|
|
("The warden: 62%"), or `null`. Core renders the text and reads nothing out of it.
|
|
|
|
|
- **Who is reading.** `viewer.userId` is the account reading the page, or null for a
|
|
|
|
|
stranger. Answer `null` to anybody your own pages would hide that information from:
|
|
|
|
|
a public page must never say what your map does not.
|
|
|
|
|
- **The cost.** It is a read, it may be asked every few seconds for as long as the
|
|
|
|
|
run lasts, and core gives it 2 s. Answer from something you already hold or
|
|
|
|
|
already cache, not from a fresh round trip to the game per reader.
|
|
|
|
|
- **Failure.** A throw or a late answer costs a line, never the page.
|
|
|
|
|
|
|
|
|
|
**`example` is required on every param, optional ones included.** It is the
|
|
|
|
|
authoring form's placeholder. It is one word at declaration time and it is
|
|
|
|
|
unreconstructable afterwards by anybody who did not write the action.
|
|
|
|
|
@@ -294,6 +309,12 @@ And when you do answer: **anything that is not an explicit
|
|
|
|
|
read as "it is gone". A resource you report missing becomes `orphaned` rather than
|
|
|
|
|
`reverted`, because nobody asked for it to go.
|
|
|
|
|
|
|
|
|
|
**A thing your game ends on its own schedule is not orphaned** (MODULE_API 1.11.0).
|
|
|
|
|
If the game removes something at its own deadline (a zone that closes when its
|
|
|
|
|
time is up), call `ctx.events.expired({ kind, ref })` when it does. Core then marks the
|
|
|
|
|
row `expired`: finished as planned, never taken back at teardown. Left to `reconcile`,
|
|
|
|
|
the same thing would turn up `orphaned` and read as something that went wrong.
|
|
|
|
|
|
|
|
|
|
**You say WHEN to reconcile, because core cannot.** Core has no concept of the game
|
|
|
|
|
being up — it sees `{ ok: false, retry: true }` and cannot tell a wedged sidecar
|
|
|
|
|
from a game that rebooted and lost everything an event made. So it asks once, at
|
|
|
|
|
@@ -355,8 +376,9 @@ Four things a second module's author reaches for and should not.
|
|
|
|
|
5. **One action that ledgers**, with `revert` and the four traps above. This is
|
|
|
|
|
where the work is, and where the damage is.
|
|
|
|
|
6. **`reconcile`**, and the boot-id watch that calls `ctx.events.reconcile()`.
|
|
|
|
|
Last, because it is the only one whose absence is merely a lower standard
|
|
|
|
|
rather than a broken promise.
|
|
|
|
|
Its absence is merely a lower standard rather than a broken promise.
|
|
|
|
|
7. **`progress`**, if a visitor would want to watch the step happen. It is the only
|
|
|
|
|
one an outsider reads, so build it once the ledger it counts from is right.
|
|
|
|
|
|
|
|
|
|
Then read your own `revert` again, and ask what it answers when the game is down.
|
|
|
|
|
|
|
|
|
|
|