커스텀 타입 정의하기

예제

customType 정의가 어떻게 작동하는지 확인하는 가장 좋은 방법은 Drizzle ORM의 customType 함수를 사용하여 기존 데이터 타입을 어떻게 정의할 수 있는지 살펴보는 것입니다.

info

각 dialect는 customType 함수를 제공합니다

import { customType } from 'drizzle-orm/pg-core';
...
import { customType } from 'drizzle-orm/mysql-core';
...
import { customType } from 'drizzle-orm/sqlite-core';
...
import { customType } from 'drizzle-orm/gel-core';
...
import { customType } from 'drizzle-orm/singlestore-core';

Integer

import { customType } from 'drizzle-orm/pg-core';

const customSerial = customType<{ data: number; }>(
  {
    dataType() {
      return 'integer';
    },
  },
);

Text

import { customType } from 'drizzle-orm/pg-core';

const customText = customType<{ data: string }>({
  dataType() {
    return 'text';
  },
});

Boolean

import { customType } from 'drizzle-orm/pg-core';

const customBoolean = customType<{ data: boolean }>({
  dataType() {
    return 'boolean';
  },
});

Jsonb

import { customType } from 'drizzle-orm/pg-core';

const customJsonb = <TData>(name: string) =>
  customType<{ data: TData; driverData: string }>({
    dataType() {
      return 'jsonb';
    },
    toDriver(value: TData): string {
      return JSON.stringify(value);
    },
  })(name);

Timestamp

import { customType } from 'drizzle-orm/pg-core';

const customTimestamp = customType<
  {
    data: Date;
    driverData: string;
    config: { withTimezone: boolean; precision?: number };
  }
>({
  dataType(config) {
    const precision = typeof config.precision !== 'undefined'
      ? ` (${config.precision})`
      : '';
    return `timestamp${precision}${
      config.withTimezone ? ' with time zone' : ''
    }`;
  },
  fromDriver(value: string): Date {
    return new Date(value);
  },
});

모든 타입의 사용법은 Drizzle ORM에 정의된 함수와 동일합니다. 예를 들어:

const usersTable = pgTable('users', {
  id: customSerial('id').primaryKey(),
  name: customText('name').notNull(),
  verified: customBoolean('verified').notNull().default(false),
  jsonb: customJsonb<string[]>('jsonb'),
  createdAt: customTimestamp('created_at', { withTimezone: true }).notNull()
    .default(sql`now()`),
});

타입 정의를 위한 TS-doc

typesparam 정의에 대한 ts-doc을 확인할 수 있습니다.

export interface CustomTypeValues = {
  /**
   * 커스텀 컬럼에 필요한 타입으로, 적절한 타입 모델을 추론합니다
   *
   * 예시:
   *
   * 조회/삽입 후 컬럼이 `string` 타입이 되도록 하려면 - `data: string`을 사용합니다. `text`, `varchar`처럼
   *
   * 조회/삽입 후 컬럼이 `number` 타입이 되도록 하려면 - `data: number`를 사용합니다. `integer`처럼
   */
  data: unknown;

  /**
 	 * 특정 데이터베이스 데이터 타입에 대해 데이터베이스 드라이버가 반환하는 타입을 나타내는 타입 헬퍼
 	 *
 	 * 드라이버의 출력과 입력 타입이 다른 경우에만 필요
 	 *
 	 * 기본값은 {@link driverData}
 	 */
 	driverOutput?: unknown;

  /**
   * 특정 데이터베이스 데이터 타입에 대해 데이터베이스 드라이버가 허용하는 타입을 나타내는 타입 헬퍼
   */
  driverData?: unknown;

  /**
 	 * Relational Queries에서 JSON으로 집계된 후 필드가 반환하는 타입을 나타내는 타입 헬퍼
 	 */
 	jsonData?: unknown;

  /**
   * {@link CustomTypeParams} `dataType` 생성에 사용해야 하는 설정 타입
   */
  config?: Record<string, unknown>;

  /**
   * 커스텀 데이터 타입이 기본적으로 notNull이어야 하는 경우 `notNull: true`를 사용할 수 있습니다
   *
   * @example
   * const customSerial = customType<{ data: number, notNull: true, default: true }>({
   *    dataType() {
   *      return 'serial';
   *    },
   * });
   */
  notNull?: boolean;

  /**
   * 커스텀 데이터 타입이 기본값을 가지는 경우 `default: true`를 사용할 수 있습니다
   *
   * @example
   * const customSerial = customType<{ data: number, notNull: true, default: true }>({
   *    dataType() {
   *      return 'serial';
   *    },
   * });
   */
  default?: boolean;
};

export interface CustomTypeParams<T extends CustomTypeValues> {
  /**
   * 마이그레이션에 사용되는 데이터베이스 데이터 타입의 문자열 표현
   * @example
   * ```
   * `jsonb`, `text`
   * ```
   *
   * 데이터베이스 데이터 타입에 추가 파라미터가 필요한 경우 `config` 파라미터에서 사용할 수 있습니다
   * @example
   * ```
   * `varchar(256)`, `numeric(2,3)`
   * ```
   *
   * `config`를 특정 타입으로 만들려면 {@link CustomTypeValues}에서 config 제네릭을 사용하세요
   *
   * @example
   * 사용 예시
   * ```
   *   dataType() {
   *     return 'boolean';
   *   },
   * ```
   * 또는
   * ```
   *   dataType(config) {
   *     return typeof config.length !== 'undefined' ? `varchar(${config.length})` : `varchar`;
   *   }
   * ```
   */
  dataType: (config: T['config']) => string;

  /**
   * 사용자 입력과 데이터베이스 드라이버가 데이터베이스에 제공할 값 사이의 선택적 매핑 함수
   * @example
   * 예를 들어, jsonb를 사용할 때 데이터베이스에 쓰기 전에 JS/TS 객체를 문자열로 매핑해야 합니다
   * ```
   * toDriver(value: TData): string {
   *   return JSON.stringify(value);
   * }
   * ```
   */
  toDriver?: (value: T['data']) => T['driverData'];

  /**
   * 드라이버가 반환한 데이터를 원하는 컬럼 출력 형식으로 변환하는 데 사용되는 선택적 매핑 함수
   * @example
   * 예를 들어, timestamp를 사용할 때 문자열 Date 표현을 JS Date로 매핑해야 합니다
   * ```
   * fromDriver(value: string): Date {
   *  return new Date(value);
   * },
   * ```
   *
   * 이렇게 하면 반환된 데이터가 다음에서:
 	 * ```
 	 * {
 	 * 	customField: "2025-04-07T03:25:16.635Z";
 	 * }
 	 * ```
 	 * 다음으로 변경됩니다:
 	 * ```
 	 * {
 	 * 	customField: new Date("2025-04-07T03:25:16.635Z");
 	 * }
 	 * ```
   */
  fromDriver?: (value: T['driverData']) => T['data'];

  	/**
 	 * 데이터베이스 데이터에서 JSON으로 변환된 데이터를 원하는 형식으로 변환하는 데 사용되는 선택적 매핑 함수
 	 *
 	 * [relational queries](https://orm.drizzle.team/docs/rqb-v2)에서 사용됩니다
 	 *
 	 * 기본값은 {@link fromDriver} 함수입니다
 	 * @example
 	 * 예를 들어, [RQB](https://orm.drizzle.team/docs/rqb-v2) 또는 [JSON functions](https://orm.drizzle.team/docs/json-functions)를 통해 bigint 컬럼을 쿼리할 때, 결과 필드는 일반 쿼리에서 반환되는 bigint와 달리 문자열 표현으로 반환됩니다
 	 * 이를 처리하기 위해 별도의 함수가 필요합니다:
 	 * ```
 	 * fromJson(value: string): bigint {
 	 * 	return BigInt(value);
 	 * },
 	 * ```
 	 *
 	 * 이렇게 하면 반환된 데이터가 다음에서:
 	 * ```
 	 * {
 	 * 	customField: "5044565289845416380";
 	 * }
 	 * ```
 	 * 다음으로 변경됩니다:
 	 * ```
 	 * {
 	 * 	customField: 5044565289845416380n;
 	 * }
 	 * ```
 	 */
 	fromJson?: (value: T['jsonData']) => T['data'];

  	/**
 	 * [JSON functions](https://orm.drizzle.team/docs/json-functions) 내에서 컬럼 선택을 수정하는 데 사용되는 선택적 선택 수정 함수
 	 *
 	 * 이러한 시나리오에 필요할 수 있는 추가 매핑은 {@link fromJson} 함수를 사용하여 처리할 수 있습니다
 	 *
 	 * [relational queries](https://orm.drizzle.team/docs/rqb-v2)에서 사용됩니다
 	 * @example
 	 * 예를 들어, bigint를 사용할 때 데이터 무결성을 보존하기 위해 필드를 text로 캐스팅해야 합니다
 	 * ```
 	 * forJsonSelect(identifier: SQL, sql: SQLGenerator, arrayDimensions?: number): SQL {
 	 * 	return sql`${identifier}::text`
 	 * },
 	 * ```
 	 *
 	 * 이렇게 하면 쿼리가 다음에서:
 	 * ```
 	 * SELECT
 	 * 	row_to_json("t".*)
 	 * 	FROM
 	 * 	(
 	 * 		SELECT
 	 * 		"table"."custom_bigint" AS "bigint"
 	 * 		FROM
 	 * 		"table"
 	 * 	) AS "t"
 	 * ```
 	 * 다음으로 변경됩니다:
 	 * ```
 	 * SELECT
 	 * 	row_to_json("t".*)
 	 * 	FROM
 	 * 	(
 	 * 		SELECT
 	 * 		"table"."custom_bigint"::text AS "bigint"
 	 * 		FROM
 	 * 		"table"
 	 * 	) AS "t"
 	 * ```
 	 *
 	 * 쿼리 객체가 반환하는 값이 다음에서:
 	 * ```
 	 * {
 	 * 	bigint: 5044565289845416000; // JSON 형식으로 직접 변환으로 인한 부분 데이터 손실
 	 * }
 	 * ```
 	 * 다음으로 변경됩니다:
 	 * ```
 	 * {
 	 * 	bigint: "5044565289845416380"; // JSON화 전 필드를 text로 변환하여 데이터 보존
 	 * }
 	 * ```
 	 */
 	forJsonSelect?: (identifier: SQL, sql: SQLGenerator, arrayDimensions?: number) => SQL;
}