From 3c2d268b765909fd97af1bbe34f1de31670afdda Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Mon, 25 Nov 2024 01:08:53 +0200 Subject: [PATCH] chore: fix jsdoc errors and add some examples, pt. 3. (#1279) --- deno.check.d.ts | 7 +- deno.check.json | 4 + src/dialect/dialect-adapter.ts | 11 +- src/dialect/mssql/mssql-adapter.ts | 10 +- src/dialect/mssql/mssql-dialect-config.ts | 44 ++-- src/dialect/mysql/mysql-adapter.ts | 8 +- src/dialect/postgres/postgres-adapter.ts | 8 +- .../postgres/postgres-dialect-config.ts | 9 +- src/dialect/sqlite/sqlite-adapter.ts | 8 +- src/driver/dummy-driver.ts | 25 +- src/dynamic/dynamic.ts | 12 +- src/expression/expression-builder.ts | 213 +++++++++++++----- src/expression/expression-wrapper.ts | 16 +- src/expression/expression.ts | 39 +++- src/helpers/mssql.ts | 88 ++++++-- src/helpers/mysql.ts | 24 +- src/helpers/postgres.ts | 11 +- src/helpers/sqlite.ts | 57 +++-- src/kysely.ts | 110 +++++---- src/migration/migrator.ts | 92 +++++++- .../operation-node-transformer.ts | 10 +- src/plugin/camel-case/camel-case-plugin.ts | 43 ++-- .../immediate-value-transformer.ts | 2 +- src/plugin/kysely-plugin.ts | 34 ++- .../parse-json-results-plugin.ts | 38 +++- .../with-schema/with-schema-transformer.ts | 2 +- .../aggregate-function-builder.ts | 4 +- src/query-builder/delete-query-builder.ts | 89 +++++--- 28 files changed, 721 insertions(+), 297 deletions(-) diff --git a/deno.check.d.ts b/deno.check.d.ts index 48196b508..c7629f1e2 100644 --- a/deno.check.d.ts +++ b/deno.check.d.ts @@ -5,10 +5,11 @@ import type { Insertable, Kysely, Selectable, + SqlBool, Updateable, } from './dist/esm' -interface Database { +export interface Database { person: PersonTable pet: PetTable toy: ToyTable @@ -36,11 +37,13 @@ interface PersonTable { website: { url: string } } | null updated_at: ColumnType + marital_status: 'single' | 'married' | 'divorced' | 'widowed' | null } interface PetTable { id: Generated - is_favorite: Generated + created_at: GeneratedAlways + is_favorite: Generated name: string owner_id: number species: Species diff --git a/deno.check.json b/deno.check.json index 7c3e3bf10..578706e71 100644 --- a/deno.check.json +++ b/deno.check.json @@ -6,7 +6,11 @@ "imports": { "better-sqlite3": "npm:better-sqlite3", "kysely": "./dist/esm", + "kysely/helpers/mssql": "./dist/esm/helpers/mssql.js", + "kysely/helpers/mysql": "./dist/esm/helpers/mysql.js", "kysely/helpers/postgres": "./dist/esm/helpers/postgres.js", + "kysely/helpers/sqlite": "./dist/esm/helpers/sqlite.js", + "lodash/snakeCase": "npm:lodash/snakeCase", "mysql2": "npm:mysql2", "pg": "npm:pg", "pg-cursor": "npm:pg-cursor", diff --git a/src/dialect/dialect-adapter.ts b/src/dialect/dialect-adapter.ts index 95c71531b..f8bf61fe1 100644 --- a/src/dialect/dialect-adapter.ts +++ b/src/dialect/dialect-adapter.ts @@ -57,10 +57,13 @@ export interface DialectAdapter { * have explicit locks but supports `FOR UPDATE` row locks and transactional DDL: * * ```ts - * import { DialectAdapterBase, MigrationLockOptions, Kysely } from 'kysely' + * import { DialectAdapterBase, type MigrationLockOptions, Kysely } from 'kysely' * * export class MyAdapter extends DialectAdapterBase { - * async override acquireMigrationLock(db: Kysely, options: MigrationLockOptions): Promise { + * override async acquireMigrationLock( + * db: Kysely, + * options: MigrationLockOptions + * ): Promise { * const queryDb = options.lockTableSchema * ? db.withSchema(options.lockTableSchema) * : db @@ -76,6 +79,10 @@ export interface DialectAdapter { * .forUpdate() * .execute() * } + * + * override async releaseMigrationLock() { + * // noop + * } * } * ``` * diff --git a/src/dialect/mssql/mssql-adapter.ts b/src/dialect/mssql/mssql-adapter.ts index 721178907..606b14fd5 100644 --- a/src/dialect/mssql/mssql-adapter.ts +++ b/src/dialect/mssql/mssql-adapter.ts @@ -4,19 +4,19 @@ import { sql } from '../../raw-builder/sql.js' import { DialectAdapterBase } from '../dialect-adapter-base.js' export class MssqlAdapter extends DialectAdapterBase { - get supportsCreateIfNotExists(): boolean { + override get supportsCreateIfNotExists(): boolean { return false } - get supportsTransactionalDdl(): boolean { + override get supportsTransactionalDdl(): boolean { return true } - get supportsOutput(): boolean { + override get supportsOutput(): boolean { return true } - async acquireMigrationLock(db: Kysely): Promise { + override async acquireMigrationLock(db: Kysely): Promise { // Acquire a transaction-level exclusive lock on the migrations table. // https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql?view=sql-server-ver16 await sql`exec sp_getapplock @DbPrincipal = ${sql.lit( @@ -26,7 +26,7 @@ export class MssqlAdapter extends DialectAdapterBase { )}`.execute(db) } - async releaseMigrationLock(): Promise { + override async releaseMigrationLock(): Promise { // Nothing to do here. `sp_getapplock` is automatically released at the // end of the transaction and since `supportsTransactionalDdl` true, we know // the `db` instance passed to acquireMigrationLock is actually a transaction. diff --git a/src/dialect/mssql/mssql-dialect-config.ts b/src/dialect/mssql/mssql-dialect-config.ts index 197610ba3..8e8e2b588 100644 --- a/src/dialect/mssql/mssql-dialect-config.ts +++ b/src/dialect/mssql/mssql-dialect-config.ts @@ -6,21 +6,23 @@ export interface MssqlDialectConfig { * (excluding `create`, `destroy` and `validate` functions which are controlled by this dialect), * `min` & `max` connections at the very least. * - * Example: + * ### Examples * * ```ts + * import { MssqlDialect } from 'kysely' * import * as Tarn from 'tarn' + * import * as Tedious from 'tedious' * * const dialect = new MssqlDialect({ - * // ... - * tarn: { - * ...Tarn, - * options: { + * tarn: { ...Tarn, options: { max: 10, min: 0 } }, + * tedious: { + * ...Tedious, + * connectionFactory: () => new Tedious.Connection({ * // ... - * min: 0, - * max: 10, - * }, - * }, + * server: 'localhost', + * // ... + * }), + * } * }) * ``` */ @@ -32,19 +34,23 @@ export interface MssqlDialectConfig { * you need to pass the `tedious` package itself. You also need to pass a factory * function that creates new `tedious` `Connection` instances on demand. * - * Example: + * ### Examples * * ```ts + * import { MssqlDialect } from 'kysely' + * import * as Tarn from 'tarn' * import * as Tedious from 'tedious' * * const dialect = new MssqlDialect({ - * // ... + * tarn: { ...Tarn, options: { max: 10, min: 0 } }, * tedious: { * ...Tedious, * connectionFactory: () => new Tedious.Connection({ * // ... + * server: 'localhost', + * // ... * }), - * }, + * } * }) * ``` */ @@ -163,20 +169,6 @@ export interface Tarn { /** * Tarn.js' Pool class. - * - * Example: - * - * ```ts - * import { Pool } from 'tarn' - * - * const dialect = new MssqlDialect({ - * // ... - * tarn: { - * // ... - * Pool, - * }, - * }) - * ``` */ Pool: typeof TarnPool } diff --git a/src/dialect/mysql/mysql-adapter.ts b/src/dialect/mysql/mysql-adapter.ts index eb142a8fa..281b86222 100644 --- a/src/dialect/mysql/mysql-adapter.ts +++ b/src/dialect/mysql/mysql-adapter.ts @@ -7,15 +7,15 @@ const LOCK_ID = 'ea586330-2c93-47c8-908d-981d9d270f9d' const LOCK_TIMEOUT_SECONDS = 60 * 60 export class MysqlAdapter extends DialectAdapterBase { - get supportsTransactionalDdl(): boolean { + override get supportsTransactionalDdl(): boolean { return false } - get supportsReturning(): boolean { + override get supportsReturning(): boolean { return false } - async acquireMigrationLock( + override async acquireMigrationLock( db: Kysely, _opt: MigrationLockOptions, ): Promise { @@ -30,7 +30,7 @@ export class MysqlAdapter extends DialectAdapterBase { )})`.execute(db) } - async releaseMigrationLock( + override async releaseMigrationLock( db: Kysely, _opt: MigrationLockOptions, ): Promise { diff --git a/src/dialect/postgres/postgres-adapter.ts b/src/dialect/postgres/postgres-adapter.ts index e49400f05..6258617f3 100644 --- a/src/dialect/postgres/postgres-adapter.ts +++ b/src/dialect/postgres/postgres-adapter.ts @@ -7,15 +7,15 @@ import { MigrationLockOptions } from '../dialect-adapter.js' const LOCK_ID = BigInt('3853314791062309107') export class PostgresAdapter extends DialectAdapterBase { - get supportsTransactionalDdl(): boolean { + override get supportsTransactionalDdl(): boolean { return true } - get supportsReturning(): boolean { + override get supportsReturning(): boolean { return true } - async acquireMigrationLock( + override async acquireMigrationLock( db: Kysely, _opt: MigrationLockOptions, ): Promise { @@ -23,7 +23,7 @@ export class PostgresAdapter extends DialectAdapterBase { await sql`select pg_advisory_xact_lock(${sql.lit(LOCK_ID)})`.execute(db) } - async releaseMigrationLock( + override async releaseMigrationLock( _db: Kysely, _opt: MigrationLockOptions, ): Promise { diff --git a/src/dialect/postgres/postgres-dialect-config.ts b/src/dialect/postgres/postgres-dialect-config.ts index 02cba7c09..88facba7c 100644 --- a/src/dialect/postgres/postgres-dialect-config.ts +++ b/src/dialect/postgres/postgres-dialect-config.ts @@ -15,13 +15,16 @@ export interface PostgresDialectConfig { /** * https://github.com/brianc/node-postgres/tree/master/packages/pg-cursor + * * ```ts + * import { PostgresDialect } from 'kysely' + * import { Pool } from 'pg' * import Cursor from 'pg-cursor' - * // or - * import * as Cursor from 'pg-cursor' + * // or import * as Cursor from 'pg-cursor' * * new PostgresDialect({ - * cursor: Cursor + * cursor: Cursor, + * pool: new Pool('postgres://localhost:5432/mydb') * }) * ``` */ diff --git a/src/dialect/sqlite/sqlite-adapter.ts b/src/dialect/sqlite/sqlite-adapter.ts index 74745c50b..28822d664 100644 --- a/src/dialect/sqlite/sqlite-adapter.ts +++ b/src/dialect/sqlite/sqlite-adapter.ts @@ -3,15 +3,15 @@ import { DialectAdapterBase } from '../dialect-adapter-base.js' import { MigrationLockOptions } from '../dialect-adapter.js' export class SqliteAdapter extends DialectAdapterBase { - get supportsTransactionalDdl(): boolean { + override get supportsTransactionalDdl(): boolean { return false } - get supportsReturning(): boolean { + override get supportsReturning(): boolean { return true } - async acquireMigrationLock( + override async acquireMigrationLock( _db: Kysely, _opt: MigrationLockOptions, ): Promise { @@ -20,7 +20,7 @@ export class SqliteAdapter extends DialectAdapterBase { // We don't need to do anything here. } - async releaseMigrationLock( + override async releaseMigrationLock( _db: Kysely, _opt: MigrationLockOptions, ): Promise { diff --git a/src/driver/dummy-driver.ts b/src/driver/dummy-driver.ts index 4a4d00fe6..e86cd29d4 100644 --- a/src/driver/dummy-driver.ts +++ b/src/driver/dummy-driver.ts @@ -11,20 +11,21 @@ import { Driver } from './driver.js' * This example creates a Kysely instance for building postgres queries: * * ```ts + * import { + * DummyDriver, + * Kysely, + * PostgresAdapter, + * PostgresIntrospector, + * PostgresQueryCompiler + * } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * * const db = new Kysely({ * dialect: { - * createAdapter() { - * return new PostgresAdapter() - * }, - * createDriver() { - * return new DummyDriver() - * }, - * createIntrospector(db: Kysely) { - * return new PostgresIntrospector(db) - * }, - * createQueryCompiler() { - * return new PostgresQueryCompiler() - * }, + * createAdapter: () => new PostgresAdapter(), + * createDriver: () => new DummyDriver(), + * createIntrospector: (db: Kysely) => new PostgresIntrospector(db), + * createQueryCompiler: () => new PostgresQueryCompiler(), * }, * }) * ``` diff --git a/src/dynamic/dynamic.ts b/src/dynamic/dynamic.ts index a0e276b7f..bd0daffff 100644 --- a/src/dynamic/dynamic.ts +++ b/src/dynamic/dynamic.ts @@ -62,11 +62,11 @@ export class DynamicModule { * const { ref } = db.dynamic * * // Some column name provided by the user. Value not known at compile time. - * const columnFromUserInput = req.query.select; + * const columnFromUserInput: PossibleColumns = 'birthdate'; * * // A type that lists all possible values `columnFromUserInput` can have. * // You can use `keyof Person` if any column of an interface is allowed. - * type PossibleColumns = 'last_name' | 'first_name' | 'birth_date' + * type PossibleColumns = 'last_name' | 'first_name' | 'birthdate' * * const [person] = await db.selectFrom('person') * .select([ @@ -78,12 +78,12 @@ export class DynamicModule { * // The resulting type contains all `PossibleColumns` as optional fields * // because we cannot know which field was actually selected before * // running the code. - * const lastName: string | undefined = person.last_name - * const firstName: string | undefined = person.first_name - * const birthDate: string | undefined = person.birth_date + * const lastName: string | null | undefined = person?.last_name + * const firstName: string | undefined = person?.first_name + * const birthDate: Date | null | undefined = person?.birthdate * * // The result type also contains the compile time selection `id`. - * person.id + * person?.id * ``` */ ref(reference: string): DynamicReferenceBuilder { diff --git a/src/expression/expression-builder.ts b/src/expression/expression-builder.ts index c3faa4c57..cb597dc51 100644 --- a/src/expression/expression-builder.ts +++ b/src/expression/expression-builder.ts @@ -96,9 +96,10 @@ export interface ExpressionBuilder { * A simple comparison: * * ```ts - * eb.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where((eb) => eb('first_name', '=', 'Jennifer')) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -113,9 +114,10 @@ export interface ExpressionBuilder { * a column reference, you can use {@link ref}: * * ```ts - * eb.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where((eb) => eb('first_name', '=', eb.ref('last_name'))) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -129,11 +131,12 @@ export interface ExpressionBuilder { * In the following example `eb` is used to increment an integer column: * * ```ts - * db.updateTable('person') + * await db.updateTable('person') * .set((eb) => ({ * age: eb('age', '+', 1) * })) - * .where('id', '=', id) + * .where('id', '=', 3) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -148,15 +151,28 @@ export interface ExpressionBuilder { * can be any expression: * * ```ts - * eb.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where((eb) => eb( - * eb.fn('lower', ['first_name']), + * eb.fn('lower', ['first_name']), * 'in', * eb.selectFrom('pet') * .select('pet.name') * .where('pet.species', '=', 'cat') * )) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select * + * from "person" + * where lower("first_name") in ( + * select "pet"."name" + * from "pet" + * where "pet"."species" = $1 + * ) * ``` */ < @@ -181,13 +197,14 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .where(({ eb, exists, selectFrom }) => * eb('first_name', '=', 'Jennifer').and( * exists(selectFrom('pet').whereRef('owner_id', '=', 'person.id').select('pet.id')) * ) * ) * .selectAll() + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -210,7 +227,7 @@ export interface ExpressionBuilder { * table of the database even if it doesn't produce valid SQL. * * ```ts - * await db.selectFrom('person') + * const result = await db.selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') * .select((eb) => [ * 'person.id', @@ -258,7 +275,7 @@ export interface ExpressionBuilder { * ]) * .execute() * - * console.log(result[0].owner_name) + * console.log(result[0]?.owner_name) * ``` * * The generated SQL (PostgreSQL): @@ -309,11 +326,9 @@ export interface ExpressionBuilder { * Kitchen sink example with 2 flavors of `case` operator: * * ```ts - * import { sql } from 'kysely' - * * const { title, name } = await db * .selectFrom('person') - * .where('id', '=', '123') + * .where('id', '=', 123) * .select((eb) => [ * eb.fn.coalesce('last_name', 'first_name').as('name'), * eb @@ -323,7 +338,7 @@ export interface ExpressionBuilder { * .when('gender', '=', 'female') * .then( * eb - * .case('maritalStatus') + * .case('marital_status') * .when('single') * .then('Ms.') * .else('Mrs.') @@ -343,7 +358,7 @@ export interface ExpressionBuilder { * case * when "gender" = $1 then $2 * when "gender" = $3 then - * case "maritalStatus" + * case "marital_status" * when $4 then $5 * else $6 * end @@ -376,72 +391,112 @@ export interface ExpressionBuilder { * This function can be used to pass in a column reference instead: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.or([ * eb('first_name', '=', eb.ref('last_name')), * eb('first_name', '=', eb.ref('middle_name')) * ])) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "person".* + * from "person" + * where "first_name" = "last_name" or "first_name" = "middle_name" * ``` * * In the next example we use the `ref` method to reference columns of the virtual * table `excluded` in a type-safe way to create an upsert operation: * * ```ts - * db.insertInto('person') - * .values(person) + * await db.insertInto('person') + * .values({ + * id: 3, + * first_name: 'Jennifer', + * last_name: 'Aniston', + * gender: 'female', + * }) * .onConflict((oc) => oc * .column('id') * .doUpdateSet(({ ref }) => ({ * first_name: ref('excluded.first_name'), - * last_name: ref('excluded.last_name') + * last_name: ref('excluded.last_name'), + * gender: ref('excluded.gender'), * })) * ) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * insert into "person" ("id", "first_name", "last_name", "gender") + * values ($1, $2, $3, $4) + * on conflict ("id") do update set + * "first_name" = "excluded"."first_name", + * "last_name" = "excluded"."last_name", + * "gender" = "excluded"."gender" * ``` * * In the next example we use `ref` in a raw sql expression. Unless you want * to be as type-safe as possible, this is probably overkill: * * ```ts - * db.update('pet').set((eb) => ({ - * name: sql`concat(${eb.ref('pet.name')}, ${suffix})` - * })) + * import { sql } from 'kysely' + * + * await db.updateTable('pet') + * .set((eb) => ({ + * name: sql`concat(${eb.ref('pet.name')}, ${' the animal'})` + * })) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * update "pet" set "name" = concat("pet"."name", $1) * ``` * * In the next example we use `ref` to reference a nested JSON property: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .where(({ eb, ref }) => eb( - * ref('address', '->').key('state').key('abbr'), + * ref('profile', '->').key('addresses').at(0).key('city'), * '=', - * 'CA' + * 'San Diego' * )) * .selectAll() + * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * select * from "person" where "address"->'state'->'abbr' = $1 + * select * from "person" where "profile"->'addresses'->0->'city' = $1 * ``` * * You can also compile to a JSON path expression by using the `->$`or `->>$` operator: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .select(({ ref }) => - * ref('experience', '->$') + * ref('profile', '->$') + * .key('addresses') * .at('last') - * .key('title') - * .as('current_job') + * .key('city') + * .as('current_city') * ) + * .execute() * ``` * * The generated SQL (MySQL): * * ```sql - * select `experience`->'$[last].title' as `current_job` from `person` + * select `profile`->'$.addresses[last].city' as `current_city` from `person` * ``` */ ref>( @@ -461,13 +516,13 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.updateTable('person') - * .set('experience', (eb) => eb.fn('json_set', [ - * 'experience', - * eb.jsonPath<'experience'>().at('last').key('title'), - * eb.val('CEO') + * await db.updateTable('person') + * .set('profile', (eb) => eb.fn('json_set', [ + * 'profile', + * eb.jsonPath<'profile'>().key('addresses').at('last').key('city'), + * eb.val('San Diego') * ])) - * .where('id', '=', id) + * .where('id', '=', 3) * .execute() * ``` * @@ -475,8 +530,8 @@ export interface ExpressionBuilder { * * ```sql * update `person` - * set `experience` = json_set(`experience`, '$[last].title', ?) - * where `id` = ? + * set `profile` = json_set(`profile`, '$.addresses[last].city', $1) + * where `id` = $2 * ``` */ jsonPath<$ extends StringReference = never>(): IsNever<$> extends true @@ -486,8 +541,13 @@ export interface ExpressionBuilder { /** * Creates a table reference. * + * ### Examples + * * ```ts - * db.selectFrom('person') + * import { sql } from 'kysely' + * import type { Pet } from 'type-editor' // imaginary module + * + * const result = await db.selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') * .select(eb => [ * 'person.id', @@ -523,13 +583,30 @@ export interface ExpressionBuilder { * be used to pass in a value instead: * * ```ts - * eb(val(38), '=', ref('age')) + * const result = await db.selectFrom('person') + * .selectAll() + * .where((eb) => eb( + * eb.val('cat'), + * '=', + * eb.fn.any( + * eb.selectFrom('pet') + * .select('species') + * .whereRef('owner_id', '=', 'person.id') + * ) + * )) + * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * $1 = "age" + * select * + * from "person" + * where $1 = any( + * select "species" + * from "pet" + * where "owner_id" = "person"."id" + * ) * ``` */ val( @@ -545,7 +622,7 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where(({ eb, refTuple, tuple }) => eb( * refTuple('first_name', 'last_name'), @@ -555,6 +632,7 @@ export interface ExpressionBuilder { * tuple('Sylvester', 'Stallone') * ] * )) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -578,7 +656,7 @@ export interface ExpressionBuilder { * function: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where(({ eb, refTuple, selectFrom }) => eb( * refTuple('first_name', 'last_name'), @@ -588,6 +666,7 @@ export interface ExpressionBuilder { * .where('species', '!=', 'cat') * .$asTuple('name', 'species') * )) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -660,7 +739,7 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where(({ eb, refTuple, tuple }) => eb( * refTuple('first_name', 'last_name'), @@ -670,6 +749,7 @@ export interface ExpressionBuilder { * tuple('Sylvester', 'Stallone') * ] * )) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -724,8 +804,9 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .select((eb) => eb.lit(1).as('one')) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -749,11 +830,12 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .select((eb) => [ * 'first_name', * eb.unary('-', 'age').as('negative_age') * ]) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -807,9 +889,10 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where((eb) => eb.between('age', 40, 60)) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -834,9 +917,10 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where((eb) => eb.betweenSymmetric('age', 40, 60)) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -869,13 +953,14 @@ export interface ExpressionBuilder { * statement: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.and([ * eb('first_name', '=', 'Jennifer'), - * eb('fist_name', '=', 'Arnold'), - * eb('fist_name', '=', 'Sylvester') + * eb('first_name', '=', 'Arnold'), + * eb('first_name', '=', 'Sylvester') * ])) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -894,12 +979,13 @@ export interface ExpressionBuilder { * equality comparisons: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.and({ * first_name: 'Jennifer', * last_name: 'Aniston' * })) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -935,13 +1021,14 @@ export interface ExpressionBuilder { * statement: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.or([ * eb('first_name', '=', 'Jennifer'), - * eb('fist_name', '=', 'Arnold'), - * eb('fist_name', '=', 'Sylvester') + * eb('first_name', '=', 'Arnold'), + * eb('first_name', '=', 'Sylvester') * ])) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -960,12 +1047,13 @@ export interface ExpressionBuilder { * equality comparisons: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.or({ * first_name: 'Jennifer', * last_name: 'Aniston' * })) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -993,9 +1081,10 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb(eb.parens('age', '+', 1), '/', 100), '<', 0.1) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -1009,13 +1098,14 @@ export interface ExpressionBuilder { * You can also pass in any expression as the only argument: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb.parens( - * eb('age', '=', 1).or('age', '=', 2)) + * eb('age', '=', 1).or('age', '=', 2) * ).and( * eb('first_name', '=', 'Jennifer').or('first_name', '=', 'Arnold') - * ) + * )) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -1053,12 +1143,13 @@ export interface ExpressionBuilder { * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .select((eb) => [ * 'id', * 'first_name', * eb.cast('age', 'integer').as('age') * ]) + * .execute() * ``` * * The generated SQL (PostgreSQL): diff --git a/src/expression/expression-wrapper.ts b/src/expression/expression-wrapper.ts index ce0d8638a..85c650b51 100644 --- a/src/expression/expression-wrapper.ts +++ b/src/expression/expression-wrapper.ts @@ -37,6 +37,8 @@ export class ExpressionWrapper /** * Returns an aliased version of the expression. * + * ### Examples + * * In addition to slapping `as "the_alias"` to the end of the SQL, * this method also provides strict typing: * @@ -75,12 +77,13 @@ export class ExpressionWrapper * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where(eb => eb('first_name', '=', 'Jennifer') * .or('first_name', '=', 'Arnold') * .or('first_name', '=', 'Sylvester') * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -99,7 +102,7 @@ export class ExpressionWrapper * this method: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where(eb => eb('first_name', '=', 'Jennifer') * .or(eb('first_name', '=', 'Sylvester').and('last_name', '=', 'Stallone')) @@ -109,6 +112,7 @@ export class ExpressionWrapper * .whereRef('pet.owner_id', '=', 'person.id') * )) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -158,12 +162,13 @@ export class ExpressionWrapper * ### Examples * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where(eb => eb('first_name', '=', 'Jennifer') * .and('last_name', '=', 'Aniston') * .and('age', '>', 40) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -182,7 +187,7 @@ export class ExpressionWrapper * this method: * * ```ts - * db.selectFrom('person') + * const result = await db.selectFrom('person') * .selectAll() * .where(eb => eb('first_name', '=', 'Jennifer') * .and(eb('first_name', '=', 'Sylvester').or('last_name', '=', 'Stallone')) @@ -192,6 +197,7 @@ export class ExpressionWrapper * .whereRef('pet.owner_id', '=', 'person.id') * )) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -405,7 +411,7 @@ export class AndWrapper * .executeTakeFirstOrThrow() * * // `is_jennifer_aniston: SqlBool` field exists in the result type. - * console.log(result.is_jennifer_or_sylvester) + * console.log(result.is_jennifer_aniston) * ``` * * The generated SQL (PostgreSQL): diff --git a/src/expression/expression.ts b/src/expression/expression.ts index a1b1f8f16..175b2af9c 100644 --- a/src/expression/expression.ts +++ b/src/expression/expression.ts @@ -12,7 +12,11 @@ import { isObject, isString } from '../util/object-utils.js' * Most Kysely methods accept instances of `Expression` and most classes like `SelectQueryBuilder` * and the return value of the {@link sql} template tag implement it. * + * ### Examples + * * ```ts + * import { type Expression, sql } from 'kysely' + * * const exp1: Expression = sql`CONCAT('hello', ' ', 'world')` * const exp2: Expression<{ first_name: string }> = db.selectFrom('person').select('first_name') * ``` @@ -24,11 +28,19 @@ export interface Expression extends OperationNodeSource { * All expressions need to have this getter for complicated type-related reasons. * Simply add this getter for your expression and always return `undefined` from it: * + * ### Examples + * * ```ts + * import { type Expression, type OperationNode, sql } from 'kysely' + * * class SomeExpression implements Expression { * get expressionType(): T | undefined { * return undefined * } + * + * toOperationNode(): OperationNode { + * return sql`some sql here`.toOperationNode() + * } * } * ``` * @@ -41,11 +53,19 @@ export interface Expression extends OperationNodeSource { /** * Creates the OperationNode that describes how to compile this expression into SQL. * + * ### Examples + * * If you are creating a custom expression, it's often easiest to use the {@link sql} * template tag to build the node: * * ```ts + * import { type Expression, type OperationNode, sql } from 'kysely' + * * class SomeExpression implements Expression { + * get expressionType(): T | undefined { + * return undefined + * } + * * toOperationNode(): OperationNode { * return sql`some sql here`.toOperationNode() * } @@ -62,6 +82,8 @@ export interface AliasableExpression extends Expression { /** * Returns an aliased version of the expression. * + * ### Examples + * * In addition to slapping `as "the_alias"` at the end of the expression, * this method also provides strict typing: * @@ -91,6 +113,8 @@ export interface AliasableExpression extends Expression { * provide the alias as the only type argument: * * ```ts + * import { sql } from 'kysely' + * * const values = sql<{ a: number, b: string }>`(values (1, 'foo'))` * * // The alias is `t(a, b)` which specifies the column names @@ -105,6 +129,7 @@ export interface AliasableExpression extends Expression { * .expression( * db.selectFrom(aliasedValues).select(['t.a', 't.b']) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -127,9 +152,16 @@ export interface AliasableExpression extends Expression { * needs to implement an `AliasedExpression`. `A` becomes the name of the selected expression * in the result and `T` becomes its type. * - * @example + * ### Examples * * ```ts + * import { + * AliasNode, + * type AliasedExpression, + * type Expression, + * IdentifierNode + * } from 'kysely' + * * class SomeAliasedExpression implements AliasedExpression { * #expression: Expression * #alias: A @@ -148,7 +180,10 @@ export interface AliasableExpression extends Expression { * } * * toOperationNode(): AliasNode { - * return AliasNode.create(this.#expression.toOperationNode(), IdentifierNode.create(this.#alias)) + * return AliasNode.create( + * this.#expression.toOperationNode(), + * IdentifierNode.create(this.#alias) + * ) * } * } * ``` diff --git a/src/helpers/mssql.ts b/src/helpers/mssql.ts index 982e6dfa0..ce5a1792a 100644 --- a/src/helpers/mssql.ts +++ b/src/helpers/mssql.ts @@ -12,8 +12,26 @@ import { Simplify } from '../util/type-utils.js' * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new MssqlDialect(config), + * import { Kysely, MssqlDialect, ParseJSONResultsPlugin } from 'kysely' + * import * as Tarn from 'tarn' + * import * as Tedious from 'tedious' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new MssqlDialect({ + * tarn: { options: { max: 10, min: 0 }, ...Tarn }, + * tedious: { + * ...Tedious, + * connectionFactory: () => new Tedious.Connection({ + * authentication: { + * options: { password: 'password', userName: 'sa' }, + * type: 'default', + * }, + * options: { database: 'test', port: 21433, trustServerCertificate: true }, + * server: 'localhost', + * }), + * }, + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -21,6 +39,8 @@ import { Simplify } from '../util/type-utils.js' * ### Examples * * ```ts + * import { jsonArrayFrom } from 'kysely/helpers/mssql' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -30,14 +50,14 @@ import { Simplify } from '../util/type-utils.js' * .select(['pet.id as pet_id', 'pet.name']) * .whereRef('pet.owner_id', '=', 'person.id') * .orderBy('pet.name') - * .modifyEnd(sql`offset 0 rows`) + * .offset(0) * ).as('pets') * ]) * .execute() * - * result[0].id - * result[0].pets[0].pet_id - * result[0].pets[0].name + * result[0]?.id + * result[0]?.pets[0]?.pet_id + * result[0]?.pets[0]?.name * ``` * * The generated SQL (MS SQL Server): @@ -49,7 +69,7 @@ import { Simplify } from '../util/type-utils.js' * from "pet" * where "pet"."owner_id" = "person"."id" * order by "pet"."name" - * offset 0 rows + * offset @1 rows * ) as "agg" for json path, include_null_values), '[]') * ) as "pets" * from "person" @@ -72,8 +92,26 @@ export function jsonArrayFrom( * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new MssqlDialect(config), + * import { Kysely, MssqlDialect, ParseJSONResultsPlugin } from 'kysely' + * import * as Tarn from 'tarn' + * import * as Tedious from 'tedious' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new MssqlDialect({ + * tarn: { options: { max: 10, min: 0 }, ...Tarn }, + * tedious: { + * ...Tedious, + * connectionFactory: () => new Tedious.Connection({ + * authentication: { + * options: { password: 'password', userName: 'sa' }, + * type: 'default', + * }, + * options: { database: 'test', port: 21433, trustServerCertificate: true }, + * server: 'localhost', + * }), + * }, + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -81,6 +119,8 @@ export function jsonArrayFrom( * ### Examples * * ```ts + * import { jsonObjectFrom } from 'kysely/helpers/mssql' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -94,9 +134,9 @@ export function jsonArrayFrom( * ]) * .execute() * - * result[0].id - * result[0].favorite_pet.pet_id - * result[0].favorite_pet.name + * result[0]?.id + * result[0]?.favorite_pet?.pet_id + * result[0]?.favorite_pet?.name * ``` * * The generated SQL (MS SQL Server): @@ -128,8 +168,26 @@ export function jsonObjectFrom( * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new MssqlDialect(config), + * import { Kysely, MssqlDialect, ParseJSONResultsPlugin } from 'kysely' + * import * as Tarn from 'tarn' + * import * as Tedious from 'tedious' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new MssqlDialect({ + * tarn: { options: { max: 10, min: 0 }, ...Tarn }, + * tedious: { + * ...Tedious, + * connectionFactory: () => new Tedious.Connection({ + * authentication: { + * options: { password: 'password', userName: 'sa' }, + * type: 'default', + * }, + * options: { database: 'test', port: 21433, trustServerCertificate: true }, + * server: 'localhost', + * }), + * }, + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -137,6 +195,8 @@ export function jsonObjectFrom( * ### Examples * * ```ts + * import { jsonBuildObject } from 'kysely/helpers/mssql' + * * const result = await db * .selectFrom('person') * .select((eb) => [ diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index 95470f1bc..0d115e79b 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -17,6 +17,8 @@ import { Simplify } from '../util/type-utils.js' * ### Examples * * ```ts + * import { jsonArrayFrom } from 'kysely/helpers/mysql' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -30,9 +32,9 @@ import { Simplify } from '../util/type-utils.js' * ]) * .execute() * - * result[0].id - * result[0].pets[0].pet_id - * result[0].pets[0].name + * result[0]?.id + * result[0]?.pets[0]?.pet_id + * result[0]?.pets[0]?.name * ``` * * The generated SQL (MySQL): @@ -73,6 +75,8 @@ export function jsonArrayFrom( * ### Examples * * ```ts + * import { jsonObjectFrom } from 'kysely/helpers/mysql' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -86,9 +90,9 @@ export function jsonArrayFrom( * ]) * .execute() * - * result[0].id - * result[0].favorite_pet.pet_id - * result[0].favorite_pet.name + * result[0]?.id + * result[0]?.favorite_pet?.pet_id + * result[0]?.favorite_pet?.name * ``` * * The generated SQL (MySQL): @@ -139,10 +143,10 @@ export function jsonObjectFrom( * ]) * .execute() * - * result[0].id - * result[0].name.first - * result[0].name.last - * result[0].name.full + * result[0]?.id + * result[0]?.name.first + * result[0]?.name.last + * result[0]?.name.full * ``` * * The generated SQL (MySQL): diff --git a/src/helpers/postgres.ts b/src/helpers/postgres.ts index e7d406fb6..b1452e77c 100644 --- a/src/helpers/postgres.ts +++ b/src/helpers/postgres.ts @@ -126,6 +126,9 @@ export function jsonObjectFrom( * ### Examples * * ```ts + * import { sql } from 'kysely' + * import { jsonBuildObject } from 'kysely/helpers/postgres' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -138,10 +141,10 @@ export function jsonObjectFrom( * ]) * .execute() * - * result[0].id - * result[0].name.first - * result[0].name.last - * result[0].name.full + * result[0]?.id + * result[0]?.name.first + * result[0]?.name.last + * result[0]?.name.full * ``` * * The generated SQL (PostgreSQL): diff --git a/src/helpers/sqlite.ts b/src/helpers/sqlite.ts index 91b5af6ad..421e91e93 100644 --- a/src/helpers/sqlite.ts +++ b/src/helpers/sqlite.ts @@ -15,8 +15,14 @@ import { Simplify } from '../util/type-utils.js' * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new SqliteDialect(config), + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, ParseJSONResultsPlugin, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:') + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -24,6 +30,8 @@ import { Simplify } from '../util/type-utils.js' * ### Examples * * ```ts + * import { jsonArrayFrom } from 'kysely/helpers/sqlite' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -37,9 +45,9 @@ import { Simplify } from '../util/type-utils.js' * ]) * .execute() * - * result[0].id - * result[0].pets[0].pet_id - * result[0].pets[0].name + * result[0]?.id + * result[0]?.pets[0].pet_id + * result[0]?.pets[0].name * ``` * * The generated SQL (SQLite): @@ -78,8 +86,14 @@ export function jsonArrayFrom( * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new SqliteDialect(config), + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, ParseJSONResultsPlugin, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:') + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -87,6 +101,8 @@ export function jsonArrayFrom( * ### Examples * * ```ts + * import { jsonObjectFrom } from 'kysely/helpers/sqlite' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -100,9 +116,9 @@ export function jsonArrayFrom( * ]) * .execute() * - * result[0].id - * result[0].favorite_pet.pet_id - * result[0].favorite_pet.name + * result[0]?.id + * result[0]?.favorite_pet?.pet_id + * result[0]?.favorite_pet?.name * ``` * * The generated SQL (SQLite): @@ -139,8 +155,14 @@ export function jsonObjectFrom( * The plugin can be installed like this: * * ```ts - * const db = new Kysely({ - * dialect: new SqliteDialect(config), + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, ParseJSONResultsPlugin, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:') + * }), * plugins: [new ParseJSONResultsPlugin()] * }) * ``` @@ -148,6 +170,9 @@ export function jsonObjectFrom( * ### Examples * * ```ts + * import { sql } from 'kysely' + * import { jsonBuildObject } from 'kysely/helpers/sqlite' + * * const result = await db * .selectFrom('person') * .select((eb) => [ @@ -160,10 +185,10 @@ export function jsonObjectFrom( * ]) * .execute() * - * result[0].id - * result[0].name.first - * result[0].name.last - * result[0].name.full + * result[0]?.id + * result[0]?.name.first + * result[0]?.name.last + * result[0]?.name.full * ``` * * The generated SQL (SQLite): diff --git a/src/kysely.ts b/src/kysely.ts index 987c8947e..dcb978f3f 100644 --- a/src/kysely.ts +++ b/src/kysely.ts @@ -42,33 +42,23 @@ import { DrainOuterGeneric } from './util/type-utils.js' * * ### Examples * - * This example assumes your database has tables `person` and `pet`: + * This example assumes your database has a "person" table: * * ```ts - * import { Kysely, Generated, PostgresDialect } from 'kysely' - * - * interface PersonTable { - * id: Generated - * first_name: string - * last_name: string - * } - * - * interface PetTable { - * id: Generated - * owner_id: number - * name: string - * species: 'cat' | 'dog' - * } + * import * as Sqlite from 'better-sqlite3' + * import { type Generated, Kysely, SqliteDialect } from 'kysely' * * interface Database { - * person: PersonTable, - * pet: PetTable + * person: { + * id: Generated + * first_name: string + * last_name: string | null + * } * } * * const db = new Kysely({ - * dialect: new PostgresDialect({ - * host: 'localhost', - * database: 'kysely_test', + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:'), * }) * }) * ``` @@ -165,30 +155,52 @@ export class Kysely } /** - * Returns a {@link FunctionModule} that can be used to write type safe function + * Returns a {@link FunctionModule} that can be used to write somewhat type-safe function * calls. * * ```ts + * const { count } = db.fn + * * await db.selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') - * .select((eb) => [ - * 'person.id', - * eb.fn.count('pet.id').as('pet_count') + * .select([ + * 'id', + * count('pet.id').as('person_count'), * ]) * .groupBy('person.id') - * .having((eb) => eb.fn.count('pet.id'), '>', 10) + * .having(count('pet.id'), '>', 10) * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * select "person"."id", count("pet"."id") as "pet_count" + * select "person"."id", count("pet"."id") as "person_count" * from "person" * inner join "pet" on "pet"."owner_id" = "person"."id" * group by "person"."id" * having count("pet"."id") > $1 * ``` + * + * Why "somewhat" type-safe? Because the function calls are not bound to the + * current query context. They allow you to reference columns and tables that + * are not in the current query. E.g. remove the `innerJoin` from the previous + * query and TypeScript won't even complain. + * + * If you want to make the function calls fully type-safe, you can use the + * {@link ExpressionBuilder.fn} getter for a query context-aware, stricter {@link FunctionModule}. + * + * ```ts + * await db.selectFrom('person') + * .innerJoin('pet', 'pet.owner_id', 'person.id') + * .select((eb) => [ + * 'person.id', + * eb.fn.count('pet.id').as('pet_count') + * ]) + * .groupBy('person.id') + * .having((eb) => eb.fn.count('pet.id'), '>', 10) + * .execute() + * ``` */ get fn(): FunctionModule { return createFunctionModule() @@ -248,12 +260,18 @@ export class Kysely * Setting the isolation level: * * ```ts + * import type { Kysely } from 'kysely' + * * await db * .transaction() * .setIsolationLevel('serializable') * .execute(async (trx) => { * await doStuff(trx) * }) + * + * async function doStuff(kysely: typeof db) { + * // ... + * } * ``` */ transaction(): TransactionBuilder { @@ -274,6 +292,10 @@ export class Kysely * // the same connection. * await doStuff(db) * }) + * + * async function doStuff(kysely: typeof db) { + * // ... + * } * ``` */ connection(): ConnectionBuilder { @@ -303,7 +325,7 @@ export class Kysely /** * @override */ - withSchema(schema: string): Kysely { + override withSchema(schema: string): Kysely { return new Kysely({ ...this.#props, executor: this.#props.executor.withPluginAtFront( @@ -323,7 +345,6 @@ export class Kysely * * The following example adds and uses a temporary table: * - * @example * ```ts * await db.schema * .createTable('temp_table') @@ -401,23 +422,23 @@ export class Transaction extends Kysely { // The return type is `true` instead of `boolean` to make Kysely // unassignable to Transaction while allowing assignment the // other way around. - get isTransaction(): true { + override get isTransaction(): true { return true } - transaction(): TransactionBuilder { + override transaction(): TransactionBuilder { throw new Error( 'calling the transaction method for a Transaction is not supported', ) } - connection(): ConnectionBuilder { + override connection(): ConnectionBuilder { throw new Error( 'calling the connection method for a Transaction is not supported', ) } - async destroy(): Promise { + override async destroy(): Promise { throw new Error( 'calling the destroy method for a Transaction is not supported', ) @@ -437,10 +458,7 @@ export class Transaction extends Kysely { }) } - /** - * @override - */ - withSchema(schema: string): Transaction { + override withSchema(schema: string): Transaction { return new Transaction({ ...this.#props, executor: this.#props.executor.withPluginAtFront( @@ -485,16 +503,32 @@ export interface KyselyConfig { * * ### Examples * + * Setting up built-in logging for preferred log levels: + * * ```ts + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * * const db = new Kysely({ - * dialect: new PostgresDialect(postgresConfig), + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:'), + * }), * log: ['query', 'error'] * }) * ``` * + * Setting up custom logging: + * * ```ts + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * * const db = new Kysely({ - * dialect: new PostgresDialect(postgresConfig), + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:'), + * }), * log(event): void { * if (event.level === 'query') { * console.log(event.query.sql) diff --git a/src/migration/migrator.ts b/src/migration/migrator.ts index d98bc7841..7c923ace8 100644 --- a/src/migration/migrator.ts +++ b/src/migration/migrator.ts @@ -38,14 +38,27 @@ export interface Migration { * ```ts * import { promises as fs } from 'node:fs' * import path from 'node:path' + * import * as Sqlite from 'better-sqlite3' + * import { + * FileMigrationProvider, + * Kysely, + * Migrator, + * SqliteDialect + * } from 'kysely' + * + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: Sqlite(':memory:') + * }) + * }) * * const migrator = new Migrator({ * db, * provider: new FileMigrationProvider({ * fs, - * path, * // Path to the folder that contains all your migrations. - * migrationFolder: 'some/path/to/migrations' + * migrationFolder: 'some/path/to/migrations', + * path, * }) * }) * ``` @@ -102,19 +115,18 @@ export class Migrator { * ### Examples * * ```ts - * const db = new Kysely({ - * dialect: new PostgresDialect({ - * host: 'localhost', - * database: 'kysely_test', - * }), - * }) + * import { promises as fs } from 'node:fs' + * import path from 'node:path' + * import * as Sqlite from 'better-sqlite3' + * import { FileMigrationProvider, Migrator } from 'kysely' * * const migrator = new Migrator({ * db, - * provider: new FileMigrationProvider( - * // Path to the folder that contains all your migrations. - * 'some/path/to/migrations' - * ) + * provider: new FileMigrationProvider({ + * fs, + * migrationFolder: 'some/path/to/migrations', + * path, + * }) * }) * * const { error, results } = await migrator.migrateToLatest() @@ -148,6 +160,20 @@ export class Migrator { * ### Examples * * ```ts + * import { promises as fs } from 'node:fs' + * import path from 'node:path' + * import { FileMigrationProvider, Migrator } from 'kysely' + * + * const migrator = new Migrator({ + * db, + * provider: new FileMigrationProvider({ + * fs, + * // Path to the folder that contains all your migrations. + * migrationFolder: 'some/path/to/migrations', + * path, + * }) + * }) + * * await migrator.migrateTo('some_migration') * ``` * @@ -157,6 +183,20 @@ export class Migrator { * you can use a special constant `NO_MIGRATIONS`: * * ```ts + * import { promises as fs } from 'node:fs' + * import path from 'node:path' + * import { FileMigrationProvider, Migrator, NO_MIGRATIONS } from 'kysely' + * + * const migrator = new Migrator({ + * db, + * provider: new FileMigrationProvider({ + * fs, + * // Path to the folder that contains all your migrations. + * migrationFolder: 'some/path/to/migrations', + * path, + * }) + * }) + * * await migrator.migrateTo(NO_MIGRATIONS) * ``` */ @@ -213,6 +253,20 @@ export class Migrator { * ### Examples * * ```ts + * import { promises as fs } from 'node:fs' + * import path from 'node:path' + * import { FileMigrationProvider, Migrator } from 'kysely' + * + * const migrator = new Migrator({ + * db, + * provider: new FileMigrationProvider({ + * fs, + * // Path to the folder that contains all your migrations. + * migrationFolder: 'some/path/to/migrations', + * path, + * }) + * }) + * * await migrator.migrateUp() * ``` */ @@ -231,6 +285,20 @@ export class Migrator { * ### Examples * * ```ts + * import { promises as fs } from 'node:fs' + * import path from 'node:path' + * import { FileMigrationProvider, Migrator } from 'kysely' + * + * const migrator = new Migrator({ + * db, + * provider: new FileMigrationProvider({ + * fs, + * // Path to the folder that contains all your migrations. + * migrationFolder: 'some/path/to/migrations', + * path, + * }) + * }) + * * await migrator.migrateDown() * ``` */ diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index ec7a01971..2e2574760 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -109,8 +109,11 @@ import { OutputNode } from './output-node.js' * snake_case, you'd do something like this: * * ```ts + * import { type IdentifierNode, OperationNodeTransformer } from 'kysely' + * import snakeCase from 'lodash/snakeCase' + * * class CamelCaseTransformer extends OperationNodeTransformer { - * transformIdentifier(node: IdentifierNode): IdentifierNode { + * override transformIdentifier(node: IdentifierNode): IdentifierNode { * node = super.transformIdentifier(node) * * return { @@ -121,7 +124,10 @@ import { OutputNode } from './output-node.js' * } * * const transformer = new CamelCaseTransformer() - * const tree = transformer.transformNode(tree) + * + * const query = db.selectFrom('person').select(['first_name', 'last_name']) + * + * const tree = transformer.transformNode(query.toOperationNode()) * ``` */ export class OperationNodeTransformer { diff --git a/src/plugin/camel-case/camel-case-plugin.ts b/src/plugin/camel-case/camel-case-plugin.ts index 5ca2dca03..eeadd59d1 100644 --- a/src/plugin/camel-case/camel-case-plugin.ts +++ b/src/plugin/camel-case/camel-case-plugin.ts @@ -58,38 +58,39 @@ export interface CamelCasePluginOptions { * using `CamelCasePlugin` we would setup Kysely like this: * * ```ts - * interface Person { - * firstName: string - * lastName: string - * } + * import * as Sqlite from 'better-sqlite3' + * import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely' * - * interface Database { - * personTable: Person + * interface CamelCasedDatabase { + * userMetadata: { + * firstName: string + * lastName: string + * } * } * - * const db = new Kysely({ - * dialect: new PostgresDialect({ - * database: 'kysely_test', - * host: 'localhost', + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:'), * }), - * plugins: [ - * new CamelCasePlugin() - * ] + * plugins: [new CamelCasePlugin()], * }) * - * const person = await db.selectFrom('personTable') + * const person = await db.selectFrom('userMetadata') * .where('firstName', '=', 'Arnold') * .select(['firstName', 'lastName']) * .executeTakeFirst() * - * // generated sql: - * // select first_name, last_name from person_table where first_name = $1 - * * if (person) { * console.log(person.firstName) * } * ``` * + * The generated SQL (SQLite): + * + * ```sql + * select "first_name", "last_name" from "user_metadata" where "first_name" = ? + * ``` + * * As you can see from the example, __everything__ needs to be defined * in camelCase in the TypeScript code: table names, columns, schemas, * __everything__. When using the `CamelCasePlugin` Kysely works as if @@ -104,11 +105,15 @@ export interface CamelCasePluginOptions { * ```ts * class MyCamelCasePlugin extends CamelCasePlugin { * protected override snakeCase(str: string): string { - * return mySnakeCase(str) + * // ... + * + * return str * } * * protected override camelCase(str: string): string { - * return myCamelCase(str) + * // ... + * + * return str * } * } * ``` diff --git a/src/plugin/immediate-value/immediate-value-transformer.ts b/src/plugin/immediate-value/immediate-value-transformer.ts index b313314c5..8b8fba9e1 100644 --- a/src/plugin/immediate-value/immediate-value-transformer.ts +++ b/src/plugin/immediate-value/immediate-value-transformer.ts @@ -10,7 +10,7 @@ import { ValueNode } from '../../operation-node/value-node.js' * @internal */ export class ImmediateValueTransformer extends OperationNodeTransformer { - transformValue(node: ValueNode): ValueNode { + override transformValue(node: ValueNode): ValueNode { return { ...super.transformValue(node), immediate: true, diff --git a/src/plugin/kysely-plugin.ts b/src/plugin/kysely-plugin.ts index 208bbcc8a..a825f7ab2 100644 --- a/src/plugin/kysely-plugin.ts +++ b/src/plugin/kysely-plugin.ts @@ -14,19 +14,41 @@ export interface KyselyPlugin { * can use a `WeakMap` with {@link PluginTransformQueryArgs.queryId | args.queryId} as the key: * * ```ts - * const plugin = { - * data: new WeakMap(), + * import type { + * KyselyPlugin, + * QueryResult, + * RootOperationNode, + * UnknownRow + * } from 'kysely' + * + * interface MyData { + * // ... + * } + * const data = new WeakMap() * + * const plugin = { * transformQuery(args: PluginTransformQueryArgs): RootOperationNode { - * this.data.set(args.queryId, something) + * const something: MyData = {} + * + * // ... + * + * data.set(args.queryId, something) + * + * // ... + * * return args.node * }, * - * transformResult(args: PluginTransformResultArgs): QueryResult { - * const data = this.data.get(args.queryId) + * async transformResult(args: PluginTransformResultArgs): Promise> { + * // ... + * + * const something = data.get(args.queryId) + * + * // ... + * * return args.result * } - * } + * } satisfies KyselyPlugin * ``` * * You should use a `WeakMap` instead of a `Map` or some other strong references because `transformQuery` diff --git a/src/plugin/parse-json-results/parse-json-results-plugin.ts b/src/plugin/parse-json-results/parse-json-results-plugin.ts index 4bad81553..17082591b 100644 --- a/src/plugin/parse-json-results/parse-json-results-plugin.ts +++ b/src/plugin/parse-json-results/parse-json-results-plugin.ts @@ -30,13 +30,43 @@ type ObjectStrategy = 'in-place' | 'create' * This plugin can be useful with dialects that don't automatically parse * JSON into objects and arrays but return JSON strings instead. * + * To apply this plugin globally, pass an instance of it to the `plugins` option + * when creating a new `Kysely` instance: + * * ```ts - * const db = new Kysely({ - * // ... - * plugins: [new ParseJSONResultsPlugin()] - * // ... + * import * as Sqlite from 'better-sqlite3' + * import { Kysely, ParseJSONResultsPlugin, SqliteDialect } from 'kysely' + * import type { Database } from 'type-editor' // imaginary module + * + * const db = new Kysely({ + * dialect: new SqliteDialect({ + * database: new Sqlite(':memory:'), + * }), + * plugins: [new ParseJSONResultsPlugin()], * }) * ``` + * + * To apply this plugin to a single query: + * + * ```ts + * import { ParseJSONResultsPlugin } from 'kysely' + * import { jsonArrayFrom } from 'kysely/helpers/sqlite' + * + * const result = await db + * .selectFrom('person') + * .select((eb) => [ + * 'id', + * 'first_name', + * 'last_name', + * jsonArrayFrom( + * eb.selectFrom('pet') + * .whereRef('owner_id', '=', 'person.id') + * .select(['name', 'species']) + * ).as('pets') + * ]) + * .withPlugin(new ParseJSONResultsPlugin()) + * .execute() + * ``` */ export class ParseJSONResultsPlugin implements KyselyPlugin { readonly #objectStrategy: ObjectStrategy diff --git a/src/plugin/with-schema/with-schema-transformer.ts b/src/plugin/with-schema/with-schema-transformer.ts index af14b0b3d..b326628b4 100644 --- a/src/plugin/with-schema/with-schema-transformer.ts +++ b/src/plugin/with-schema/with-schema-transformer.ts @@ -89,7 +89,7 @@ export class WithSchemaTransformer extends OperationNodeTransformer { } } - protected transformReferences(node: ReferencesNode): ReferencesNode { + protected override transformReferences(node: ReferencesNode): ReferencesNode { const transformed = super.transformReferences(node) if (transformed.table.table.schema) { diff --git a/src/query-builder/aggregate-function-builder.ts b/src/query-builder/aggregate-function-builder.ts index ebd559fbf..9aff159f8 100644 --- a/src/query-builder/aggregate-function-builder.ts +++ b/src/query-builder/aggregate-function-builder.ts @@ -113,7 +113,7 @@ export class AggregateFunctionBuilder * .selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') * .select((eb) => - * eb.fn.jsonAgg('pet.name').orderBy('pet.name').as('person_pets') + * eb.fn.jsonAgg('pet').orderBy('pet.name').as('person_pets') * ) * .executeTakeFirstOrThrow() * ``` @@ -121,7 +121,7 @@ export class AggregateFunctionBuilder * The generated SQL (PostgreSQL): * * ```sql - * select json_agg("pet"."name" order by "pet"."name") as "person_pets" + * select json_agg("pet" order by "pet"."name") as "person_pets" * from "person" * inner join "pet" ON "pet"."owner_id" = "person"."id" * ``` diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 0dde970a9..c8023e17c 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -300,7 +300,7 @@ export class DeleteQueryBuilder * .innerJoin('pet', 'pet.owner_id', 'person.id') * // `select` needs to come after the call to `innerJoin` so * // that you can select from the joined table. - * .select('person.id', 'pet.name') + * .select(['person.id', 'pet.name']) * .execute() * * result[0].id @@ -372,7 +372,7 @@ export class DeleteQueryBuilder * ```ts * await db.selectFrom('person') * .innerJoin( - * qb.selectFrom('pet') + * db.selectFrom('pet') * .select(['owner_id', 'name']) * .where('name', '=', 'Doggo') * .as('doggos'), @@ -536,7 +536,7 @@ export class DeleteQueryBuilder * Return all columns from all tables * * ```ts - * const result = ctx.db + * const result = await db * .deleteFrom('toy') * .using(['pet', 'person']) * .whereRef('toy.pet_id', '=', 'pet.id') @@ -560,7 +560,7 @@ export class DeleteQueryBuilder * Return all columns from a single table. * * ```ts - * const result = ctx.db + * const result = await db * .deleteFrom('toy') * .using(['pet', 'person']) * .whereRef('toy.pet_id', '=', 'pet.id') @@ -584,7 +584,7 @@ export class DeleteQueryBuilder * Return all columns from multiple tables. * * ```ts - * const result = ctx.db + * const result = await db * .deleteFrom('toy') * .using(['pet', 'person']) * .whereRef('toy.pet_id', '=', 'pet.id') @@ -677,10 +677,11 @@ export class DeleteQueryBuilder * ### Examples * * ```ts - * db.deleteFrom('pet') + * await db.deleteFrom('pet') * .returningAll() * .where('name', '=', 'Max') * .clearReturning() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -702,11 +703,12 @@ export class DeleteQueryBuilder * ### Examples * * ```ts - * db.deleteFrom('pet') + * await db.deleteFrom('pet') * .returningAll() * .where('name', '=', 'Max') * .limit(5) * .clearLimit() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -728,11 +730,12 @@ export class DeleteQueryBuilder * ### Examples * * ```ts - * db.deleteFrom('pet') + * await db.deleteFrom('pet') * .returningAll() * .where('name', '=', 'Max') * .orderBy('id') * .clearOrderBy() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -812,6 +815,12 @@ export class DeleteQueryBuilder * .limit(5) * .execute() * ``` + * + * The generated SQL (MySQL): + * + * ```sql + * delete from `pet` order by `created_at` limit ? + * ``` */ limit(limit: ValueExpression): DeleteQueryBuilder { return new DeleteQueryBuilder({ @@ -829,10 +838,12 @@ export class DeleteQueryBuilder * ### Examples * * ```ts + * import { sql } from 'kysely' + * * await db.deleteFrom('person') - * .where('first_name', '=', 'John') - * .modifyEnd(sql.raw('-- This is a comment')) - * .execute() + * .where('first_name', '=', 'John') + * .modifyEnd(sql`-- This is a comment`) + * .execute() * ``` * * The generated SQL (MySQL): @@ -864,12 +875,14 @@ export class DeleteQueryBuilder * The next example uses a helper function `log` to log a query: * * ```ts + * import type { Compilable } from 'kysely' + * * function log(qb: T): T { * console.log(qb.compile()) * return qb * } * - * db.deleteFrom('person') + * await db.deleteFrom('person') * .$call(log) * .execute() * ``` @@ -957,25 +970,33 @@ export class DeleteQueryBuilder * Turn this code: * * ```ts + * import type { Person } from 'type-editor' // imaginary module + * * const person = await db.deleteFrom('person') - * .where('id', '=', id) + * .where('id', '=', 3) * .where('nullable_column', 'is not', null) * .returningAll() * .executeTakeFirstOrThrow() * - * if (person.nullable_column) { + * if (isWithNoNullValue(person)) { * functionThatExpectsPersonWithNonNullValue(person) * } + * + * function isWithNoNullValue(person: Person): person is Person & { nullable_column: string } { + * return person.nullable_column != null + * } * ``` * * Into this: * * ```ts + * import type { NotNull } from 'kysely' + * * const person = await db.deleteFrom('person') - * .where('id', '=', id) + * .where('id', '=', 3) * .where('nullable_column', 'is not', null) * .returningAll() - * .$narrowType<{ nullable_column: string }>() + * .$narrowType<{ nullable_column: NotNull }>() * .executeTakeFirstOrThrow() * * functionThatExpectsPersonWithNonNullValue(person) @@ -1008,22 +1029,26 @@ export class DeleteQueryBuilder * ### Examples * * ```ts - * const result = await db - * .with('deleted_person', (qb) => qb - * .deleteFrom('person') - * .where('id', '=', person.id) - * .returning('first_name') - * .$assertType<{ first_name: string }>() - * ) - * .with('deleted_pet', (qb) => qb - * .deleteFrom('pet') - * .where('owner_id', '=', person.id) - * .returning(['name as pet_name', 'species']) - * .$assertType<{ pet_name: string, species: Species }>() - * ) - * .selectFrom(['deleted_person', 'deleted_pet']) - * .selectAll() - * .executeTakeFirstOrThrow() + * import type { Species } from 'type-editor' // imaginary module + * + * async function deletePersonAndPets(personId: number) { + * return await db + * .with('deleted_person', (qb) => qb + * .deleteFrom('person') + * .where('id', '=', personId) + * .returning('first_name') + * .$assertType<{ first_name: string }>() + * ) + * .with('deleted_pets', (qb) => qb + * .deleteFrom('pet') + * .where('owner_id', '=', personId) + * .returning(['name as pet_name', 'species']) + * .$assertType<{ pet_name: string, species: Species }>() + * ) + * .selectFrom(['deleted_person', 'deleted_pets']) + * .selectAll() + * .execute() + * } * ``` */ $assertType(): O extends T