diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f5586..425e91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,21 @@ # Changelog -## Unreleased - +## 0.17.2 - 2026-10-08 + +- The README names the agent guide at the start of Installation, and the agent + guide says that a reminder changes state only when it runs under + `runtime.run(signal)`, so a query must not compute expiry from the clock. +- Correct `docs/agents.md`: `reject` takes a code and an options object, + `this.reject(code, { message })`, not `this.reject(code, message)`. +- `docs/agents.md` now says that a worker process runs only the actor classes + that it knows. For an unregistered class, actor setup fails with + `UnknownActorType`, the worker reports `solid_objects.activation.failed`, + and the message returns to the queue without a counted attempt. Nothing + prints without an `instrumentation` callback. Register each class before + `runtime.run(signal)`. + It also tells agents to install the current release, puts the + deny-by-default reminder beside the install step, and lists the API + mistakes found in agent-written code. - Claim the Context7 library: `context7.json` now carries the library `url` and the maintainer `public_key`. - Add the Context7 refresh workflow. A push to `main` that changes the README, diff --git a/README.md b/README.md index a35ef91..203640b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ subscriptions. Start with the [Cloudflare example](examples/cloudflare/README.md ## Installation Solid Objects is ESM-only and requires Node.js 24.4 or newer. TypeScript users -need TypeScript 5.9 or newer. +need TypeScript 5.9 or newer. Coding agents should follow the +[agent guide](docs/agents.md), which gives each step in order. ```bash npm install solid-objects diff --git a/docs/agents.md b/docs/agents.md index c05a38e..898dea6 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -68,6 +68,10 @@ The quickstart runs 25 concurrent calls against one identity on a temporary SQLite database. It proves that no update is lost. Add `pg` or `mysql2` when you use PostgreSQL or MySQL. +Install the current release. `npm install solid-objects` selects it. Do not +pin a version that you remember from earlier work; the API changed between +releases. The current version is on . + Create the runtime and its tables at startup: ```typescript @@ -81,7 +85,9 @@ const runtime = configure({ await runtime.install() ``` -[Configuration](configuration.md) lists each option and database adapter. +Every authorization callback denies by default. Do step 5 before you call an +actor. [Configuration](configuration.md) lists each option and database +adapter. ## 5. Authorize @@ -184,13 +190,26 @@ Obey these rules in actor code: object argument. - Use `this.schedule({ at, key })` for delayed work. A new `schedule` with the same key moves the alarm. -- Use `this.reject(code, message)` for a business rule failure that must not - retry. +- Use `this.reject(code, { message })` for a business rule failure that must + not retry. The second argument is an object, for example + `this.reject("room_full", { message: "The room is full" })`. - Do not write application tables directly from an operation. Use `this.commitAction()` for a short write in the same database. - Do not call an external API in an operation. Use `this.emit()` and an effect handler. - Write each operation so that it can run again. Delivery is at least once. +- A reminder changes state only when it runs, and it runs only while a process + calls `runtime.run(signal)`. Do not compute expiry from the clock in a query; + read the state that the reminder committed. + +Avoid these mistakes: + +| Mistake | Correct form | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `this.schedule({ at, key })` with no operation after it | `this.schedule({ at, key }).expire({ buyer })`. `schedule()` stages a reminder only when you call an operation on its result | +| `this.reject("room full")` or `this.reject(code, message)` | `this.reject("room_full", { message: "The room is full" })` | +| `registerEffect(name, (context, argumentsValue) => ...)` | `registerEffect(name, (argumentsValue, context) => ...)`. The staged arguments come first | +| A worker process that never names the actor class | `runtime.register(TicketSale)` before `runtime.run(signal)`. See step 7 | [State and lifecycle](state-and-lifecycle.md) and the [public API](api.md) give the full rules. @@ -206,13 +225,25 @@ runs in the caller. These features need a process that calls - Messages to other actors. - Realtime broadcasts. +The worker process runs only the actor classes that it knows. For a message +whose class is not registered, actor setup fails with `UnknownActorType`. The +worker reports `solid_objects.activation.failed` through instrumentation, +returns the message to the queue without counting an attempt, and tries again. +Nothing prints unless the application sets an `instrumentation` callback, so +the message seems to wait with no error. Register each class before `run()`: + ```typescript +runtime.register(TicketSale) + const controller = new AbortController() process.once("SIGTERM", () => controller.abort()) await runtime.run(controller.signal) await runtime.close() ``` +`TicketSale.ref(...)` also registers the class, so a script that calls `ref()` +before `run()`, like the direct call above, already works. + The packaged `solid-objects start` command does the same for a runtime that `solid-objects.config.js` exports. When no process runs, committed work waits in SQL and runs later. [Operations](operations.md) covers roles and shutdown. diff --git a/docs/parity.md b/docs/parity.md index 5324f70..05a6749 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -4,7 +4,7 @@ This ledger tracks capability parity with the Ruby `solid_objects` gem. Parity preserves a capability and its correctness or security boundary. It does not copy a Rails API into Node. -Reference: Ruby `solid_objects` 0.17.1. The JavaScript package began at the Ruby +Reference: Ruby `solid_objects` 0.17.2. The JavaScript package began at the Ruby design's `0.12` capability generation; that version number did not imply earlier JavaScript releases. diff --git a/docs/virtual-actors.md b/docs/virtual-actors.md index 895d9b4..091e138 100644 --- a/docs/virtual-actors.md +++ b/docs/virtual-actors.md @@ -113,6 +113,10 @@ buyer gets the ticket. The hold and its reminder commit in one transaction. If the worker stops, the reminder stays in `tickets.sqlite3`. It runs when the worker starts again. +The `work` branch can run the reminder because `TicketSale.ref(...)` registers +the class first. A separate worker process must call +`runtime.register(TicketSale)` before `runtime.run(signal)`. + The authorization callbacks above allow every caller. Use them only for a local example. The package release check runs this file against the packed npm tarball. The source is [`examples/ticket-sale.ts`](../examples/ticket-sale.ts). diff --git a/package.json b/package.json index 201836e..04c1d58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-objects", - "version": "0.17.1", + "version": "0.17.2", "description": "SQL-backed virtual actor library for TypeScript and Node.js, with durable state, ordered operations, and automatic activation on SQLite, PostgreSQL, or MySQL", "type": "module", "license": "MIT", diff --git a/src/version.ts b/src/version.ts index f92d2a2..91b8da3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.17.1" +export const VERSION = "0.17.2"