Skip to content

fix(foreign): keep missing elements and raw ids of a multi reference - #116

Merged
Upd4ting merged 2 commits into
mainfrom
fix/ant-1401
Nov 9, 2026
Merged

Upd4ting merged 2 commits into
mainfrom
fix/ant-1401

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Why

A module author with a multi reference (@Foreign(Item, undefined, true)) should get what docs/7.foreign-keys.md promises. A missing element should be null, and a noForeign route should answer the raw ids. Instead, get answered {} for a missing element, and noForeign answered objects keyed by character index, such as {"0":"i","1":"1"}.

Fixes #93

What changed

  • Validation.Unlock ran every element of a multi reference through fromDatabase, which copies its input with { ...raw }. That turned null into {} and a raw id string into a character-index object. It now converts only the elements that are objects.
  • Validation.ClearInternal checks only the first element to decide whether to post-process the array. So with noForeign, a stored [null, "<id>"] still sent the string id through toPlainData. Its array branch now post-processes only the elements that are objects.
  • In the verify-interface skill, the issue-93-multi-foreign journey failed on purpose until this fix. It is renamed foreign-multi, and its known-issue notes are removed.
flowchart LR
  Q[Get or List query] -->|noForeign unset| F[Foreign: Get per key, default null]
  Q -->|noForeign set| R[raw ids]
  F --> U[Unlock]
  R --> U
  U -->|element is an object| FD[fromDatabase]
  U -->|null or id string| K[kept as is]
  FD --> C[ClearInternal]
  K --> C
  C -->|element is an object| P[pluck and toPlainData]
  C -->|null or id string| K2[kept as is]
Loading

Proof

The test src/tests/components/foreign_multi.test.ts lands in its own commit, before the fix. Command: pnpm run build && pnpm exec ajs module test . -f dist/tests/components/foreign_multi.test.js.

Before the fix (main's src/internal/validation.ts):

    1) gets null for a missing element
    2) gets the raw ids with noForeign
    3) lists the raw ids with noForeign
  0 passing (137ms)
  3 failing
  1) Foreign multi references
      AssertionError: expected [ { …(2) }, {} ] to deeply equal [ { …(2) }, null ]
  2) Foreign multi references
      AssertionError: expected [ {}, {} ] to deeply equal [ …(2) ]
  3) Foreign multi references
      AssertionError: expected [ [ { '0': 'f', …(35) }, …(1) ], …(1) ] to have the same members as [ [ …(2) ], [ null, …(1) ] ]

After the fix:

    ✔ gets null for a missing element (123ms)
    ✔ gets the raw ids with noForeign
    ✔ lists the raw ids with noForeign
  3 passing (163ms)

The verify-interface run against the packed interface, ajs project dev and Docker MongoDB passes every journey of smoke, foreign, listing and route-options on the head rebased onto dd3ae1c. The one exception is issue-90-edit-index, which fails on purpose until #90. In foreign-multi, GET /orders/get?id=o3 (stored itemsAll: ["i1","ghost"]) answers "itemsAll":[{"_id":"i1","name":"Widget","price":9.99},null]. GET /orders/raw?id=o3 answers "itemsAll":["i1","ghost"].

pnpm run checks reports 9 passed and 0 failed.

Not in this PR

The review turned up two older bugs. Both show the same output on main:

@Upd4ting
Upd4ting merged commit f4872d3 into main Nov 9, 2026
7 checks passed
@Upd4ting
Upd4ting deleted the fix/ant-1401 branch November 9, 2026 15:02
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(foreign): a multi reference answers {} for a missing element and character-index objects with noForeign

1 participant