Skip to content

kanel

Source of truth: PostgreSQL

Turn your Postgres schema into Typescript types that look like this:

ts
// @generated
// This file is automatically generated by Kanel. Do not modify manually.

import { countryId, type CountryId } from './Country.js';
import { z } from 'zod';

/** Identifier type for city */
export type CityId = number & { __flavor?: 'CityId' };

/** Represents the table public.city */
export default interface City {
  /** Database type: pg_catalog.int4 */
  city_id: CityId;

  /** Database type: pg_catalog.varchar */
  city: string;

  /** Database type: pg_catalog.int2 */
  country_id: CountryId;

  /** Database type: pg_catalog.timestamp */
  last_update: Date;
}

/** Represents the initializer for the table public.city */
export interface CityInitializer {
  /**
   * Database type: pg_catalog.int4
   * Default value: nextval('city_city_id_seq'::regclass)
   */
  city_id?: CityId;

  /** Database type: pg_catalog.varchar */
  city: string;

  /** Database type: pg_catalog.int2 */
  country_id: CountryId;

  /**
   * Database type: pg_catalog.timestamp
   * Default value: now()
   */
  last_update?: Date;
}

/** Represents the mutator for the table public.city */
export interface CityMutator {
  /** Database type: pg_catalog.int4 */
  city_id?: CityId;

  /** Database type: pg_catalog.varchar */
  city?: string;

  /** Database type: pg_catalog.int2 */
  country_id?: CountryId;

  /** Database type: pg_catalog.timestamp */
  last_update?: Date;
}

export const cityId = z.number() as unknown as z.Schema<CityId>;

export const city = z.object({
  city_id: cityId,
  city: z.string(),
  country_id: countryId,
  last_update: z.date(),
}) as unknown as z.Schema<City>;

export const cityInitializer = z.object({
  city_id: cityId.optional(),
  city: z.string(),
  country_id: countryId,
  last_update: z.date().optional(),
}) as unknown as z.Schema<CityInitializer>;

export const cityMutator = z.object({
  city_id: cityId.optional(),
  city: z.string().optional(),
  country_id: countryId.optional(),
  last_update: z.date().optional(),
}) as unknown as z.Schema<CityMutator>;

It does this by inspecting a live PostgreSQL database, sort of like a reverse object/relations mapper.

You check the generated code into your repository and work with it using Knex.js or similar.

The idea is introduced in this blog post.


If you want to learn about how I use this together with tRPC to create end-to-end type safety with PostgreSQL as the source of truth, check out my course on Newline:


Copyright © 2018 Kristian Dupont, licensed under the MIT License

Cinnamon