diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b17664..eb8c6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +- The agent guide now lists the import path, the call, and the driver of each + SQL database adapter, and it shows how to cancel a reminder with + `unschedule()`. Agents in the Track B evaluation invented the names + `solid-objects/database/postgres` and `this.cancelReminder()`. + ## 1.0.0 - 2026-10-13 - Version 1.0 freezes the public API under semantic versioning. diff --git a/docs/agents.md b/docs/agents.md index ab10094..8faea75 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -95,6 +95,14 @@ const runtime = configure({ await runtime.install() ``` +Each SQL database has its own adapter. Import the adapter from its own path: + +| Database | Import | Call | Driver | +| ---------- | ---------------------------------------------------------------- | ---------------------------------- | ------------------------------- | +| SQLite | `import { sqlite } from "solid-objects/database/sqlite"` | `sqlite({ path: "app.sqlite3" })` | None. Node.js has `node:sqlite` | +| PostgreSQL | `import { postgresql } from "solid-objects/database/postgresql"` | `postgresql({ connectionString })` | `npm install pg` | +| MySQL | `import { mysql } from "solid-objects/database/mysql"` | `mysql({ connectionString })` | `npm install mysql2` | + Every authorization callback denies by default. Do step 5 before you call an actor. [Configuration](configuration.md) lists each option and database adapter. @@ -199,7 +207,8 @@ Obey these rules in actor code: - Public methods are ordered operations. Each one takes no argument or one object argument. - Use `this.schedule({ at, key })` for delayed work. A new `schedule` with the - same key moves the alarm. + same key moves the alarm. `this.unschedule("expire", { key: buyer })` cancels + it. - 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" })`. @@ -221,6 +230,8 @@ Avoid these mistakes: | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `this.schedule({ at, key })` with no operation after it | `this.schedule({ at, key }).expire({ buyer })`. A `schedule()` call with no operation after it fails the turn with `ScheduleWithoutOperation` | | `this.reject("room full")` or `this.reject(code, message)` | `this.reject("room_full", { message: "The room is full" })` | +| `postgres()` from `solid-objects/database/postgres` | `postgresql({ connectionString })` from `solid-objects/database/postgresql`. Step 4 lists each adapter | +| A guessed cancel method, such as `this.cancelReminder()` | `this.unschedule("expire", { key: buyer })`. `this.unscheduleAll("expire")` cancels every key of one operation | | `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 | diff --git a/test/agent-guide.test.ts b/test/agent-guide.test.ts new file mode 100644 index 0000000..1ec22d1 --- /dev/null +++ b/test/agent-guide.test.ts @@ -0,0 +1,25 @@ +import { readFileSync } from "node:fs" +import { describe, expect, it } from "vitest" + +const guide = readFileSync(new URL("../docs/agents.md", import.meta.url), "utf8") +const packageDefinition = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +) as { exports: Record } + +describe("agent guide", () => { + it("names the import path of each server database adapter", () => { + const adapters = Object.keys(packageDefinition.exports).filter( + (path) => path.startsWith("./database/") && !path.endsWith("-wasm"), + ) + expect(adapters).toEqual( + expect.arrayContaining(["./database/sqlite", "./database/postgresql", "./database/mysql"]), + ) + for (const adapter of adapters) { + expect(guide, adapter).toContain(`"solid-objects${adapter.slice(1)}"`) + } + }) + + it("shows how to cancel a reminder", () => { + expect(guide).toContain('this.unschedule("expire", { key: buyer })') + }) +})