docs(kit): pin core 1.19.0; teach the public demo's module members #17

Merged
whitlocktech merged 1 commits from docs/core-1.19.0 into main 2026-10-26 06:02:56 -05:00
4 changed files with 84 additions and 4 deletions

View File

@@ -147,8 +147,9 @@ statement of your dependencies, and it makes a test double for it — see
## What you register
Nineteen calls, all synchronous and all listed in [§2.4][api]: fifteen
registrations, the two lifecycle hooks, and the two identity-link hooks. Five of the
Twenty calls, all synchronous and all listed in [§2.4][api]: fifteen
registrations, the two lifecycle hooks, the two identity-link hooks, and the demo's
seed hook. Five of the
registrations are the event system's, and [chapter 5](05-events.md) has those. What
is worth knowing is not their signatures but the model behind them.
@@ -537,6 +538,55 @@ api.onIdentityLinked(async ({ userId, provider, subject, displayName }) => {})
module made from it is yours to keep or remove.
- Neither hook can mint a session or change who is signed in.
### On a public demo site
A site can run as a public demo (`MODULE_API_VERSION` 1.18.0). Anybody signs in with
published logins, nothing destructive is allowed, and the site is wiped and seeded
again every couple of hours. It is the same image with one flag set,
`DEMO_MODE=true`. Core locks its own routes; yours are yours to declare, and three
members are for that. With the flag off none of them changes anything, except that
`demoLocks` is still validated.
```json
"demoLocks": [
{ "method": "PUT", "path": "/admin/mygame/servers/:id" },
{ "method": "DELETE", "path": "/admin/mygame/servers/:id" }
]
```
(The template has no write routes, so this is a shape rather than one of its files.)
- **`demoLocks` in `module.json` lists your write routes that the demo refuses.**
Core answers a match `403 { code: 'DEMO_LOCKED', message }` before your handler
runs, the same body it gives for its own locks, so the site's one toast explains
it. Writes only (`POST`, `PUT`, `PATCH`, `DELETE`), in your routers' own `:param`
syntax with an optional trailing `/*`, and under one of your declared `mounts`.
**It is validated on every boot, demo or not**, so a wrong entry fails on your
machine rather than only on the demo.
- **`ctx.demoMode` is for a lock a route list cannot express**, one that depends on
what is in the request rather than which URL it went to. The Rust module refuses a
console command by its first word, and a plugin's config file by the plugin that
owns it. Check it in your handler and answer with the same `403` body. It is also
how you hide the rare read that must never be shown on a demo, such as a file
holding credentials; `demoLocks` takes writes only.
- **`api.onDemoSeed(fn)` writes your rows into the demo's fresh database.** The seed
calls `fn(data, { accounts })`: `data` is your section of the fixture
(`modules.<id>`, with every `"$env:NAME"` string already replaced) and `accounts`
maps each demo username to its user id. Core never writes your tables. **Upsert,
never insert**: the seed may run more than once on one database, and core seeds
its own content only the first time but calls your hook on every run. A fixture section for a module with no hook fails the seed, and so does a
hook that throws.
- **Lock at the server, never in the UI.** A request sent with `curl`, or from an
old app build, must be refused exactly like a click. Your pages may explain a lock;
they must not hide or disable a control as the way of locking it.
- **Declaring `demoLocks` needs `coreApi` `^1.18.0`**, because an older core rejects
the unknown key. Using only the other two should declare it as well: an older core
has no `onDemoSeed`, and its `ctx.demoMode` is `undefined`, which reads as "not a
demo".
An event step the demo should refuse when it is saved is
[chapter 5](05-events.md)'s `demoRefuses`.
## The schema fragment
`template/server/db/schema.sql` is your tables. It is replayed **in full, at every

View File

@@ -150,6 +150,7 @@ api.registerEventActions([{
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
async demoRefuses(params) {}, // optional, 1.19.0
}])
```
@@ -189,6 +190,22 @@ event's public page and in the app.
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.
**`demoRefuses` keeps a step off a public demo** (MODULE_API 1.19.0). A demo site
([chapter 2](02-website-module.md)) lets anybody author an event, and some steps must
never be saved there: the Rust module refuses a console command that would stop the
server. Core asks `demoRefuses(params)` only with `DEMO_MODE` on, for every step,
when a definition is created, saved or published, with the step's checked params.
- **The answer.** A sentence refuses the whole write with `403 DEMO_LOCKED`, and the
author reads it as written, so phrase it for them: *"quit" is turned off on the
demo site.* `null` lets the step through.
- **It is a pure check.** No database, no game, nothing changed. A throw refuses too,
with core's own sentence, because a lock that cannot decide fails closed.
- **Check again in `perform()`**, behind `ctx.demoMode`. A version published before
your list grew is never asked again, so the save cannot be the only lock.
- **It needs `coreApi` `^1.19.0`.** An older core never asks it, and the save goes
through without a word.
**`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.

View File

@@ -1,10 +1,23 @@
{
"repo": "https://gitea.whitlocktech.com/RunicGateway/website.git",
"branch": "main",
"ref": "6890a74c7c4ad5690509e8c163e477d7f7b399e7",
"ref": "69998ec54854d46681def92985a2b299ee3582a6",
"why": [
"The core this kit is written against, pinned to a commit rather than a branch.",
"",
"2026-10-26: moved from 6890a74 (1.17.0) to 69998ec (1.19.0, website#224),",
"past 1.18.0 (website#223). Both are the public demo (DEMO_MODE.md), and the",
"chapters were read again for both versions:",
"- 1.18.0: demoLocks in module.json, ctx.demoMode and api.onDemoSeed",
" (chapter 2 gains a section, and its call count becomes twenty);",
"- 1.19.0: an event action's demoRefuses (chapter 5, a paragraph and a line",
" in the action listing).",
"The one other core change in the window (website#221) is a wording fix on",
"core's Teams panel and nothing a module sees. The template declares none of",
"the new members, so nothing it does changed meaning; module.json's coreApi",
"becomes ^1.19.0 for the equality check. The real core run was repeated at",
"this ref, with the template as the only module in the directory.",
"",
"2026-10-23: moved from 0a37a44 (1.12.0) to 6890a74 (1.17.0, website#220,",
"the cutover that brought Steam sign-in and the Rust last fixes to `main`),",
"past 1.13.0 to 1.16.0, which reached `main` together on 2026-10-22. The",

View File

@@ -2,7 +2,7 @@
"id": "examplegame",
"name": "Example Game",
"version": "0.1.0",
"coreApi": "^1.17.0",
"coreApi": "^1.19.0",
"server": "server/index.js",
"client": { "entry": "client/dist/entry.js" },
"schema": "server/db/schema.sql",