Skip to content

[Bug]: SelectProxy strips optionality, so .select() output types relation fields as always-present #12

Description

@RyanThurbon

Package version

1.1.1

Runtime

Bun

Reproduction

SelectProxy<T> in src/types/query.types.ts uses -? to strip the optional modifier from every key:

export type SelectProxy<T> = {
  [K in keyof T]-?: NonNullable<T[K]> extends Array<infer U>
    ? SelectProxy<NonNullable<U>> & { $all: SelectProxyLeaf }
    : NonNullable<T[K]> extends object
      ? SelectProxy<NonNullable<T[K]>> & { $all: SelectProxyLeaf }
      : NonNullable<T[K]>;
} & { $all: SelectProxyLeaf };

This lets you write game.cover.image_id inside .select() without a compile error, even though cover?: Cover is optional on the real Game model. This is fine for building the query. The problem is that the callback's return type is computed as ReturnType<TSelector> (InferShape<TProxy, TSelector>), and since the proxy has already erased optionality, the inferred output shape never reintroduces it. So .execute() returns objects typed as if every selected relation is guaranteed present, when IGDB can and does omit fields like cover at runtime.

Reproduction:

const games = await client.games
  .query()
  .select((game) => ({
    id: game.id,
    name: game.name,
    cover: { imageId: game.cover.image_id },
  }))
  .execute();

games[0].cover.imageId; // typed as string, no null check required

Hovering the inferred type of games shows:

const games: {
  id: number;
  name: string;
  cover: {
    imageId: string;
  };
}[]

cover should not be seen as guaranteed here.

Expected behavior

Expected:
cover (and any other optional relation) in the result type should be matching what IGDB can actually return.

Actual behavior

Actual:
cover is typed as { imageId: string }, always present, letting you access .imageId without a guard and hit a runtime crash on games with no cover.

Validation

No response

Checklist

  • I removed secrets, access tokens, and Twitch credentials from this report.
  • I checked the docs for the feature or method I am using.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions