drizzle-valibot
drizzle-valibot
는 **Drizzle ORM **을 위한 플러그인으로, Drizzle ORM 스키마에서 Valibot 스키마를 생성할 수 있게 해줍니다.
의존성 설치하기
npm i drizzle-valibot
yarn add drizzle-valibot
pnpm add drizzle-valibot
bun add drizzle-valibot
IMPORTANT
이 문서는 drizzle-valibot@0.3.0
이상 버전을 기준으로 작성되었습니다.
Drizzle ORM v0.36.0 이상과 Valibot v1.0.0-beta.7 이상 버전도 함께 설치해야 합니다.
데이터 선택 스키마 정의
데이터베이스에서 쿼리한 데이터의 형태를 정의합니다. 이를 통해 API 응답을 검증할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-valibot' ;
import { parse } from 'valibot' ;
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 } = parse (userSelectSchema , rows[ 0 ]); // 오류: 위 쿼리에서 `age`가 반환되지 않음
const rows = await db .select () .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = parse (userSelectSchema , rows[ 0 ]); // 성공적으로 파싱됨
뷰와 열거형도 지원됩니다.
import { pgEnum } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-valibot' ;
import { parse } from 'valibot' ;
const roles = pgEnum ( 'roles' , [ 'admin' , 'basic' ]);
const rolesSchema = createSelectSchema (roles);
const parsed : 'admin' | 'basic' = 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 } = parse (usersViewSchema , ... );
데이터 삽입 스키마 정의
데이터베이스에 삽입할 데이터의 구조를 정의합니다. 이 스키마는 API 요청을 검증하는 데 사용할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createInsertSchema } from 'drizzle-valibot' ;
import { parse } from 'valibot' ;
// 사용자 테이블 정의
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 } = parse (userInsertSchema , user); // 오류: `age`가 정의되지 않음
// 올바른 데이터 예제
const user = { name : 'Jane' , age : 30 };
const parsed : { name : string , age : number } = parse (userInsertSchema , user); // 성공적으로 파싱됨
await db .insert (users) .values (parsed); // 데이터베이스에 삽입
이 코드는 사용자 데이터를 데이터베이스에 삽입하기 전에 스키마를 통해 검증하는 과정을 보여줍니다. createInsertSchema
를 사용해 스키마를 생성하고, parse
함수로 데이터를 검증합니다. 필수 필드가 누락된 경우 오류가 발생하며, 모든 필드가 올바르게 정의된 경우 데이터베이스에 삽입됩니다.
스키마 업데이트
데이터베이스에서 업데이트할 데이터의 구조를 정의합니다. 이를 통해 API 요청을 검증할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createUpdateSchema } from 'drizzle-valibot' ;
import { parse } from 'valibot' ;
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 } = parse (userUpdateSchema , user); // 오류: `id`는 자동 생성 컬럼이므로 업데이트할 수 없음
const user = { age : 35 };
const parsed : { name ?: string | undefined , age ?: number | undefined } = parse (userUpdateSchema , user); // 성공적으로 파싱됨
await db .update (users) .set (parsed) .where ( eq ( users .name , 'Jane' ));
개선 사항
각 create schema
함수는 필드의 스키마를 확장하거나 수정, 완전히 덮어쓸 수 있는 추가적인 선택적 매개변수를 받습니다. 콜백 함수를 정의하면 스키마를 확장하거나 수정할 수 있고, Valibot 스키마를 제공하면 해당 필드를 완전히 덮어씁니다.
import { pgTable , text , integer , json } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-valibot' ;
import { parse , pipe , maxLength , object , string } from 'valibot' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
bio : text () ,
preferences : json ()
});
const userSelectSchema = createSelectSchema (users , {
name : (schema) => pipe (schema , maxLength ( 20 )) , // 스키마 확장
bio : (schema) => pipe (schema , maxLength ( 1000 )) , // 스키마 확장 후 nullable/optional로 변경
preferences : object ({ theme : string () }) // 필드 덮어쓰기 (nullability 포함)
});
const parsed : {
id : number ;
name : string ,
bio ?: string | undefined ;
preferences : {
theme : string ;
};
} = parse (userSelectSchema , ... );
데이터 타입 참조
pg .boolean ();
mysql .boolean ();
sqlite .integer ({ mode : 'boolean' });
// 스키마
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' });
// 스키마
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' });
// 스키마
string ();
pg .bit ({ dimensions : ... });
// 스키마
pipe ( string () , regex ( / ^ [01] +$ / ) , maxLength (dimensions));
pg .uuid ();
// 스키마
pipe ( string () , uuid ());
pg .char ({ length : ... });
mysql .char ({ length : ... });
// 스키마
pipe ( string () , length (length));
pg .varchar ({ length : ... });
mysql .varchar ({ length : ... });
sqlite .text ({ mode : 'text' , length : ... });
// 스키마
pipe ( string () , maxLength (length));
mysql .tinytext ();
// 스키마
pipe ( string () , maxLength ( 255 )); // 부호 없는 8비트 정수 한계
mysql .text ();
// 스키마
pipe ( string () , maxLength ( 65_535 )); // 부호 없는 16비트 정수 한계
mysql .mediumtext ();
// 스키마
pipe ( string () , maxLength ( 16_777_215 )); // 부호 없는 24비트 정수 한계
mysql .longtext ();
// 스키마
pipe ( 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 : ... });
// 스키마
enum (enum);
mysql .tinyint ();
// 스키마
pipe ( number () , minValue ( - 128 ) , maxValue ( 127 ) , integer ()); // 8비트 정수 하한 및 상한
mysql .tinyint ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 255 ) , integer ()); // 부호 없는 8비트 정수 하한 및 상한
pg .smallint ();
pg .smallserial ();
mysql .smallint ();
// 스키마
pipe ( number () , minValue ( - 32_768 ) , maxValue ( 32_767 ) , integer ()); // 16비트 정수 하한 및 상한
mysql .smallint ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 65_535 ) , integer ()); // 부호 없는 16비트 정수 하한 및 상한
pg .real ();
mysql .float ();
// 스키마
pipe ( number () , minValue ( - 8_388_608 ) , maxValue ( 8_388_607 )); // 24비트 정수 하한 및 상한
mysql .mediumint ();
// 스키마
pipe ( number () , minValue ( - 8_388_608 ) , maxValue ( 8_388_607 ) , integer ()); // 24비트 정수 하한 및 상한
mysql .float ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 16_777_215 )); // 부호 없는 24비트 정수 하한 및 상한
mysql .mediumint ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 16_777_215 ) , integer ()); // 부호 없는 24비트 정수 하한 및 상한
pg .integer ();
pg .serial ();
mysql .int ();
// 스키마
pipe ( number () , minValue ( - 2_147_483_648 ) , maxValue ( 2_147_483_647 ) , integer ()); // 32비트 정수 하한 및 상한
mysql .int ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 4_294_967_295 ) , integer ()); // 부호 없는 32비트 정수 하한 및 상한
pg .doublePrecision ();
mysql .double ();
mysql .real ();
sqlite .real ();
// 스키마
pipe ( number () , minValue ( - 140_737_488_355_328 ) , maxValue ( 140_737_488_355_327 )); // 48비트 정수 하한 및 상한
mysql .double ({ unsigned : true });
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 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' });
// 스키마
pipe ( number () , minValue ( - 9_007_199_254_740_991 ) , maxValue ( 9_007_199_254_740_991 ) , integer ()); // 자바스크립트 최소 및 최대 안전 정수
mysql .serial ();
// 스키마
pipe ( number () , minValue ( 0 ) , maxValue ( 9_007_199_254_740_991 ) , integer ()); // 자바스크립트 최대 안전 정수
pg .bigint ({ mode : 'bigint' });
pg .bigserial ({ mode : 'bigint' });
mysql .bigint ({ mode : 'bigint' });
sqlite .blob ({ mode : 'bigint' });
// 스키마
pipe ( bigint () , minValue ( - 9_223_372_036_854_775_808 n ) , maxValue ( 9_223_372_036_854_775_807 n )); // 64비트 정수 하한 및 상한
mysql .bigint ({ mode : 'bigint' , unsigned : true });
// 스키마
pipe ( bigint () , minValue ( 0 ) , maxValue ( 18_446_744_073_709_551_615 n )); // 부호 없는 64비트 정수 하한 및 상한
mysql .year ();
// 스키마
pipe ( number () , minValue ( 1_901 ) , maxValue ( 2_155 ) , integer ());
pg .geometry ({ type : 'point' , mode : 'tuple' });
pg .point ({ mode : 'tuple' });
// 스키마
tuple ([ number () , number ()]);
pg .geometry ({ type : 'point' , mode : 'xy' });
pg .point ({ mode : 'xy' });
// 스키마
object ({ x : number () , y : number () });
pg .halfvec ({ dimensions : ... });
pg .vector ({ dimensions : ... });
// 스키마
pipe ( array ( number ()) , length (dimensions));
pg .line ({ mode : 'abc' });
// 스키마
object ({ a : number () , b : number () , c : number () });
pg .line ({ mode : 'tuple' });
// 스키마
tuple ([ number () , number () , number ()]);
pg .json ();
pg .jsonb ();
mysql .json ();
sqlite .blob ({ mode : 'json' });
sqlite .text ({ mode : 'json' });
// 스키마
const self = union ([ union ([ string () , number () , boolean () , null ()]) , array ( lazy (() => self)) , record ( string () , lazy (() => self))]);
sqlite .blob ({ mode : 'buffer' });
// 스키마
custom ((v) => v instanceof Buffer );
pg .dataType () .array ( ... );
// 스키마
pipe ( array (baseDataTypeSchema) , length (size));