From a3bc3ee5cde7f1463f699e066fcb6a564c8bf47f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:37:39 +0000 Subject: [PATCH] Version packages --- .changeset/inspect-the-local-stores.md | 34 ------------------ .changeset/one-session-snapshot.md | 41 --------------------- .changeset/scaffold-ships-inspect.md | 7 ---- .changeset/watch-the-fixtures.md | 17 --------- clients/dart/CHANGELOG.md | 2 +- clients/dart/README.md | 2 +- clients/dart/pubspec.yaml | 2 +- packages/create-eigen-game/CHANGELOG.md | 22 ++++++++++++ packages/create-eigen-game/package.json | 2 +- packages/kernel/CHANGELOG.md | 7 ++++ packages/kernel/package.json | 2 +- packages/rules/CHANGELOG.md | 2 ++ packages/rules/package.json | 2 +- packages/server/CHANGELOG.md | 48 +++++++++++++++++++++++++ packages/server/openapi.json | 2 +- packages/server/package.json | 2 +- packages/testkit/CHANGELOG.md | 38 ++++++++++++++++++++ packages/testkit/package.json | 2 +- 18 files changed, 126 insertions(+), 108 deletions(-) delete mode 100644 .changeset/inspect-the-local-stores.md delete mode 100644 .changeset/one-session-snapshot.md delete mode 100644 .changeset/scaffold-ships-inspect.md delete mode 100644 .changeset/watch-the-fixtures.md diff --git a/.changeset/inspect-the-local-stores.md b/.changeset/inspect-the-local-stores.md deleted file mode 100644 index fb6cdac..0000000 --- a/.changeset/inspect-the-local-stores.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"@eigeninteractive/testkit": patch ---- - -New `eigen-inspect` command, for reading a Worker's local `.wrangler` state from the terminal while you develop a game. - -A game's truth is split across two stores on purpose. D1 is the index: discovery, history, ratings, identity. The game's own Durable Object is the session: the authoritative status, the roster, and the append-only transition log. That split is what makes the engine's read paths cheap, and it is also why a question like "the lobby filled, so why is nothing happening" cannot be answered by looking at one table. The D1 `games` row is a fire-and-forget display mirror, so it can lag; the DO is the only authority. - -`eigen-inspect game ` joins both, decodes every JSON column, prints the transition log as a timeline, and ends with the sentence you actually wanted: - -``` -Diagnosis ready and not started: 2/2 seats filled. Nothing happens until the - CREATOR calls POST /api/engine/games/{id}/start. A start is explicit - and creator-only; filling the lobby does not start a game. -``` - -When the two stores disagree it says so as `Mirror drift`, naming the DO as authoritative, so a lost mirror write reads as a lost mirror write rather than as a mystery. A finished game with surviving `outbox` rows is called out the same way, since that means the D1 finish apply has not landed and ratings are not published. - -The other commands are `games` (the index), `do` (every local Durable Object mapped back to its game id, which the hashed filename cannot tell you), `players`, `tables`, and `sql` for anything unmodelled. `--game ` points `tables` and `sql` at one game's Durable Object instead of D1; `--json` emits everything for a script or an agent. - -This complements rather than replaces Wrangler's own local browser, which is `e` in `wrangler dev` (or `/cdn-cgi/explorer`) and is the better tool for generic table browsing. `eigen-inspect` is the one that knows what a game is, and it needs no dev server, browser, or account. - -The reader is also exported as a library at `@eigeninteractive/testkit/local-state`, so a test or a script can assert against local state directly: - -```ts -import { LocalStore } from "@eigeninteractive/testkit/local-state"; - -const store = LocalStore.open(); -const game = store.game("ABC123"); -expect(game?.meta?.status).toBe("active"); -store.close(); -``` - -Every database is opened read-only, so all of this is safe to run against a live `wrangler dev`. It reads them through Node's built-in `node:sqlite`, so it adds no dependency, needs no native build, and nothing new reaches your Worker's runtime dependencies. It needs Node 24, which the engine already requires. diff --git a/.changeset/one-session-snapshot.md b/.changeset/one-session-snapshot.md deleted file mode 100644 index 1f6f0a9..0000000 --- a/.changeset/one-session-snapshot.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -"@eigeninteractive/server": minor ---- - -The socket now carries one message, a complete per-seat **session snapshot**, and every accepted command answers with the same value. `roster`, `sync` and `frame` as separate message kinds are gone. - -This is a breaking wire change, and it fixes a class of bug rather than an instance. The old shape asked a client to assemble one session out of four sources, each carrying a different slice of the truth under a different versioning scheme: an HTTP summary that carried status, unversioned `roster` messages that stopped at the lobby, a `sync` on mid-game open that carried only a version, and versioned `frame` messages that carried neither. Read that list for "how does a client learn the game became `active`" and the answer is that it cannot. Nothing on the live channel said so. A creator sitting in a full waiting room never saw a Start button, and every seat stayed in the waiting room after the game began, until the screen was disposed and re-entered. - -```jsonc -{ - "type": "session", - "seq": 7, // monotonic per game, incremented by EVERY commit - "gameId": "…", "shortCode": "ABC123", "access": "private", - "schemaVersion": 1, "config": { … }, - "turnSeconds": null, "budgetSeconds": null, "incrementSeconds": null, - "rated": false, "ratingPool": null, - "minPlayers": 2, "maxPlayers": 2, "createdBy": "…", - "status": "active", // what moves - "players": [ … ], - "version": 3, // null in the lobby - "frame": { … } // THIS seat's observation; null in the lobby -} -``` - -It is sent on socket open at **every** status, and after every committed change, lobby or state. Being complete makes it idempotent: a client that applies the newest one it has seen is correct however many it missed, so there is nothing to reconstruct and no second channel to correlate against. It carries the immutable header as well as the moving parts because a game screen must not need a second source; the D1 summary stays what it always was, the index behind lists and discovery. - -Hidden information is safe by construction rather than by vigilance. The envelope is projected per seat before it is sent, `frame` is only ever the receiving principal's own seat's view resolved against the roster at send time, and a socket holding no seat gets the envelope with `frame: null`, which is how a viewer learns the game started at all. - -**`seq` is the new ordering key**, because `version` cannot order a lobby change that has none. Apply a snapshot when `seq` exceeds the one you hold; that single comparison resolves a command response racing its own socket push, a duplicate delivery, and a reconnect that missed nothing. One clause is added, and it is a property of the state machine rather than an exception: a `finished` or `aborted` snapshot applies whatever its `seq`, because those statuses are absorbing and the abort teardown drops the storage the counter lives in. - -Gap recovery is unchanged and still animates. `GET /games/{id}/frames?from=&to=` still fills a version jump, and the missing span is played through **under the previous envelope**, so only the real snapshot may move `status`, `players` and `seq`. A client that missed a finish therefore animates the moves and then shows the outcome, instead of displaying a finished game while mid-game moves play. - -New and changed surface: - -- `GET /games/{id}/session` returns the snapshot over HTTP, for the paths that have no socket. -- `LobbyAccepted`, `Joined` and `Roster` are gone. `CommandAccepted` and `SoloStarted` are now `{ session }`. -- `RosterSnapshot` and `SyncMessage` are removed from the exported protocol types; `SessionSnapshot` replaces them. `FrameMessage` remains, as the payload inside a snapshot and the element type of the range fetch. -- `GameStub` gains `session(gameId, userId)`. -- The Durable Object's `meta` table gains `seq`, `short_code` and `outcomes`. Pre-1.0 and with no games depending on the engine, these are edited into the init migration in place rather than added as a second one, so **local `.wrangler` state predating this must be deleted**. Outcomes are retained rather than drained by the finish compaction, because they are kernel output that no transition row holds, so a cold open of a finished game could not otherwise be answered from the DO alone. - -One incidental fix rides along: `frame` was typed non-nullable in the generated Dart client, because a nullable `$ref` lost its null branch on the way into the OpenAPI document. The schema now spells it as a union, so it emits `anyOf: [$ref, {type: null}]` and generates as `Frame?`. A lobby session would have crashed the old typing on arrival. diff --git a/.changeset/scaffold-ships-inspect.md b/.changeset/scaffold-ships-inspect.md deleted file mode 100644 index a3f9bfa..0000000 --- a/.changeset/scaffold-ships-inspect.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"create-eigen-game": patch ---- - -A scaffolded game now ships the local inspector wired up: `pnpm inspect games`, `pnpm inspect game `, `pnpm inspect do`. - -One line makes it work. The `inspect` script points at `eigen-inspect`, which the testkit already installs as a `devDependency`, and which reads the local `.wrangler` databases through Node's built-in `node:sqlite`, so there is nothing to install and no native build to approve. diff --git a/.changeset/watch-the-fixtures.md b/.changeset/watch-the-fixtures.md deleted file mode 100644 index 0167902..0000000 --- a/.changeset/watch-the-fixtures.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"create-eigen-game": patch ---- - -`test:watch` now reruns when you edit only a fixture. It did not before, and the failure was silent: you saved a case, nothing happened, and the suite you were watching kept reporting the last result. - -The cause is that `twinFixtureTests` reads fixtures with `readFileSync` at collect time, so they are not in Vite's module graph. Vitest looks a changed path up in that graph, finds nothing, checks whether the file is itself a test file, and reruns nothing. Fixture JSON was never even watched, since Vitest only adds paths outside the graph to its watcher when a trigger glob names them. - -A scaffold therefore now ships `server/vitest.config.mts` carrying one option: - -```ts -forceRerunTriggers: [...configDefaults.forceRerunTriggers, "**/src/module/fixtures/**/*.json"], -``` - -This is what `forceRerunTriggers` is for. Vitest's own defaults use it for `package.json` and the config files, which are read outside the graph for the same reason, and it appends `setupFiles` and `snapshotSerializers` to the same list. - -Two things worth keeping if you edit the file. Spread the defaults rather than replacing them, because config resolution is a shallow merge and a bare array quietly stops manifest and config edits from triggering a rerun. And leave the trigger off any `@cloudflare/vitest-pool-workers` project you add later: a match reruns the whole suite, which costs nothing for pure-Node tests and is not free for workerd, where a fixture cannot change the result anyway. diff --git a/clients/dart/CHANGELOG.md b/clients/dart/CHANGELOG.md index aef8c85..736edd5 100644 --- a/clients/dart/CHANGELOG.md +++ b/clients/dart/CHANGELOG.md @@ -2,7 +2,7 @@ This package is generated from the Eigen engine's OpenAPI specification and its version tracks [`@eigeninteractive/server`](https://www.npmjs.com/package/@eigeninteractive/server) -exactly: 0.2.5 here is 0.2.5 there. It has no changes of its own. +exactly: 0.3.0 here is 0.3.0 there. It has no changes of its own. See the engine's changelog: diff --git a/clients/dart/README.md b/clients/dart/README.md index 22cf343..af52a2d 100644 --- a/clients/dart/README.md +++ b/clients/dart/README.md @@ -16,7 +16,7 @@ The server-authoritative game engine API. Client routes (`/api/engine/_*`) requi This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 0.2.5 +- API version: 0.3.0 - Generator version: 7.17.0 - Build package: org.openapitools.codegen.languages.DartDioClientCodegen diff --git a/clients/dart/pubspec.yaml b/clients/dart/pubspec.yaml index 39c39f5..a54a54f 100644 --- a/clients/dart/pubspec.yaml +++ b/clients/dart/pubspec.yaml @@ -15,7 +15,7 @@ description: >- # `version:` is stamped by the generation script from @eigeninteractive/server's # package.json. The wire contract is the server's, so the client carries the # server's version. Do not edit it by hand. -version: 0.2.5 +version: 0.3.0 homepage: https://eigeninteractive.com repository: https://github.com/eigeninteractive/eigen-server issue_tracker: https://github.com/eigeninteractive/eigen-server/issues diff --git a/packages/create-eigen-game/CHANGELOG.md b/packages/create-eigen-game/CHANGELOG.md index 7aa549e..7570eb4 100644 --- a/packages/create-eigen-game/CHANGELOG.md +++ b/packages/create-eigen-game/CHANGELOG.md @@ -1,5 +1,27 @@ # create-eigen-game +## 0.9.3 + +### Patch Changes + +- [#51](https://github.com/eigeninteractive/eigen-server/pull/51) [`e140da5`](https://github.com/eigeninteractive/eigen-server/commit/e140da5ee5f916a4a763b17c9dc338cb836484b6) Thanks [@seenu-k](https://github.com/seenu-k)! - A scaffolded game now ships the local inspector wired up: `pnpm inspect games`, `pnpm inspect game `, `pnpm inspect do`. + + One line makes it work. The `inspect` script points at `eigen-inspect`, which the testkit already installs as a `devDependency`, and which reads the local `.wrangler` databases through Node's built-in `node:sqlite`, so there is nothing to install and no native build to approve. + +- [#51](https://github.com/eigeninteractive/eigen-server/pull/51) [`8c7d54e`](https://github.com/eigeninteractive/eigen-server/commit/8c7d54eefd3920a4e0839064b15bc4e2a0e769bf) Thanks [@seenu-k](https://github.com/seenu-k)! - `test:watch` now reruns when you edit only a fixture. It did not before, and the failure was silent: you saved a case, nothing happened, and the suite you were watching kept reporting the last result. + + The cause is that `twinFixtureTests` reads fixtures with `readFileSync` at collect time, so they are not in Vite's module graph. Vitest looks a changed path up in that graph, finds nothing, checks whether the file is itself a test file, and reruns nothing. Fixture JSON was never even watched, since Vitest only adds paths outside the graph to its watcher when a trigger glob names them. + + A scaffold therefore now ships `server/vitest.config.mts` carrying one option: + + ```ts + forceRerunTriggers: [...configDefaults.forceRerunTriggers, "**/src/module/fixtures/**/*.json"], + ``` + + This is what `forceRerunTriggers` is for. Vitest's own defaults use it for `package.json` and the config files, which are read outside the graph for the same reason, and it appends `setupFiles` and `snapshotSerializers` to the same list. + + Two things worth keeping if you edit the file. Spread the defaults rather than replacing them, because config resolution is a shallow merge and a bare array quietly stops manifest and config edits from triggering a rerun. And leave the trigger off any `@cloudflare/vitest-pool-workers` project you add later: a match reruns the whole suite, which costs nothing for pure-Node tests and is not free for workerd, where a fixture cannot change the result anyway. + ## 0.9.2 ### Patch Changes diff --git a/packages/create-eigen-game/package.json b/packages/create-eigen-game/package.json index f7b6a0d..a6daf90 100644 --- a/packages/create-eigen-game/package.json +++ b/packages/create-eigen-game/package.json @@ -1,6 +1,6 @@ { "name": "create-eigen-game", - "version": "0.9.2", + "version": "0.9.3", "license": "MIT", "description": "Scaffold an EigenInteractive game Worker and Flutter app without cloning the engine repositories.", "type": "module", diff --git a/packages/kernel/CHANGELOG.md b/packages/kernel/CHANGELOG.md index bae50bf..7c04b8c 100644 --- a/packages/kernel/CHANGELOG.md +++ b/packages/kernel/CHANGELOG.md @@ -1,5 +1,12 @@ # @eigeninteractive/kernel +## 0.3.0 + +### Patch Changes + +- Updated dependencies []: + - @eigeninteractive/rules@0.3.0 + ## 0.2.5 ### Patch Changes diff --git a/packages/kernel/package.json b/packages/kernel/package.json index d6730f6..5f755c4 100644 --- a/packages/kernel/package.json +++ b/packages/kernel/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/kernel", - "version": "0.2.5", + "version": "0.3.0", "license": "MIT", "description": "The pure decision core of the EigenInteractive engine: given a game, state, roster and intent it returns a commit plan or a rejection. No I/O, no platform APIs.", "main": "./dist/index.js", diff --git a/packages/rules/CHANGELOG.md b/packages/rules/CHANGELOG.md index 5f8cac2..8ed6f06 100644 --- a/packages/rules/CHANGELOG.md +++ b/packages/rules/CHANGELOG.md @@ -1,5 +1,7 @@ # @eigeninteractive/rules +## 0.3.0 + ## 0.2.5 ### Patch Changes diff --git a/packages/rules/package.json b/packages/rules/package.json index c74501a..34c7eeb 100644 --- a/packages/rules/package.json +++ b/packages/rules/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/rules", - "version": "0.2.5", + "version": "0.3.0", "license": "MIT", "description": "The game-rules contract for the EigenInteractive engine: GameRules, GameModule, the hooks, and the Envelope/Observation types. Pure types, zero engine dependencies.", "main": "./dist/index.js", diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index 341be61..54aabca 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,53 @@ # @eigeninteractive/server +## 0.3.0 + +### Minor Changes + +- [#51](https://github.com/eigeninteractive/eigen-server/pull/51) [`d0b28f4`](https://github.com/eigeninteractive/eigen-server/commit/d0b28f4397a3363027b469982451a87b282bbd25) Thanks [@seenu-k](https://github.com/seenu-k)! - The socket now carries one message, a complete per-seat **session snapshot**, and every accepted command answers with the same value. `roster`, `sync` and `frame` as separate message kinds are gone. + + This is a breaking wire change, and it fixes a class of bug rather than an instance. The old shape asked a client to assemble one session out of four sources, each carrying a different slice of the truth under a different versioning scheme: an HTTP summary that carried status, unversioned `roster` messages that stopped at the lobby, a `sync` on mid-game open that carried only a version, and versioned `frame` messages that carried neither. Read that list for "how does a client learn the game became `active`" and the answer is that it cannot. Nothing on the live channel said so. A creator sitting in a full waiting room never saw a Start button, and every seat stayed in the waiting room after the game began, until the screen was disposed and re-entered. + + ```jsonc + { + "type": "session", + "seq": 7, // monotonic per game, incremented by EVERY commit + "gameId": "…", "shortCode": "ABC123", "access": "private", + "schemaVersion": 1, "config": { … }, + "turnSeconds": null, "budgetSeconds": null, "incrementSeconds": null, + "rated": false, "ratingPool": null, + "minPlayers": 2, "maxPlayers": 2, "createdBy": "…", + "status": "active", // what moves + "players": [ … ], + "version": 3, // null in the lobby + "frame": { … } // THIS seat's observation; null in the lobby + } + ``` + + It is sent on socket open at **every** status, and after every committed change, lobby or state. Being complete makes it idempotent: a client that applies the newest one it has seen is correct however many it missed, so there is nothing to reconstruct and no second channel to correlate against. It carries the immutable header as well as the moving parts because a game screen must not need a second source; the D1 summary stays what it always was, the index behind lists and discovery. + + Hidden information is safe by construction rather than by vigilance. The envelope is projected per seat before it is sent, `frame` is only ever the receiving principal's own seat's view resolved against the roster at send time, and a socket holding no seat gets the envelope with `frame: null`, which is how a viewer learns the game started at all. + + **`seq` is the new ordering key**, because `version` cannot order a lobby change that has none. Apply a snapshot when `seq` exceeds the one you hold; that single comparison resolves a command response racing its own socket push, a duplicate delivery, and a reconnect that missed nothing. One clause is added, and it is a property of the state machine rather than an exception: a `finished` or `aborted` snapshot applies whatever its `seq`, because those statuses are absorbing and the abort teardown drops the storage the counter lives in. + + Gap recovery is unchanged and still animates. `GET /games/{id}/frames?from=&to=` still fills a version jump, and the missing span is played through **under the previous envelope**, so only the real snapshot may move `status`, `players` and `seq`. A client that missed a finish therefore animates the moves and then shows the outcome, instead of displaying a finished game while mid-game moves play. + + New and changed surface: + + - `GET /games/{id}/session` returns the snapshot over HTTP, for the paths that have no socket. + - `LobbyAccepted`, `Joined` and `Roster` are gone. `CommandAccepted` and `SoloStarted` are now `{ session }`. + - `RosterSnapshot` and `SyncMessage` are removed from the exported protocol types; `SessionSnapshot` replaces them. `FrameMessage` remains, as the payload inside a snapshot and the element type of the range fetch. + - `GameStub` gains `session(gameId, userId)`. + - The Durable Object's `meta` table gains `seq`, `short_code` and `outcomes`. Pre-1.0 and with no games depending on the engine, these are edited into the init migration in place rather than added as a second one, so **local `.wrangler` state predating this must be deleted**. Outcomes are retained rather than drained by the finish compaction, because they are kernel output that no transition row holds, so a cold open of a finished game could not otherwise be answered from the DO alone. + + One incidental fix rides along: `frame` was typed non-nullable in the generated Dart client, because a nullable `$ref` lost its null branch on the way into the OpenAPI document. The schema now spells it as a union, so it emits `anyOf: [$ref, {type: null}]` and generates as `Frame?`. A lobby session would have crashed the old typing on arrival. + +### Patch Changes + +- Updated dependencies []: + - @eigeninteractive/kernel@0.3.0 + - @eigeninteractive/rules@0.3.0 + ## 0.2.5 ### Patch Changes diff --git a/packages/server/openapi.json b/packages/server/openapi.json index 742c02b..274d571 100644 --- a/packages/server/openapi.json +++ b/packages/server/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "EigenInteractive Engine API", - "version": "0.2.5", + "version": "0.3.0", "description": "The server-authoritative game engine API. Client routes (`/api/engine/*`) require a Firebase ID token; the external-bot webhook (`/api/bot/action`) is HMAC-authenticated." }, "tags": [ diff --git a/packages/server/package.json b/packages/server/package.json index eef3b8a..fc9370c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/server", - "version": "0.2.5", + "version": "0.3.0", "license": "MIT", "description": "The deployable half of the EigenInteractive engine: the createEngine Worker factory, the BaseGameDO Durable Object base class, the D1 schema and appliers, auth, bots and push.", "main": "./dist/index.js", diff --git a/packages/testkit/CHANGELOG.md b/packages/testkit/CHANGELOG.md index 778b4ca..79b4bbb 100644 --- a/packages/testkit/CHANGELOG.md +++ b/packages/testkit/CHANGELOG.md @@ -1,5 +1,43 @@ # @eigeninteractive/testkit +## 0.3.0 + +### Patch Changes + +- [#51](https://github.com/eigeninteractive/eigen-server/pull/51) [`e140da5`](https://github.com/eigeninteractive/eigen-server/commit/e140da5ee5f916a4a763b17c9dc338cb836484b6) Thanks [@seenu-k](https://github.com/seenu-k)! - New `eigen-inspect` command, for reading a Worker's local `.wrangler` state from the terminal while you develop a game. + + A game's truth is split across two stores on purpose. D1 is the index: discovery, history, ratings, identity. The game's own Durable Object is the session: the authoritative status, the roster, and the append-only transition log. That split is what makes the engine's read paths cheap, and it is also why a question like "the lobby filled, so why is nothing happening" cannot be answered by looking at one table. The D1 `games` row is a fire-and-forget display mirror, so it can lag; the DO is the only authority. + + `eigen-inspect game ` joins both, decodes every JSON column, prints the transition log as a timeline, and ends with the sentence you actually wanted: + + ``` + Diagnosis ready and not started: 2/2 seats filled. Nothing happens until the + CREATOR calls POST /api/engine/games/{id}/start. A start is explicit + and creator-only; filling the lobby does not start a game. + ``` + + When the two stores disagree it says so as `Mirror drift`, naming the DO as authoritative, so a lost mirror write reads as a lost mirror write rather than as a mystery. A finished game with surviving `outbox` rows is called out the same way, since that means the D1 finish apply has not landed and ratings are not published. + + The other commands are `games` (the index), `do` (every local Durable Object mapped back to its game id, which the hashed filename cannot tell you), `players`, `tables`, and `sql` for anything unmodelled. `--game ` points `tables` and `sql` at one game's Durable Object instead of D1; `--json` emits everything for a script or an agent. + + This complements rather than replaces Wrangler's own local browser, which is `e` in `wrangler dev` (or `/cdn-cgi/explorer`) and is the better tool for generic table browsing. `eigen-inspect` is the one that knows what a game is, and it needs no dev server, browser, or account. + + The reader is also exported as a library at `@eigeninteractive/testkit/local-state`, so a test or a script can assert against local state directly: + + ```ts + import { LocalStore } from "@eigeninteractive/testkit/local-state"; + + const store = LocalStore.open(); + const game = store.game("ABC123"); + expect(game?.meta?.status).toBe("active"); + store.close(); + ``` + + Every database is opened read-only, so all of this is safe to run against a live `wrangler dev`. It reads them through Node's built-in `node:sqlite`, so it adds no dependency, needs no native build, and nothing new reaches your Worker's runtime dependencies. It needs Node 24, which the engine already requires. +- Updated dependencies []: + - @eigeninteractive/kernel@0.3.0 + - @eigeninteractive/rules@0.3.0 + ## 0.2.5 ### Patch Changes diff --git a/packages/testkit/package.json b/packages/testkit/package.json index 2709fbf..37b5691 100644 --- a/packages/testkit/package.json +++ b/packages/testkit/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/testkit", - "version": "0.2.5", + "version": "0.3.0", "license": "MIT", "description": "Test helpers for games built on the EigenInteractive engine: drive rules through the real kernel and run shared twin fixtures, with no Worker, database or network.", "main": "./dist/index.js",