Skip to content

fix(selection): accept a getAll key proxy of any SelectionKey type - #80

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

Upd4ting merged 2 commits into
mainfrom
fix/ant-1404

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Nov 8, 2026

Copy link
Copy Markdown
Member

Why

A module author keys a computed subquery on a row field, for example table.getAll(doc.key("name"), "name").count(). Once that proxy is bound to a variable, or typed explicitly as in row.key<{ _id: string }, "_id">("_id"), it is a ValueProxy<string>. getAll rejected it (TS2345), so the author had to restate the key type with .cast<string | number | boolean>().

ValueProxy is invariant in its value type, so ValueProxy<string> is not assignable to ValueProxy<SelectionKey>, the only proxy IndexKeys accepted.

Fixes #68

What changes

  • IndexKeys takes a type parameter, IndexKeys<K extends SelectionKey = SelectionKey>. The default keeps the old type, so adapters that type stage.args[0] as IndexKeys (@antelopejs/mongodb, @antelopejs/postgres) see no change, and QueryStageMap["getAll"] stays the same.
  • Selection.getAll gains a second overload, getAll<K extends SelectionKey>(keys: IndexKeys<K>, index?). The original signature stays first, so every call that compiled before resolves exactly as it did, including a list of doc.key(...) calls whose type comes from the expected argument. The implementation and the recorded stage are unchanged, and no type assertion is added.
  • The conformance suite gains "Do with Subquery Keyed on a Typed Field" in src/tests/do_operations.test.ts. It binds doc.key("name") to a const and counts getAll(name, "name"). It needs nothing new from adapters: @antelopejs/mongodb 1.6.0 passes it.

SelectionKey is still exported from the /common subpath only, not the root. A caller no longer needs to name it.

flowchart LR
  P["ValueProxy&lt;string&gt;<br/>(row field)"] --> O1{"getAll(keys: IndexKeys)"}
  O1 -- "invariant: not ValueProxy&lt;SelectionKey&gt;" --> O2{"getAll&lt;K extends SelectionKey&gt;(keys: IndexKeys&lt;K&gt;)"}
  O2 -- "K = string" --> I["implementation<br/>QueryStage('getAll', { index }, keys)"]
  O1 -- "literals, SelectionKey proxies,<br/>contextually typed doc.key()" --> I
  I --> S["recorded stage args: [keys: IndexKeys]<br/>(unchanged for adapters)"]
Loading

Proof

The test commit 6f69421 lands before the fix. Its build on top of main's code:

src/tests/do_operations.test.ts(276,46): error TS2345: Argument of type 'ValueProxy<string>' is not assignable to parameter of type 'IndexKeys'.
 ELIFECYCLE  Command failed with exit code 2.

With the fix (pnpm exec ajs module test . -f dist/tests/do_operations.test.js):

    ✔ Do with Subquery Keyed on a Typed Field
  21 passing (45ms)
  • pnpm run checks passes all 8 checks. pnpm test:staged-query passes, and tsc -p docs/tsconfig.json is clean.
  • antelopejs-verify passes all 29 journeys over the 18 features its plan maps, and no formal check covers the diff.
  • Two adversarial reviewers compared the branch against main with type probes. Every call main accepts still compiles. Proxies of an object, array, Date, null, undefined or unknown are still rejected.

Attention

  • One-way doors. antelopejs-verify doors flags the format and contract doors on src/staged-query/selection.ts and src/staged-query/stages.ts. The contract door also covers src/tests/do_operations.test.ts. The contract change is additive: one overload, and a type parameter whose default is the old type.
  • Still rejected, as on main. A key list that mixes proxies of different key types, such as [ValueProxy<string>, ValueProxy<number>], still fails, because TypeScript picks one K for the list. One index holds one key type, and a field typed string | number gives a single ValueProxy<string | number>, which this accepts.

@Upd4ting
Upd4ting merged commit 6b963fe into main Nov 9, 2026
8 checks passed
@Upd4ting
Upd4ting deleted the fix/ant-1404 branch November 9, 2026 14:41
Upd4ting added a commit that referenced this pull request Nov 10, 2026
## Why

Put interface-database on `@antelopejs/tooling-configs` 0.5.1 and make
it pass cleanly, not just carry the new version. 0.5.1 adds
`antelopejs/no-read-then-insert`, relaxes `no-inherited-key-lookup`, and
fixes the declared-surface walk that overflowed the stack on this
package (tooling-configs#290). Task ANT-1857, parent ANT-1840.

## What changed

- `@antelopejs/tooling-configs` is declared `^0.5.1`, and the lockfile
resolves 0.5.1.
- `pnpm run sync` rewrote `AGENTS.md` (new rule row, three sync notes
now enforced by `--check`) and
`.agents/skills/verify-interface/SKILL.md` (`verdict --judgement`).
- `features/smoke.md` no longer blames the stack overflow.
`every-function` and `minor-bump` now run here but cannot pass, so the
gotcha states the two tool-side reasons and `through-core` stays the
smoke journey.

No lint finding came up. `no-read-then-insert` and
`no-unknown-parameters` report nothing here. The repo has no
`no-inherited-key-lookup` suppression for #291 to retire.
`guardrails.suppressions.json` is unchanged. No published surface
changes.

## Proof

Before. Main with `^0.5.1` installed, before `pnpm run sync`:

```
$ pnpm run sync:check
Drift: AGENTS.md; run antelopejs-sync
Drift: .agents/skills/verify-interface/SKILL.md; run antelopejs-sync
 ELIFECYCLE  Command failed with exit code 1.
exit 1

$ pnpm run checks
[sync:check]
Drift: AGENTS.md; run antelopejs-sync
Drift: .agents/skills/verify-interface/SKILL.md; run antelopejs-sync
 ELIFECYCLE  Command failed with exit code 1.
Checks: 10 passed, 1 failed.
 ELIFECYCLE  Command failed with exit code 1.
exit 1
```

After, at this head:

```
$ pnpm run sync:check
Guardrails are in sync.
exit 0

$ pnpm run checks
Checks: 11 passed, 0 failed.
exit 0
```

Verify layer. `antelopejs-verify journey <run> --plan` ran all 20
features (the diff touches `package.json`) and passed 31 of 31 journeys.

## Attention

Run by hand at this head with `smoke` naming them, `every-function` and
`minor-bump` both fail:

- `every-function` fails 635 checks. 577 are class prototype methods
(`.:ValueProxy.prototype.gt`, and so on). The `.d.ts` walk lists them
and the consumer's runtime walk does not (it returns 65 top-level
members), so each reads as "declared but not exported". The other 58 are
plain classes and functions that would need `interface.calls` hooks.
- `minor-bump` reports 21 added and 21 removed `__@asyncIterator@<n>`
members, whose ids differ between the published 0.3.1 build and head
(1858 against 3457). Each signature also embeds its referenced type
closure, so #80's real `getAll<K extends SelectionKey>` overload marks
630 functions as changed. With symbol ids normalised, 42 still differ,
all through that overload. That part is genuine: under the rule, the
next release of this package needs at least 0.4.0.
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.

getAll rejects a ValueProxy key typed as a row field, so a computed subquery keyed on a row field needs a cast

1 participant