From 36f29a9cace203a9d2a330848fe1c3133dae5b89 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Thu, 29 Oct 2026 22:59:09 -0500 Subject: [PATCH] docs(kit): pin core 1.20.0; teach onUserDeleting Moves ci/core-ref.json from 69998ec (1.19.0) to 7bd93b8 (1.20.0, website#232, account deletion). Chapter 2 gains "When a member deletes their account": the hook runs before the row goes, a cascade is not an unlink, it cannot stop the deletion, so a module that talks to a game needs a record and a retry. Its call count becomes twenty-one. The demo section now says core refuses multipart bodies and data: URIs (website#229) on module routes without them being listed. The template's coreApi becomes ^1.20.0 for the equality check. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- book/02-website-module.md | 44 ++++++++++++++++++++++++++++++++++++--- ci/core-ref.json | 16 +++++++++++++- template/module.json | 2 +- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/book/02-website-module.md b/book/02-website-module.md index 9b4b96c..3b3b5b3 100644 --- a/book/02-website-module.md +++ b/book/02-website-module.md @@ -147,9 +147,9 @@ statement of your dependencies, and it makes a test double for it — see ## What you register -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 +Twenty-one calls, all synchronous and all listed in [§2.4][api]: fifteen +registrations, the two lifecycle hooks, the two identity-link hooks, the demo's +seed hook, and the account-deletion 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. @@ -538,6 +538,39 @@ 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. +### When a member deletes their account + +A member can delete their own site account, and staff can delete one from the +admin panel (`MODULE_API_VERSION` 1.20.0). Core removes the row, and every table +with a foreign key to it follows: `ON DELETE CASCADE` for what was theirs alone, +`SET NULL` for what they made that others still read. Your module hears about it +once, first: + +```js +api.onUserDeleting(async ({ userId, deletedBy }) => {}) // deletedBy: 'self' | 'staff' +``` + +- **It runs before the row goes**, so you can still read what the person holds. +- **A cascade is not an unlink.** If your game holds anything for the person, such + as a link it reports back on every login or a grant it enforces, undo it here + through your module's own unlink, the same call a player's own unlink makes. That + way the game sees exactly what it would see if they had unlinked by hand. Clean up + any data you keep without a foreign key here too. +- **It cannot stop the deletion.** A throw is logged, and so is no answer within + 10 s, and the account is deleted anyway, because a person who asks to be deleted + must be. So a module that talks to a game needs **a record of what is left and a + retry**: write down what you are about to undo *before* the first call, keep what + the game could not take (it was down, or it refused), and try again when the game + comes back. That record cannot have a foreign key to `users`, because it outlives + the user it names. +- **Your hook keeps running past the 10 s.** Core stops waiting, not your code, so + the user may already be gone when your last call finishes. Anything you write + afterwards must not need the row: an activity entry, for instance, names the id in + its `detail`, not its `userId`. +- **Every module that holds per-person game state should register it.** Declaring it + needs `coreApi` `^1.20.0`, because an older core has no `api.onUserDeleting`, and + calling a missing function throws during `register()`. + ### On a public demo site A site can run as a public demo (`MODULE_API_VERSION` 1.18.0). Anybody signs in with @@ -563,6 +596,11 @@ members are for that. With the flag off none of them changes anything, except th 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. +- **Two refusals reach your routes without being listed.** On a demo, core refuses + any request with a multipart body, and any write whose JSON body carries a + `data:` URI in any string (a pasted image is an upload by another name), with the + same `403` body. An upload route of yours is locked on a demo whether or not + `demoLocks` names it. - **`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 diff --git a/ci/core-ref.json b/ci/core-ref.json index 0ecf04a..b94128d 100644 --- a/ci/core-ref.json +++ b/ci/core-ref.json @@ -1,10 +1,24 @@ { "repo": "https://gitea.whitlocktech.com/RunicGateway/website.git", "branch": "main", - "ref": "69998ec54854d46681def92985a2b299ee3582a6", + "ref": "7bd93b8bb0da734a642587f256d10c523ca39bea", "why": [ "The core this kit is written against, pinned to a commit rather than a branch.", "", + "2026-10-30: moved from 69998ec (1.19.0) to 7bd93b8 (1.20.0, website#232,", + "account deletion, ACCOUNT_DELETION.md). Chapter 2 was read again:", + "- 1.20.0: api.onUserDeleting (chapter 2 gains a section, and its call count", + " becomes twenty-one).", + "The other core changes in the window (website#225-#231) are the public", + "demo's deploy, seed and locks. One reaches a module: website#229 refuses, on", + "a demo, any write whose JSON body carries a data: URI, beside the multipart", + "refusal 1.18.0 already had, and neither needs listing in demoLocks. Chapter", + "2's demo section now says so; it had not taught the multipart one either.", + "The template declares none of the new members, so nothing it does changed", + "meaning; module.json's coreApi becomes ^1.20.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-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:", diff --git a/template/module.json b/template/module.json index a1d1723..060967f 100644 --- a/template/module.json +++ b/template/module.json @@ -2,7 +2,7 @@ "id": "examplegame", "name": "Example Game", "version": "0.1.0", - "coreApi": "^1.19.0", + "coreApi": "^1.20.0", "server": "server/index.js", "client": { "entry": "client/dist/entry.js" }, "schema": "server/db/schema.sql", -- 2.49.1