Repository navigation
fix(list): leave out getters the route's pluck mode does not list - #117
Merged
Merged
Conversation
A list route evaluates every readable getter, whatever its pluck mode, so a getter only the default mode lists answers with the fields that mode plucked missing.
Upd4ting
added a commit
that referenced
this pull request
Nov 9, 2026
## Why
A route registered as `DefaultRoutes.WithOptions(DefaultRoutes.Edit, {
index: "slug" })` should edit the row the index finds. It found that
row, then wrote the update to the row whose primary key equals `id`.
Usually no such row exists, so the edit was silently lost and the route
still answered 200 with the old row. Fixes #90.
## What changes
- `editRow` in `src/index.ts` takes the primary key (`_id`) of the row
it found, asserts it is a string, and uses it for the update and for the
read-back. Reading back by primary key also gives the right answer when
the edit changes the indexed field itself. The old read through the
index returned the unchanged row in that case.
- Without `index` the behaviour is the same as before, because the found
row's `_id` is the requested `id`.
- `docs/3.routes.md` describes the fixed behaviour in the Edit options
table.
- The verify journey `issue-90-edit-index` is renamed from "fails on
purpose" to a guard, and `features/README.md` no longer lists it as
failing.
```mermaid
flowchart LR
R["PUT /edit?id=hello (index: slug)"] --> G["Query.Get(table, id, index)"]
G -->|stored row, _id a1| W["WriteProperties + Lock"]
W --> U_old["before: table.get('hello').update"]
W --> U_new["after: table.get(stored._id).update"]
U_new --> Rd["readRow by stored._id"]
```
## Proof
`src/tests/index/edit_index.test.ts` drives the route against the
in-memory MongoDB. One row's `_id` equals the indexed value, so an
implementation that writes to the primary-key row fails the test.
Before, with `main`'s `src/index.ts`:
```text
Edit with the index option
[2026-11-08 03:46:25] [ERROR] HTTP 500 PUT /edit-index/edit?id=hello: After applying the update, the (immutable) field '_id' was found to have been altered to _id: "a1"
1) updates the row the index finds, not the row whose primary key is the id
2) answers with the edited row when the edit changes the indexed field
0 passing (93ms)
2 failing
1) Edit with the index option
updates the row the index finds, not the row whose primary key is the id:
AssertionError: expected 500 to equal 200
+ expected - actual
-500
+200
2) Edit with the index option
answers with the edited row when the edit changes the indexed field:
AssertionError: expected { slug: 'rename-me', title: 'Before' } to have deep property 'slug' of 'renamed', but got 'rename-me'
+ expected - actual
-rename-me
+renamed
```
After:
```text
Edit with the index option
✔ updates the row the index finds, not the row whose primary key is the id (53ms)
✔ answers with the edited row when the edit changes the indexed field
2 passing (152ms)
```
`pnpm run checks` prints `Checks: 8 passed, 0 failed.`
`antelopejs-verify` at `0d0e4a1`, before the rebases, every feature
`plan` lists: `route-options/issue-90-edit-index` passes. The only
failures were `issue-92-getter-mode` and `issue-93-multi-foreign`, which
#117 and #116 have since fixed on `main`. With #90 fixed too,
`features/README.md` no longer lists any journey that fails on purpose.
After the rebase onto `f4872d3`, `pnpm run checks` prints `Checks: 9
passed, 0 failed.` and both new tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A list route with a non-default
pluckModeshould leave out a getter that its mode does not list. Today it evaluates every readable getter, so a module author who declares@Listable(["name", "email"]) get fullName()for the default mode getsfullName: "Alice <undefined>"on adetailedroute that never pluckedemail.Fixes #92
What changes
listRows(src/index.ts) passes the route's pluck mode toReadProperties. It passes no mode when the route setsnoPluck, which reads every field.ReadProperties(src/internal/read-properties.ts) skips a getter when itslistablehas no own entry for that mode. An entry with no fields, as@Listable([])writes, still lists the getter.Query.ReadProperties(obj, meta, dbData, action, onlyList)and thegetroute behave as before.issue-92-getter-modeas a passing journey. Its docs no longer say it fails on purpose.flowchart LR R["GET /people/brief<br/>pluckMode: brief"] --> P["pluck fields of 'brief'"] P --> RP["ReadProperties(listMode: 'brief')"] RP --> G{"getter listable<br/>has 'brief'?"} G -- yes --> E["evaluate getter"] G -- no --> S["leave it out"] N["noPluck route"] --> A["ReadProperties(no listMode)<br/>evaluates every getter"]Proof
src/tests/index/list_getter_modes.test.ts(commit af72592). The controller listsfullNameandkind(@Listable([])) in the default mode andinitialindetailed, with a third route that setsnoPluck.Before the fix (
af72592):After the fix (
caeb4c6):pnpm run checksat 568eb14:Checks: 8 passed, 0 failed.The verify-interface skill, run on the packed interface at caeb4c6:
listing/issue-92-getter-modepasses.GET /people/briefanswers{"results":[{"_id":"u1"},{"_id":"u2"},{"_id":"u3"},{"_id":"u4"},{"_id":"u5"}],"total":5,"offset":0,"limit":10}.listing/pluck-modespasses.GET /people/liststill answers"fullName":"Alice <alice@example.com>"for each row.issue-90-edit-index(fix(routes): Edit with theindexoption updates the row whose primary key equalsid#90) andissue-93-multi-foreign(fix(foreign): a multi reference answers{}for a missing element and character-index objects withnoForeign#93). The skill's README lists both as failing on head for their own issues.Attention
@Listableis now left out of list routes, asdocs/6.listable.mdsays for any field without the decorator. Before this change, list rows carried it.@Listable(false, mode)on a getter still evaluates it in that mode, as before. The metadata storesfalseand[]the same way, so telling them apart needs a change tosetListable. That is out of scope here.Controller(path, Base)also changes the base controller's routes.