From 0b028b29fcaa27d1a2cbe53f3e6b36f177c09092 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:48:00 +0000 Subject: [PATCH] Version packages --- .changeset/opaque-pagination-cursors.md | 49 ---------------------- clients/dart/CHANGELOG.md | 2 +- clients/dart/README.md | 2 +- clients/dart/pubspec.yaml | 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 | 56 +++++++++++++++++++++++++ packages/server/openapi.json | 2 +- packages/server/package.json | 2 +- packages/testkit/CHANGELOG.md | 8 ++++ packages/testkit/package.json | 2 +- 13 files changed, 81 insertions(+), 57 deletions(-) delete mode 100644 .changeset/opaque-pagination-cursors.md diff --git a/.changeset/opaque-pagination-cursors.md b/.changeset/opaque-pagination-cursors.md deleted file mode 100644 index cc51bd2..0000000 --- a/.changeset/opaque-pagination-cursors.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@eigeninteractive/server": minor ---- - -Pagination cursors are now opaque tokens, and empty query parameters are treated as absent. - -Every paged list could silently return nothing. A client that sent an optional -query parameter it had no value for produced `?cursor`, which arrives as the -empty string, and `z.coerce.number()` is `Number()`, so `""` became `0`. Zero is -a structurally valid integer cursor meaning "strictly older than the beginning of -time", so the request did not fail: the lobby, both `games/mine` buckets, the -friends list and every history screen returned `200` with an empty array. Nothing -was logged, because nothing had gone wrong as far as any layer could tell. - -Three changes, each closing a different part of it: - -- **`cursor` is now an opaque string** rather than a bare epoch-millisecond - integer, and paged responses carry a **`nextCursor`** that is null exactly when - the list is exhausted. There is no byte string that accidentally decodes to the - beginning of time, so the failure above cannot recur by construction. A cursor - that does not decode is a `400` with the new `invalidCursor` code. -- **The cursor carries the row id alongside the sort value**, so pages no longer - drop a row when two games share a timestamp - a limitation the previous - implementation documented but did not fix. The comparison is a SQLite row value - (`(sortKey, id) < (?, ?)`), which is planned against the same index. -- **Query parameters are parsed, never coerced.** This is the origin of the whole - failure, and it took four correct layers to become invisible. `Number(null)` is - `0`, so `z.coerce.number().int().min(0)` genuinely accepts null; the emitted - schema honestly reported `["integer", "null"]`; `openapi-generator` correctly - dropped its `if (x != null)` guard, because the API had declared null welcome; - dio rendered that null as a bare `?to=`; and the server coerced `""` back to - `0`. No library was misbehaving. Integer query parameters now parse from a - strict pattern, so they reject null and empty alike, which means the generated - client omits an absent parameter on its own and a malformed one is a loud `400` - rather than a plausible, wrong `200`. `?pool=` and `?to=` were failing the same - way and are fixed by the same change. - - A contract test now asserts that no query parameter in the published document - is nullable, which is the tell that was invisible in review: `.min(0)` was - nullable and `.min(1)` was not. - -Because `nextCursor` is now an answer rather than something a client infers from -a short page, callers no longer need to know how any list is sorted, and a final -page that happens to be exactly full no longer triggers one pointless request. - -Breaking for direct API consumers: `cursor` is a string, and the four paged -responses (`Lobby`, `MyGames`, `PlayerGames`, `FriendsGames`) gained a required -`nextCursor` field. The generated Dart client and `eigen_flutter` are updated to -match. diff --git a/clients/dart/CHANGELOG.md b/clients/dart/CHANGELOG.md index 6ceb158..73e53a2 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.3.1 here is 0.3.1 there. It has no changes of its own. +exactly: 0.4.0 here is 0.4.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 f705db6..c7c0d05 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.3.1 +- API version: 0.4.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 1986cf7..020a607 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.3.1 +version: 0.4.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/kernel/CHANGELOG.md b/packages/kernel/CHANGELOG.md index 0f77e10..b985baa 100644 --- a/packages/kernel/CHANGELOG.md +++ b/packages/kernel/CHANGELOG.md @@ -1,5 +1,12 @@ # @eigeninteractive/kernel +## 0.4.0 + +### Patch Changes + +- Updated dependencies []: + - @eigeninteractive/rules@0.4.0 + ## 0.3.1 ### Patch Changes diff --git a/packages/kernel/package.json b/packages/kernel/package.json index 31affc4..74ef827 100644 --- a/packages/kernel/package.json +++ b/packages/kernel/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/kernel", - "version": "0.3.1", + "version": "0.4.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 cbbc62c..6f7bbc1 100644 --- a/packages/rules/CHANGELOG.md +++ b/packages/rules/CHANGELOG.md @@ -1,5 +1,7 @@ # @eigeninteractive/rules +## 0.4.0 + ## 0.3.1 ## 0.3.0 diff --git a/packages/rules/package.json b/packages/rules/package.json index 475d6a2..5e3777a 100644 --- a/packages/rules/package.json +++ b/packages/rules/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/rules", - "version": "0.3.1", + "version": "0.4.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 d04ead2..ca1b0d9 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,61 @@ # @eigeninteractive/server +## 0.4.0 + +### Minor Changes + +- [#60](https://github.com/eigeninteractive/eigen-server/pull/60) [`4339dff`](https://github.com/eigeninteractive/eigen-server/commit/4339dff9c9f43bff9b9f482f6e9cd72f4f9476c7) Thanks [@seenu-k](https://github.com/seenu-k)! - Pagination cursors are now opaque tokens, and empty query parameters are treated as absent. + + Every paged list could silently return nothing. A client that sent an optional + query parameter it had no value for produced `?cursor`, which arrives as the + empty string, and `z.coerce.number()` is `Number()`, so `""` became `0`. Zero is + a structurally valid integer cursor meaning "strictly older than the beginning of + time", so the request did not fail: the lobby, both `games/mine` buckets, the + friends list and every history screen returned `200` with an empty array. Nothing + was logged, because nothing had gone wrong as far as any layer could tell. + + Three changes, each closing a different part of it: + + - **`cursor` is now an opaque string** rather than a bare epoch-millisecond + integer, and paged responses carry a **`nextCursor`** that is null exactly when + the list is exhausted. There is no byte string that accidentally decodes to the + beginning of time, so the failure above cannot recur by construction. A cursor + that does not decode is a `400` with the new `invalidCursor` code. + - **The cursor carries the row id alongside the sort value**, so pages no longer + drop a row when two games share a timestamp - a limitation the previous + implementation documented but did not fix. The comparison is a SQLite row value + (`(sortKey, id) < (?, ?)`), which is planned against the same index. + - **Query parameters are parsed, never coerced.** This is the origin of the whole + failure, and it took four correct layers to become invisible. `Number(null)` is + `0`, so `z.coerce.number().int().min(0)` genuinely accepts null; the emitted + schema honestly reported `["integer", "null"]`; `openapi-generator` correctly + dropped its `if (x != null)` guard, because the API had declared null welcome; + dio rendered that null as a bare `?to=`; and the server coerced `""` back to + `0`. No library was misbehaving. Integer query parameters now parse from a + strict pattern, so they reject null and empty alike, which means the generated + client omits an absent parameter on its own and a malformed one is a loud `400` + rather than a plausible, wrong `200`. `?pool=` and `?to=` were failing the same + way and are fixed by the same change. + + A contract test now asserts that no query parameter in the published document + is nullable, which is the tell that was invisible in review: `.min(0)` was + nullable and `.min(1)` was not. + + Because `nextCursor` is now an answer rather than something a client infers from + a short page, callers no longer need to know how any list is sorted, and a final + page that happens to be exactly full no longer triggers one pointless request. + + Breaking for direct API consumers: `cursor` is a string, and the four paged + responses (`Lobby`, `MyGames`, `PlayerGames`, `FriendsGames`) gained a required + `nextCursor` field. The generated Dart client and `eigen_flutter` are updated to + match. + +### Patch Changes + +- Updated dependencies []: + - @eigeninteractive/kernel@0.4.0 + - @eigeninteractive/rules@0.4.0 + ## 0.3.1 ### Patch Changes diff --git a/packages/server/openapi.json b/packages/server/openapi.json index 37434ed..85a92a5 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.3.1", + "version": "0.4.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 04afdcc..10cf864 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/server", - "version": "0.3.1", + "version": "0.4.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 afd6371..d4a9cae 100644 --- a/packages/testkit/CHANGELOG.md +++ b/packages/testkit/CHANGELOG.md @@ -1,5 +1,13 @@ # @eigeninteractive/testkit +## 0.4.0 + +### Patch Changes + +- Updated dependencies []: + - @eigeninteractive/kernel@0.4.0 + - @eigeninteractive/rules@0.4.0 + ## 0.3.1 ### Patch Changes diff --git a/packages/testkit/package.json b/packages/testkit/package.json index f799d26..a0a07e4 100644 --- a/packages/testkit/package.json +++ b/packages/testkit/package.json @@ -1,6 +1,6 @@ { "name": "@eigeninteractive/testkit", - "version": "0.3.1", + "version": "0.4.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",