Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a generic function with narrowed type in ExpressionBuilder #1007

Closed
joeledwardson opened this issue May 25, 2024 · 3 comments
Closed
Labels
duplicate This issue or pull request already exists question Further information is requested typescript Related to Typescript

Comments

@joeledwardson
Copy link

I am trying to use kysely to narrow a type in a function so I can create a generalise function that accepts string column types (see filterString). But typescript is not recognising the value as narrowed to a string type and throwing an error.

I've taken the database tables from the docs for an example.

How can I create a generic function that will allow kysely to recognise a narrowed type of a reference expression so I can call it will a string?

import { ExpressionBuilder } from "kysely";
import { ExtractTypeFromReferenceExpression } from "kysely";
import { SimpleReferenceExpression } from "kysely";
import {
  ColumnType,
  Generated,
  Insertable,
  JSONColumnType,
  Selectable,
  Updateable,
} from "kysely";

export interface Database {
  person: PersonTable;
  pet: PetTable;
}

// This interface describes the `person` table to Kysely. Table
// interfaces should only be used in the `Database` type above
// and never as a result type of a query!. See the `Person`,
// `NewPerson` and `PersonUpdate` types below.
export interface PersonTable {
  // Columns that are generated by the database should be marked
  // using the `Generated` type. This way they are automatically
  // made optional in inserts and updates.
  id: Generated<number>;

  first_name: string;
  gender: "man" | "woman" | "other";

  // If the column is nullable in the database, make its type nullable.
  // Don't use optional properties. Optionality is always determined
  // automatically by Kysely.
  last_name: string | null;

  // You can specify a different type for each operation (select, insert and
  // update) using the `ColumnType<SelectType, InsertType, UpdateType>`
  // wrapper. Here we define a column `created_at` that is selected as
  // a `Date`, can optionally be provided as a `string` in inserts and
  // can never be updated:
  created_at: ColumnType<Date, string | undefined, never>;

  // You can specify JSON columns using the `JSONColumnType` wrapper.
  // It is a shorthand for `ColumnType<T, string, string>`, where T
  // is the type of the JSON object/array retrieved from the database,
  // and the insert and update types are always `string` since you're
  // always stringifying insert/update values.
  metadata: JSONColumnType<{
    login_at: string;
    ip: string | null;
    agent: string | null;
    plan: "free" | "premium";
  }>;
}

// You should not use the table schema interfaces directly. Instead, you should
// use the `Selectable`, `Insertable` and `Updateable` wrappers. These wrappers
// make sure that the correct types are used in each operation.
//
// Most of the time you should trust the type inference and not use explicit
// types at all. These types can be useful when typing function arguments.
export type Person = Selectable<PersonTable>;
export type NewPerson = Insertable<PersonTable>;
export type PersonUpdate = Updateable<PersonTable>;

export interface PetTable {
  id: Generated<number>;
  name: string;
  owner_id: number;
  species: "dog" | "cat";
}

export type Pet = Selectable<PetTable>;
export type NewPet = Insertable<PetTable>;
export type PetUpdate = Updateable<PetTable>;

type PersonEB = ExpressionBuilder<Database, "person">;

// create dummy expression builder
const builder = null as any as PersonEB;

type RE = SimpleReferenceExpression<Database, "person">;


type IsStringColumn<T extends RE> = ExtractTypeFromReferenceExpression<
  Database,
  "person",
  T
> extends string
  ? T
  : never;

type A = IsStringColumn<"first_name">

export function filterString<T extends RE, B extends IsStringColumn<T>>(
  key: B,
  value: string
) {
  /**
   * VALUE is errornous here
   */
  return builder(key, "=", value);
}
@koskimas
Copy link
Member

Answered about a billion times.

@koskimas koskimas added the duplicate This issue or pull request already exists label May 25, 2024
@igalklebanov igalklebanov added question Further information is requested typescript Related to Typescript labels May 25, 2024
@joeledwardson
Copy link
Author

where?

I did have a look through the docs and issues before writing this and found many asking similar questions regarding type narrowing and filtering but none that match what I'm asking here.

#310 and #335 are referring to the returned type of data when a where query is applied, not creating a generic function for columns of a specific type

#380 again talks about $narrowType which is referring to the returned type

@kysely-org kysely-org locked as resolved and limited conversation to collaborators May 25, 2024
@koskimas
Copy link
Member

koskimas commented May 26, 2024

Here's just a few I found using a the word generic. There are A LOT more here and in discord. The gist is always the same: extremely strict (Kysely) and generics don't work well together. Strict expects exact types to be known and generic is the opposite.

#997
#995
#670
#642
#496
#385
#122
#109
#43

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
duplicate This issue or pull request already exists question Further information is requested typescript Related to Typescript
Projects
None yet
Development

No branches or pull requests

3 participants