Skip to content

Fix select narrowing of snake case models, distinctOn(), clearSelect() and jsonExtract() (#16) - #341

Merged
lehni merged 1 commit into
nextfrom
fix/typings-select-narrowing
Oct 12, 2026
Merged

lehni merged 1 commit into
nextfrom
fix/typings-select-narrowing

Conversation

@lehni

@lehni lehni commented Oct 12, 2026 •

Copy link
Copy Markdown

What

Four fixes to the narrowing of result rows in strict mode, so that the types match what objection selects:

  • Models with ColumnNameMapping: 'snakeCase' or 'upperSnakeCase': select() removes the columns that it doesn't select from the rows. It left the camelCase properties in, as it omitted the snake case column names from them.
  • distinctOn() takes columns like groupBy() and doesn't narrow the rows, as objection doesn't select its columns.
  • clearSelect() gives back the model rows, as objection selects all columns again. A select() after it starts over instead of adding to the cleared selection.
  • jsonExtract() adds its alias to the rows, of unknown type, and orderBy() takes it. It doesn't narrow the rows, as it isn't a select: alone it comes next to all columns, with select() (before or after it) next to the selected ones. Extractions without an alias, or several in one call, add properties of any name, like raw selections.
const rows = await Person.query().select('id').jsonExtract('address', '$.street', 'street');
rows[0].street; // unknown
rows[0].firstName; // Error: not selected

const all = await Person.query().distinctOn('lastName');
all[0].firstName; // string

const cleared = await Person.query().select('id').clearSelect();
cleared[0].firstName; // string

jsonExtract() aliases are tracked by their own exported brand, ExtractedBrand<A>, next to SelectedBrand<A>, so that a select() after jsonExtract() still narrows the rows. Both are type aliases, so that the rows keep the implicit index signature of object types. The extracted aliases stay through clearSelect() and withGraphFetched() / withGraphJoined(), as they do at runtime.

Stacked on #339.

Why

The rows claimed columns that weren't selected (snake case models, clearSelect().select()), or rejected columns that were (distinctOn(), clearSelect()), and jsonExtract() aliases weren't typed at all.

Breaking

  • In loose mode, distinctOn() takes column names, raw() and ref(), like groupBy(), instead of any selection.

Tests

  • tests/ts/strict/snake-case.ts: unselected multi-word columns are gone, for snakeCase and upperSnakeCase.
  • tests/ts/strict/select.ts: distinctOn(), clearSelect() (also with first() and a custom query builder) and jsonExtract() before and after select(), with orderBy(), clearSelect(), withGraphFetched() and withGraphJoined(), and its rows assignable to Record<string, unknown>.
  • tests/ts/strict/declarations.ts: an exported function returning a jsonExtract() query.
  • Each new expectation fails without the change.
  • pnpm test:typings with TypeScript 7, and tsc / tsc -p tests/ts/strict with TypeScript 5.8.3, pass.

Part of #53.

@lehni lehni added the 4.0 Breaking change, deferred to 4.0 (#53) label Oct 12, 2026
@lehni
lehni force-pushed the fix/typings-select-narrowing branch 3 times, most recently from 6dad49a to 7973963 Compare October 12, 2026 20:04
@lehni
lehni force-pushed the fix/typings-select-narrowing branch from 7973963 to a457142 Compare October 12, 2026 21:16
@lehni
lehni merged commit b0fe73c into next Oct 12, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 Breaking change, deferred to 4.0 (#53)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant