Skip to Content
문서핵심 개념TypeIs 타입 시스템

TypeIs 타입 시스템

TypeIs는 ASAPJS의 핵심 혁신입니다. 필드를 한 번만 선언하면 프레임워크가 그 단일 선언으로부터 데이터베이스 컬럼 정의, Swagger 문서, 런타임 타입 강제 변환기를 모두 도출할 수 있게 해주는 메커니즘입니다.


핵심 아이디어

일반적인 Node.js 백엔드에서는 모든 필드에 대해 최소 세 가지 별도의 설명을 유지해야 합니다:

  • Sequelize 컬럼 정의 (DataTypes.STRING(255))
  • Swagger 스키마 프로퍼티 ({ type: 'string', description: '...' })
  • 요청 처리 시 유효성 검사 또는 강제 변환 단계

이 세 가지 표현은 시간이 지남에 따라 서로 어긋나기 마련입니다. TypeIs는 세 가지 출력을 모두 단일 데코레이터 호출의 결과로 만들어 이 문제를 근본적으로 해결합니다.

@TypeIs.STRING({ comment: 'User email' }) email: string;

내부적으로 이 하나의 선언은 세 가지를 생성합니다:

1. Sequelize 컬럼 정의 (toSequelize())

{ type: DataTypes.STRING(), comment: 'User email', }

모델이 initSequelizeModule에 등록될 때, ASAPJS는 toSequelize() 출력을 읽어 Sequelize의 컬럼 정의 메커니즘에 전달합니다. 별도의 @Column 데코레이터가 필요하지 않습니다.

2. Swagger 스키마 프로퍼티 (toSwagger())

{ type: 'string', description: 'User email', }

라우트 데코레이터가 body:, query:, response:를 통해 DTO 또는 엔티티 클래스를 참조할 때, ASAPJS는 TypeIs 데코레이터가 달린 모든 필드에 대해 toSwagger()를 호출하여 OpenAPI 스키마를 인라인으로 빌드합니다.

3. 런타임 타입 강제 변환기 (fixValue())

(o: any) => (o === undefined || o === null ? o : String(o))

fixValue는 들어오는 값을 기대하는 기본 타입으로 변환합니다. STRING의 경우 String(o)를 호출하고, INT의 경우 parseInt를 호출합니다. ASAPJS가 요청 데이터를 DTO를 통해 매핑할 때 자동으로 실행됩니다.


아키텍처: @asapjs/schema와 TypeIsDecorators

TypeIs 시스템은 두 개의 패키지 레이어로 구성됩니다:

@asapjs/schema — 타입 정의 코어

@asapjs/schema 패키지는 타입 시스템의 핵심 엔진입니다. 플러그인 기반 아키텍처를 통해 하나의 타입 정의에서 여러 출력(Swagger, Sequelize 등)을 생성합니다.

  • BaseSchemaType<T> — 모든 타입의 추상 기본 클래스. validate(), parse(), toSwagger(), toSequelize() 메서드를 제공합니다.
  • SchemaPluginRegistryswagger, sequelize 등의 플러그인을 등록하고, BaseSchemaType.to(target) 호출 시 해당 플러그인의 transform()을 실행합니다.
  • TypeIs — 등록된 모든 타입 팩토리(INT, STRING, BOOLEAN 등)를 포함하는 네임스페이스 객체입니다.

@asapjs/sequelize — 데코레이터 브릿지

@asapjs/sequelize에서 export하는 TypeIs는 실제로 TypeIsDecorators라는 래퍼입니다. 이 래퍼는 @asapjs/schemaTypeIs 타입 팩토리들을 순회하며, 각각을 TypeScript 프로퍼티 데코레이터로 사용할 수 있는 형태로 변환합니다.

// packages/sequelize/src/types/decorators.ts (간략화) import { TypeIs, registry, sequelizePlugin } from '@asapjs/schema'; // @asapjs/schema의 모든 TypeIs 타입에 대해 데코레이터 wrapper 생성 function createTypeIsDecorators() { const decorators: any = {}; for (const key in TypeIs) { decorators[key] = createDecorator(key); } return decorators; } export const TypeIsDecorators = createTypeIsDecorators();
// packages/sequelize/src/index.ts export { TypeIsDecorators as TypeIs } from './types/decorators';

사용자 관점에서는 import { TypeIs } from '@asapjs/sequelize'로 import하여 동일하게 사용합니다. 내부적으로 @asapjs/schemaBaseSchemaType을 기존 typeGenerator 호환 형태(TypeIsData)로 변환하여, Sequelize 메타데이터 시스템과의 호환성을 유지합니다.

또한 @asapjs/sequelize는 Sequelize 전용 타입(QUERY, FOREIGNKEY, BELONGSTO)을 registerSequelizeTypes()를 통해 @asapjs/schemaTypeIs에 동적으로 등록합니다.


TypeIs 내부 동작 방식

TypeIs.* 타입은 내부적으로 TypeIsData 레코드를 생성합니다:

// Simplified view of TypeIsData type TypeIsData = { __name: string; toSwagger?: (...args: any[]) => any; toSequelize?: (...args: any[]) => any; fixValue?: (o: any) => any; };

데코레이터로 사용될 때, 이 레코드는 클래스 메타데이터 레지스트리에 저장됩니다. 레지스트리는 클래스 이름을 키로 사용하므로, ASAPJS는 런타임에 어떤 클래스든 검사하여 모든 TypeIs 필드를 열거할 수 있습니다. 이것이 단일 DTO 클래스가 Sequelize 모델 동기화와 Swagger 스펙 빌더 모두에 활용될 수 있는 원리입니다.


사용 컨텍스트

TypeIs 데코레이터는 세 가지 컨텍스트에서 작동하며, 각 컨텍스트는 세 가지 출력 중 서로 다른 부분을 사용합니다.

@Table 엔티티 — 데이터베이스 컬럼

@Table을 확장한 Model의 프로퍼티에 TypeIs.* 데코레이터를 붙이면, ASAPJS는 모델 등록 시 toSequelize()를 읽어 그 결과를 Sequelize에 컬럼 정의로 전달합니다.

// example/src/user/domain/entity/UsersTable.ts @Table({ tableName: 'users', timestamps: true }) export default class UsersTable extends Model { @TypeIs.INT({ primaryKey: true, autoIncrement: true, comment: 'User ID' }) id: number; @TypeIs.STRING({ unique: true, comment: 'Email address (unique)' }) email: string; @TypeIs.PASSWORD({ comment: 'bcrypt-hashed password' }) password: string; @TypeIs.DATETIME({ comment: 'Record created at' }) created_at: Date; }

unique, primaryKey, autoIncrement 옵션은 표준 Sequelize ModelAttributeColumnOptions이며 그대로 전달됩니다.

ExtendableDto 서브클래스 — API 스키마 및 쿼리 설정

DTO는 ExtendableDto를 확장하며 동일한 TypeIs.* 데코레이터를 사용합니다. 이 컨텍스트에서 ASAPJS는 toSwagger()를 읽어 DTO의 OpenAPI 스키마를 빌드하고, fixValue()를 사용하여 들어오는 요청 필드 값을 강제 변환합니다.

// example/src/user/dto/UserInfoDto.ts import { ExtendableDto, TypeIs } from '@asapjs/sequelize'; import UsersTable from '../domain/entity/UsersTable'; export default class UserInfoDto extends ExtendableDto { @TypeIs.INT({ comment: 'User ID' }) id: number; @TypeIs.STRING({ comment: 'Email address' }) email: string; @TypeIs.STRING({ comment: 'Display name' }) name: string; }

defineTable 참조는 이 DTO가 어떤 엔티티의 프로젝션인지를 ASAPJS에 알려주며, 프레임워크가 시작 시 필드 이름의 유효성을 검사할 수 있게 합니다.

IOptions.response / IOptions.body — 인라인 Swagger 스키마

응답에 단순한 boolean이나 기본 타입으로 충분한 경우, DTO 클래스를 별도로 정의하지 않고도 TypeIs.* 표현식을 IOptions에 직접 전달할 수 있습니다. ASAPJS는 해당 표현식의 toSwagger()를 호출하여 인라인 스키마를 생성합니다.

@Get('/active', { title: 'Is service active', response: TypeIs.BOOLEAN(), }) async isActive({}: ExecuteArgs) { return { value: true }; }

TypeIs.ARRAYTypeIs.PAGING 같은 조합 타입도 동일하게 작동합니다:

// 문자열 배열 인라인 response: TypeIs.ARRAY(TypeIs.STRING()) // DTO의 페이지네이션 목록 response: TypeIs.PAGING(PostInfoDto)

사용 가능한 타입

카테고리타입
숫자형INT, BIGINT, LONG, FLOAT, DECIMAL, DOUBLE
문자열형STRING, TEXT, PASSWORD
특수형ENUM, JSON, BOOLEAN, BASE64, BINARY
날짜/시간형DATEONLY, DATETIME
관계형FOREIGNKEY, BELONGSTO
조합형DTO, QUERY, ARRAY, PAGING

FOREIGNKEYBELONGSTO는 엔티티 전용이며 Swagger 출력이 없습니다. DTO, QUERY, ARRAY, PAGING는 DTO/라우터 전용이며 Sequelize 컬럼 출력이 없습니다.

전체 레퍼런스 — 모든 타입, 모든 옵션, 모든 출력 매핑 — 는 데이터 모델링 API 레퍼런스를 참고하세요.


요약

TypeIs는 ASAPJS가 진정한 저보일러플레이트 워크플로우를 제공할 수 있는 이유입니다. 단일 데코레이터가 Sequelize, Swagger, 강제 변환을 동시에 인코딩하기 때문에, 시스템에 새 필드를 추가한다는 것은 정확히 하나의 파일에서 정확히 한 줄만 수정하는 것을 의미합니다. 프레임워크의 나머지 부분은 그 레코드에서 자동으로 읽어갑니다.

Last updated on