Skip to main content

Type Signature

type Updateable<R> = DrainOuterGeneric<{
  [K in UpdateKeys<R>]?: UpdateType<R[K]>
}>

Overview

Updateable<T> is a utility type that extracts the update type from all columns in a table interface. It resolves the UpdateType from any ColumnType definitions and creates a type representing what you can update in the table.

Type Parameters

R
interface
required
A table interface with column definitions.

Behavior

  • Extracts the third type parameter (UpdateType) from each ColumnType<SelectType, InsertType, UpdateType>
  • For regular types without ColumnType, uses the type as-is
  • All fields are optional (since you typically update only some columns)
  • Excludes columns where UpdateType is never

Examples

Basic Usage

import { Generated, ColumnType, Updateable } from 'kysely'

interface PersonTable {
  id: Generated<number>
  first_name: string
  last_name: string
  created_at: ColumnType<Date, string, never>
}

type UpdateablePerson = Updateable<PersonTable>
// {
//   id?: number
//   first_name?: string
//   last_name?: string
//   // created_at excluded (UpdateType is never)
// }
Notice:
  • All fields are optional (updates are partial by nature)
  • created_at is excluded because its UpdateType is never

Using with Update Queries

interface Database {
  person: PersonTable
}

const db = new Kysely<Database>({ /* ... */ })

// Type-safe update
const updates: UpdateablePerson = {
  first_name: 'John',
  last_name: 'Doe'
}

await db
  .updateTable('person')
  .set(updates)
  .where('id', '=', 1)
  .execute()

// Or inline
await db
  .updateTable('person')
  .set({
    last_name: 'Smith'
  })
  .where('id', '=', 1)
  .execute()

Read-Only Columns

interface PostTable {
  id: GeneratedAlways<number>
  title: string
  content: string
  created_at: ColumnType<Date, string, never>
  updated_at: ColumnType<Date, string, string>
}

type UpdateablePost = Updateable<PostTable>
// {
//   title?: string
//   content?: string
//   updated_at?: string
//   // id excluded (GeneratedAlways has UpdateType = never)
//   // created_at excluded (UpdateType = never)
// }

await db
  .updateTable('post')
  .set({
    title: 'New Title',
    updated_at: new Date().toISOString()
  })
  .where('id', '=', 1)
  .execute()

// Type error - can't update read-only columns
await db
  .updateTable('post')
  .set({
    id: 999,           // Error!
    created_at: '2024-01-01'  // Error!
  })
  .where('id', '=', 1)
  .execute()

Different Types for Update

interface EventTable {
  id: Generated<number>
  name: string
  // Accept string for updates, but return Date for select
  event_date: ColumnType<Date, string, string>
  // JSON column: accept string, return object
  metadata: ColumnType<{ tags: string[] }, string, string>
}

type UpdateableEvent = Updateable<EventTable>
// {
//   id?: number
//   name?: string
//   event_date?: string
//   metadata?: string
// }

await db
  .updateTable('event')
  .set({
    name: 'Updated Conference',
    event_date: '2024-12-15',
    metadata: JSON.stringify({ tags: ['tech', 'updated'] })
  })
  .where('id', '=', 1)
  .execute()

All Fields Optional

Unlike Insertable, all fields in Updateable are optional since updates are typically partial:
interface UserTable {
  id: Generated<number>
  username: string     // Required in Insertable
  email: string        // Required in Insertable
  bio: string | null
}

type UpdateableUser = Updateable<UserTable>
// {
//   id?: number
//   username?: string    // Optional in Updateable
//   email?: string       // Optional in Updateable
//   bio?: string | null
// }

// Valid - update only one field
await db
  .updateTable('user')
  .set({ bio: 'New bio' })
  .where('id', '=', 1)
  .execute()

// Valid - update multiple fields
await db
  .updateTable('user')
  .set({
    username: 'newusername',
    email: '[email protected]'
  })
  .where('id', '=', 1)
  .execute()

Common Patterns

Function Parameters

import { Selectable, Updateable } from 'kysely'

type Person = Selectable<PersonTable>
type PersonUpdate = Updateable<PersonTable>

async function updatePerson(
  id: number,
  updates: PersonUpdate
): Promise<Person> {
  return await db
    .updateTable('person')
    .set(updates)
    .where('id', '=', id)
    .returningAll()
    .executeTakeFirstOrThrow()
}

// Usage
const updated = await updatePerson(1, {
  first_name: 'Jane'
})

Partial Updates with Type Safety

type PersonUpdate = Updateable<PersonTable>

function validateUpdate(updates: PersonUpdate) {
  if (updates.first_name !== undefined && updates.first_name.length === 0) {
    throw new Error('First name cannot be empty')
  }
  if (updates.last_name !== undefined && updates.last_name.length === 0) {
    throw new Error('Last name cannot be empty')
  }
}

const updates: PersonUpdate = { first_name: 'John' }
validateUpdate(updates)

await db
  .updateTable('person')
  .set(updates)
  .where('id', '=', 1)
  .execute()

Conditional Updates

type PersonUpdate = Updateable<PersonTable>

async function conditionalUpdate(id: number) {
  const updates: PersonUpdate = {}

  const shouldUpdateName = true
  if (shouldUpdateName) {
    updates.first_name = 'NewName'
  }

  const shouldUpdateEmail = false
  if (shouldUpdateEmail) {
    updates.email = '[email protected]'
  }

  if (Object.keys(updates).length > 0) {
    await db
      .updateTable('person')
      .set(updates)
      .where('id', '=', id)
      .execute()
  }
}

Combining with Omit for Restricted Updates

// Prevent updating certain fields
type SafePersonUpdate = Omit<Updateable<PersonTable>, 'id'>

function updatePersonSafely(id: number, updates: SafePersonUpdate) {
  return db
    .updateTable('person')
    .set(updates)
    .where('id', '=', id)
    .execute()
}

// Type error - can't update id
updatePersonSafely(1, {
  id: 999,  // Error!
  first_name: 'John'
})

Repository Pattern

import { Selectable, Insertable, Updateable } from 'kysely'

type Person = Selectable<PersonTable>
type NewPerson = Insertable<PersonTable>
type PersonUpdate = Updateable<PersonTable>

class PersonRepository {
  async create(person: NewPerson): Promise<Person> {
    return await db
      .insertInto('person')
      .values(person)
      .returningAll()
      .executeTakeFirstOrThrow()
  }

  async update(id: number, updates: PersonUpdate): Promise<Person> {
    return await db
      .updateTable('person')
      .set(updates)
      .where('id', '=', id)
      .returningAll()
      .executeTakeFirstOrThrow()
  }

  async findById(id: number): Promise<Person | undefined> {
    return await db
      .selectFrom('person')
      .selectAll()
      .where('id', '=', id)
      .executeTakeFirst()
  }
}

Source

View source on GitHub

Build docs developers (and LLMs) love