drizzle-typebox
drizzle-typebox
는 **Drizzle ORM **을 위한 플러그인입니다. 이 플러그인을 사용하면 Drizzle ORM 스키마에서 Typebox 스키마를 생성할 수 있습니다.
의존성 설치
npm i drizzle-typebox
yarn add drizzle-typebox
pnpm add drizzle-typebox
bun add drizzle-typebox
IMPORTANT
이 문서는 drizzle-typebox@0.2.0
이상 버전을 기준으로 작성되었습니다.
Drizzle ORM v0.36.0 이상과 Typebox v0.34.8 이상 버전이 설치되어 있어야 합니다.
데이터 선택 스키마 정의
데이터베이스에서 쿼리한 데이터의 형태를 정의합니다. 이를 통해 API 응답을 검증할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-typebox' ;
import { Value } from '@sinclair/typebox/value' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userSelectSchema = createSelectSchema (users);
const rows = await db .select ({ id : users .id , name : users .name }) .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = Value .Parse (userSelectSchema , rows[ 0 ]); // 오류: 위 쿼리에서 `age`가 반환되지 않음
const rows = await db .select () .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = Value .Parse (userSelectSchema , rows[ 0 ]); // 성공적으로 파싱됨
뷰와 열거형(enum)도 지원됩니다.
import { pgEnum } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-typebox' ;
import { Value } from '@sinclair/typebox/value' ;
const roles = pgEnum ( 'roles' , [ 'admin' , 'basic' ]);
const rolesSchema = createSelectSchema (roles);
const parsed : 'admin' | 'basic' = Value .Parse (rolesSchema , ... );
const usersView = pgView ( 'users_view' ) .as ((qb) => qb .select () .from (users) .where ( gt ( users .age , 18 )));
const usersViewSchema = createSelectSchema (usersView);
const parsed : { id : number ; name : string ; age : number } = Value .Parse (usersViewSchema , ... );
데이터 삽입 스키마 정의
데이터베이스에 삽입할 데이터의 구조를 정의합니다. 이 스키마는 API 요청을 검증하는 데 사용할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createInsertSchema } from 'drizzle-typebox' ;
import { Value } from '@sinclair/typebox/value' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userInsertSchema = createInsertSchema (users);
const user = { name : 'John' };
const parsed : { name : string , age : number } = Value .Parse (userInsertSchema , user); // 오류: `age`가 정의되지 않음
const user = { name : 'Jane' , age : 30 };
const parsed : { name : string , age : number } = Value .Parse (userInsertSchema , user); // 성공적으로 파싱됨
await db .insert (users) .values (parsed);
스키마 업데이트
데이터베이스에서 업데이트할 데이터의 구조를 정의합니다. 이를 통해 API 요청을 검증할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createUpdateSchema } from 'drizzle-typebox' ;
import { Value } from '@sinclair/typebox/value' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userUpdateSchema = createUpdateSchema (users);
const user = { id : 5 , name : 'John' };
const parsed : { name ?: string | undefined , age ?: number | undefined } = Value .Parse (userUpdateSchema , user); // 오류: `id`는 자동 생성 컬럼이므로 업데이트할 수 없음
const user = { age : 35 };
const parsed : { name ?: string | undefined , age ?: number | undefined } = Value .Parse (userUpdateSchema , user); // 성공적으로 파싱됨
await db .update (users) .set (parsed) .where ( eq ( users .name , 'Jane' ));
스키마 세부 조정
각 create schema
함수는 필드의 스키마를 확장, 수정 또는 완전히 덮어쓸 수 있는 추가적인 선택적 매개변수를 받습니다. 콜백 함수를 정의하면 스키마를 확장하거나 수정할 수 있고, Typebox 스키마를 제공하면 완전히 덮어쓸 수 있습니다.
import { pgTable , text , integer , json } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-typebox' ;
import { Type } from '@sinclair/typebox' ;
import { Value } from '@sinclair/typebox/value' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
bio : text () ,
preferences : json ()
});
const userSelectSchema = createSelectSchema (users , {
name : (schema) => Type .String ({ ... schema , maxLength : 20 }) , // 스키마 확장
bio : (schema) => Type .String ({ ... schema , maxLength : 1000 }) , // 스키마 확장 후 nullable/optional로 변경
preferences : Type .Object ({ theme : Type .String () }) // 필드 덮어쓰기 (nullability 포함)
});
const parsed : {
id : number ;
name : string ,
bio ?: string | undefined ;
preferences : {
theme : string ;
};
} = Value .Parse (userSelectSchema , ... );
이 예제에서는 createSelectSchema
함수를 사용하여 users
테이블의 스키마를 생성하고, 각 필드에 대해 추가적인 조정을 적용했습니다. name
필드는 최대 길이를 20으로 제한하고, bio
필드는 최대 길이를 1000으로 제한한 후 nullable로 설정했습니다. preferences
필드는 완전히 새로운 스키마로 덮어쓰여졌습니다.
팩토리 함수
더 복잡한 사용 사례를 위해 createSchemaFactory
함수를 사용할 수 있습니다.
사용 사례: 확장된 Typebox 인스턴스 사용하기
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSchemaFactory } from 'drizzle-typebox' ;
import { t } from 'elysia' ; // 확장된 Typebox 인스턴스
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const { createInsertSchema } = createSchemaFactory ({ typeboxInstance : t });
const userInsertSchema = createInsertSchema (users , {
// 이제 확장된 인스턴스를 사용할 수 있습니다
name : (schema) => t .Number ({ ... schema } , { error : '`name`은 문자열이어야 합니다' })
});
데이터 타입 참조
pg .boolean ();
mysql .boolean ();
sqlite .integer ({ mode : 'boolean' });
// 스키마
Type .Boolean ();
pg .date ({ mode : 'date' });
pg .timestamp ({ mode : 'date' });
mysql .date ({ mode : 'date' });
mysql .datetime ({ mode : 'date' });
mysql .timestamp ({ mode : 'date' });
sqlite .integer ({ mode : 'timestamp' });
sqlite .integer ({ mode : 'timestamp_ms' });
// 스키마
Type .Date ();
pg .date ({ mode : 'string' });
pg .timestamp ({ mode : 'string' });
pg .cidr ();
pg .inet ();
pg .interval ();
pg .macaddr ();
pg .macaddr8 ();
pg .numeric ();
pg .text ();
pg .sparsevec ();
pg .time ();
mysql .binary ();
mysql .date ({ mode : 'string' });
mysql .datetime ({ mode : 'string' });
mysql .decimal ();
mysql .time ();
mysql .timestamp ({ mode : 'string' });
mysql .varbinary ();
sqlite .numeric ();
sqlite .text ({ mode : 'text' });
// 스키마
Type .String ();
pg .bit ({ dimensions : ... });
// 스키마
t .RegExp ( / ^ [01] +$ / , { maxLength : dimensions });
pg .uuid ();
// 스키마
Type .String ({ format : 'uuid' });
pg .char ({ length : ... });
mysql .char ({ length : ... });
// 스키마
Type .String ({ minLength : length , maxLength : length });
pg .varchar ({ length : ... });
mysql .varchar ({ length : ... });
sqlite .text ({ mode : 'text' , length : ... });
// 스키마
Type .String ({ maxLength : length });
mysql .tinytext ();
// 스키마
Type .String ({ maxLength : 255 }); // 8비트 정수 상한
mysql .text ();
// 스키마
Type .String ({ maxLength : 65_535 }); // 16비트 정수 상한
mysql .mediumtext ();
// 스키마
Type .String ({ maxLength : 16_777_215 }); // 24비트 정수 상한
mysql .longtext ();
// 스키마
Type .String ({ maxLength : 4_294_967_295 }); // 32비트 정수 상한
pg .text ({ enum : ... });
pg .char ({ enum : ... });
pg .varchar ({ enum : ... });
mysql .tinytext ({ enum : ... });
mysql .mediumtext ({ enum : ... });
mysql .text ({ enum : ... });
mysql .longtext ({ enum : ... });
mysql .char ({ enum : ... });
mysql .varchar ({ enum : ... });
mysql .mysqlEnum ( ... , ... );
sqlite .text ({ mode : 'text' , enum : ... });
// 스키마
Type .Enum (enum);
mysql .tinyint ();
// 스키마
Type .Integer ({ minimum : - 128 , maximum : 127 }); // 8비트 정수 하한 및 상한
mysql .tinyint ({ unsigned : true });
// 스키마
Type .Integer ({ minimum : 0 , maximum : 255 }); // 부호 없는 8비트 정수 하한 및 상한
pg .smallint ();
pg .smallserial ();
mysql .smallint ();
// 스키마
Type .Integer ({ minimum : - 32_768 , maximum : 32_767 }); // 16비트 정수 하한 및 상한
mysql .smallint ({ unsigned : true });
// 스키마
Type .Integer ({ minimum : 0 , maximum : 65_535 }); // 부호 없는 16비트 정수 하한 및 상한
pg .real ();
mysql .float ();
// 스키마
Type .Number () .min ( - 8_388_608 ) .max ( 8_388_607 ); // 24비트 정수 하한 및 상한
mysql .mediumint ();
// 스키마
Type .Integer ({ minimum : - 8_388_608 , maximum : 8_388_607 }); // 24비트 정수 하한 및 상한
mysql .float ({ unsigned : true });
// 스키마
Type .Number ({ minimum : 0 , maximum : 16_777_215 }); // 부호 없는 24비트 정수 상한
mysql .mediumint ({ unsigned : true });
// 스키마
Type .Integer ({ minimum : 0 , maximum : 16_777_215 }); // 부호 없는 24비트 정수 상한
pg .integer ();
pg .serial ();
mysql .int ();
// 스키마
Type .Integer ({ minimum : - 2_147_483_648 , maximum : 2_147_483_647 }); // 32비트 정수 하한 및 상한
mysql .int ({ unsigned : true });
// 스키마
Type .Integer ({ minimum : 0 , maximum : 4_294_967_295 }); // 부호 없는 32비트 정수 상한
pg .doublePrecision ();
mysql .double ();
mysql .real ();
sqlite .real ();
// 스키마
Type .Number ({ minimum : - 140_737_488_355_328 , maximum : 140_737_488_355_327 }); // 48비트 정수 하한 및 상한
mysql .double ({ unsigned : true });
// 스키마
Type .Number ({ minimum : 0 , maximum : 281_474_976_710_655 }); // 부호 없는 48비트 정수 상한
pg .bigint ({ mode : 'number' });
pg .bigserial ({ mode : 'number' });
mysql .bigint ({ mode : 'number' });
mysql .bigserial ({ mode : 'number' });
sqlite .integer ({ mode : 'number' });
// 스키마
Type .Integer ({ minimum : - 9_007_199_254_740_991 , maximum : 9_007_199_254_740_991 }); // 자바스크립트 안전 정수 범위
mysql .serial ();
// 스키마
Type .Integer ({ minimum : 0 , maximum : 9_007_199_254_740_991 }); // 자바스크립트 최대 안전 정수
pg .bigint ({ mode : 'bigint' });
pg .bigserial ({ mode : 'bigint' });
mysql .bigint ({ mode : 'bigint' });
sqlite .blob ({ mode : 'bigint' });
// 스키마
Type .BigInt ({ minimum : - 9_223_372_036_854_775_808 n , maximum : 9_223_372_036_854_775_807 n }); // 64비트 정수 하한 및 상한
mysql .bigint ({ mode : 'bigint' , unsigned : true });
// 스키마
Type .BigInt ({ minimum : 0 , maximum : 18_446_744_073_709_551_615 n }); // 부호 없는 64비트 정수 상한
mysql .year ();
// 스키마
Type .Integer ({ minimum : 1_901 , maximum : 2_155 });
pg .geometry ({ type : 'point' , mode : 'tuple' });
pg .point ({ mode : 'tuple' });
// 스키마
Type .Tuple ([ Type .Number () , Type .Number ()]);
pg .geometry ({ type : 'point' , mode : 'xy' });
pg .point ({ mode : 'xy' });
// 스키마
Type .Object ({ x : Type .Number () , y : Type .Number () });
pg .halfvec ({ dimensions : ... });
pg .vector ({ dimensions : ... });
// 스키마
Type .Array ( Type .Number () , { minItems : dimensions , maxItems : dimensions });
pg .line ({ mode : 'abc' });
// 스키마
Type .Object ({ a : Type .Number () , b : Type .Number () , c : Type .Number () });
pg .line ({ mode : 'tuple' });
// 스키마
Type .Tuple ([ Type .Number () , Type .Number () , Type .Number ()]);
pg .json ();
pg .jsonb ();
mysql .json ();
sqlite .blob ({ mode : 'json' });
sqlite .text ({ mode : 'json' });
// 스키마
Type .Recursive ((self) => Type .Union ([ Type .Union ([ Type .String () , Type .Number () , Type .Boolean () , Type .Null ()]) , Type .Array (self) , Type .Record ( Type .String () , self)]));
sqlite .blob ({ mode : 'buffer' });
// 스키마
TypeRegistry .Set ( 'Buffer' , (_ , value) => value instanceof Buffer ); // drizzle-typebox는 이 메서드를 실행하여 Typebox의 타입 시스템에 추가
{ [Kind]: 'Buffer' , type : 'buffer' }; // Typebox 스키마
pg .dataType () .array ( ... );
// 스키마
Type .Array (baseDataTypeSchema , { minItems : size , maxItems : size });