API Reference
메서드 시그니처와 타입을 빠르게 찾아볼 수 있는 레퍼런스예요. 사용법과 예제는 각 주제별 문서를 참고해 주세요.
- Entity Definition | Relations | EntityManager
- Query Builder | Transactions | Migrations
- Advanced Features | Multi-Tenancy | Configuration
EntityManager
import { EntityManager } from "@stingerloom/orm";
const em = new EntityManager();Connection
| Method | Signature | 설명 |
|---|---|---|
register | (options: DatabaseClientOptions, connectionName?: string): Promise<void> | DB 연결 및 엔티티 등록 |
getConnectionName | (): string | 커넥션 이름 (기본값: "default") |
getDriver | (): ISqlDriver | undefined | SQL 드라이버 반환 |
propagateShutdown | (): Promise<void> | 내부 리소스 정리 |
CRUD
| Method | Signature | 설명 |
|---|---|---|
find | <T>(entity, option?): Promise<T[]> | 목록 조회 |
findOne | <T>(entity, option): Promise<T | null> | 단건 조회 |
findAndCount | <T>(entity, option?): Promise<[T[], number]> | 목록 + 전체 개수 |
findWithCursor | <T>(entity, option?): Promise<CursorPaginationResult<T>> | 커서 페이지네이션 |
save | <T>(entity, item): Promise<InstanceType<ClazzType<T>>> | INSERT 또는 UPDATE |
delete | <T>(entity, criteria): Promise<DeleteResult> | 영구 삭제 |
softDelete | <T>(entity, criteria): Promise<DeleteResult> | Soft Delete |
restore | <T>(entity, criteria): Promise<DeleteResult> | Soft Delete 복원 |
upsert | <T>(entity, data, conflictColumns?): Promise<void> | INSERT ... ON CONFLICT |
updateMany | <T>(entity, criteria, partial): Promise<{ affected: number }> | 조건별 일괄 UPDATE |
Batch
| Method | Signature | 설명 |
|---|---|---|
insertMany | <T>(entity, items[]): Promise<{ affected: number }> | 다건 INSERT |
saveMany | <T>(entity, items[]): Promise<InstanceType<ClazzType<T>>[]> | 다건 INSERT/UPDATE |
deleteMany | <T>(entity, ids[]): Promise<DeleteResult> | 다건 삭제 |
Aggregation
| Method | Signature | 설명 |
|---|---|---|
count | <T>(entity, where?): Promise<number> | 개수 |
sum | <T>(entity, field, where?): Promise<number> | 합계 |
avg | <T>(entity, field, where?): Promise<number> | 평균 |
min | <T>(entity, field, where?): Promise<number> | 최솟값 |
max | <T>(entity, field, where?): Promise<number> | 최댓값 |
Streaming
| Method | Signature | 설명 |
|---|---|---|
stream | <T>(entity, option?, batchSize?): AsyncGenerator<T> | 대용량 데이터를 위한 async generator |
for await (const user of em.stream(User, { where: { isActive: true } }, 1000)) {
// process one entity at a time without holding all rows in memory
}Transaction
| Method | Signature | 설명 |
|---|---|---|
transaction | <T>(callback: (em: EntityManager) => Promise<T>, options?: TransactionOptions): Promise<T> | 콜백을 트랜잭션으로 실행해요 (deadlock 재시도 옵션 지원) |
Raw Query / Analysis
| Method | Signature | 설명 |
|---|---|---|
query | <T>(sql: string | Sql, params?: unknown[]): Promise<T[]> | 임의의 SQL 실행 |
explain | <T>(entity, option?): Promise<ExplainResult> | EXPLAIN 분석 |
Query Builder
| Method | Signature | 설명 |
|---|---|---|
createQueryBuilder | (): BaseRawQueryBuilder | RawQueryBuilder 생성 (자유 형식 SQL) |
createQueryBuilder | <T>(entity, alias): SelectQueryBuilder<T> | 타입 안전한 SelectQueryBuilder 생성 |
Plugin System
| Method | Signature | 설명 |
|---|---|---|
extend | <TApi>(plugin: StingerloomPlugin<TApi>): this & TApi | 플러그인 설치 |
hasPlugin | (name: string): boolean | 설치 여부 확인 |
getPluginApi | <T>(name: string): T | undefined | 이름으로 플러그인 API 가져오기 |
Events
| Method | Signature | 설명 |
|---|---|---|
on | (event: EntityEventType, listener): void | 리스너 등록 |
off | (event: EntityEventType, listener): void | 리스너 제거 |
removeAllListeners | (): void | 모든 리스너 제거 |
addSubscriber | (subscriber: EntitySubscriber<any>): void | 구독자 등록 |
removeSubscriber | (subscriber: EntitySubscriber<any>): void | 구독자 제거 |
getQueryLog | (): ReadonlyArray<QueryLogEntry> | 쿼리 로그 |
BaseRepository
엔티티별 CRUD 래퍼예요. 사용법 ->
const userRepo = em.getRepository(User);
// or
const userRepo = BaseRepository.of(User, em);find, findOne, findWithCursor, findAndCount, save, delete, remove, softDelete, restore, insertMany, saveMany, deleteMany, count, sum, avg, min, max, explain, upsert, persist, stream, createQueryBuilder -- EntityManager와 동일한 API를 엔티티 지정 없이 사용할 수 있어요.
Decorators
Entity
| Decorator | 설명 |
|---|---|
@Entity(options?) | 클래스를 ORM 엔티티로 등록해요. { name: "table_name" } |
@Column(option?) | 일반 컬럼 |
@PrimaryGeneratedColumn(option?) | 자동 증가 PK |
@PrimaryColumn(option?) | 수동 PK |
@Index() | 단일 컬럼 인덱스 (프로퍼티 레벨) |
@Index(columns, name?) | 복합 비고유 인덱스 (클래스 레벨) |
@UniqueIndex(columns, name?) | 복합 고유 인덱스 (클래스 레벨) |
@Version() | 낙관적 잠금 버전 컬럼 |
@DeletedAt() | Soft Delete 타임스탬프 |
@CreateTimestamp() | INSERT 시 자동 설정 (datetime NOT NULL) |
@UpdateTimestamp() | INSERT/UPDATE 시 자동 설정 (datetime NOT NULL) |
Relations
| Decorator | 설명 |
|---|---|
@ManyToOne(getEntity, getProperty?, option?) | Many-to-one (FK 소유 측) |
@OneToMany(getEntity, option) | One-to-many (역방향) |
@OneToOne(getEntity, option?) | One-to-one |
@ManyToMany(getEntity, option?) | Many-to-many |
Lifecycle Hooks
| Decorator | 시점 |
|---|---|
@BeforeInsert | INSERT 직전 |
@AfterInsert | INSERT 직후 |
@BeforeUpdate | UPDATE 직전 |
@AfterUpdate | UPDATE 직후 |
@BeforeDelete | DELETE 직전 |
@AfterDelete | DELETE 직후 |
Validation
| Decorator | 설명 |
|---|---|
@NotNull() | null/undefined 불허 |
@MinLength(n) | 최소 문자열 길이 |
@MaxLength(n) | 최대 문자열 길이 |
@Min(n) | 최소 숫자 값 |
@Max(n) | 최대 숫자 값 |
Transaction / DI
| Decorator | 설명 |
|---|---|
@Transactional(isolationLevel?) | 메서드를 트랜잭션으로 감싸요 |
@InjectRepository(Entity, connectionName?) | NestJS에서 BaseRepository<T> 주입 (생략 시 기본 커넥션) |
@InjectEntityManager(connectionName?) | NestJS에서 EntityManager 주입 (생략 시 기본 커넥션) |
Type Reference
FindOption<T>
interface FindOption<T> {
select?: (keyof T)[] | Partial<Record<keyof T, boolean>>;
where?: WhereClause<T> | WhereClause<T>[]; // single or array (OR between groups)
limit?: number | [number, number];
skip?: number;
take?: number;
orderBy?: Partial<Record<keyof T, "ASC" | "DESC">>;
groupBy?: (keyof T)[];
having?: Sql[];
relations?: (keyof T)[];
withDeleted?: boolean;
distinct?: boolean; // SELECT DISTINCT
timeout?: number;
useMaster?: boolean;
lock?: LockMode;
}WhereClause<T>
각 필드에는 단순 값(동등 비교), 필터 객체, Sql 객체, 또는 null을 넣을 수 있어요:
type WhereClause<T> = {
[K in keyof T]?: T[K] | FieldFilter<T[K]> | Sql | null;
} & {
OR?: WhereClause<T>[];
AND?: WhereClause<T>[];
NOT?: WhereClause<T>;
};Filter Operators
연산자는 필드 타입에 따라 달라져요. string 필드에는 contains, startsWith 같은 추가 연산자가 제공돼요.
// All types: BaseFilter<T>
{ eq, ne, in, notIn, not, isNull }
// number, Date, bigint: ComparableFilter<T> (extends BaseFilter)
{ gt, gte, lt, lte, between }
// string: StringFilter (extends ComparableFilter)
{ like, notLike, ilike, contains, startsWith, endsWith }사용 예제는 Querying -- WHERE Filters를 참고해 주세요.
WhereOperator (SelectQueryBuilder)
3개 인자를 받는 where() 메서드용 타입 안전 연산자 union이에요:
type WhereOperator =
| "=" | "!=" | "<>" | "<" | ">" | "<=" | ">="
| "LIKE" | "NOT LIKE" | "ILIKE"
| "IN" | "NOT IN"
| "IS NULL" | "IS NOT NULL" | "BETWEEN";TransactionOptions
interface TransactionOptions {
retryOnDeadlock?: boolean; // Enable deadlock retry (default: false)
maxRetries?: number; // Maximum retries (default: 3)
retryDelayMs?: number; // Delay between retries in ms (default: 100)
}SelectQueryBuilder<T>
// Created via em.createQueryBuilder(Entity, "alias")
class SelectQueryBuilder<T> {
select(columns: (keyof T & string)[] | "*"): this;
addSelect(expr: Sql | string, alias?: string): this;
setDistinct(value?: boolean): this;
where(condition: Sql): this;
where(column: keyof T & string, value: any): this;
where(column: keyof T & string, operator: string, value: any): this;
andWhere(...): this;
orWhere(...): this;
whereIn(column: keyof T & string, values: any[]): this;
whereNotIn(column: keyof T & string, values: any[]): this;
whereNull(column: keyof T & string): this;
whereNotNull(column: keyof T & string): this;
whereBetween(column: keyof T & string, min: any, max: any): this;
whereLike(column: keyof T & string, pattern: string): this;
leftJoin(table: string, alias: string, condition: Sql | string): this;
innerJoin(table: string, alias: string, condition: Sql | string): this;
rightJoin(table: string, alias: string, condition: Sql | string): this;
orderBy(spec: { [K in keyof T & string]?: "ASC" | "DESC" }): this;
addOrderBy(column: keyof T & string, direction: "ASC" | "DESC"): this;
groupBy(columns: (keyof T & string)[]): this;
having(condition: Sql): this;
limit(count: number): this;
offset(count: number): this;
skip(count: number): this;
take(count: number): this;
forUpdate(): this;
forShare(): this;
withDeleted(): this;
appendSql(fragment: Sql): this;
validate(validator: RowValidator<TResult>): this;
validateArray(validator: ArrayValidator<TResult>): this;
toSql(): Sql;
getSql(): { text: string; values: any[] };
getMany(): Promise<TResult[]>;
getOne(): Promise<TResult | null>;
getCount(): Promise<number>;
getManyAndCount(): Promise<[T[], number]>;
exists(): Promise<boolean>;
asSubquery(alias: string): Sql;
}RawQueryBuilder -- Set Operations, CTE, Window Functions
class RawQueryBuilder {
// ... (basic SELECT/FROM/WHERE/JOIN/ORDER BY/LIMIT methods)
// Set Operations
union(): this;
unionAll(): this;
intersect(): this;
except(): this;
// DISTINCT
selectDistinct(columns: string[]): this;
selectDistinctOn(distinctColumns: string[], selectColumns: string[] | "*"): this;
// Common Table Expressions
with(name: string, subquery: Sql | ((qb: RawQueryBuilder) => RawQueryBuilder)): this;
withRecursive(name: string, subquery: Sql | ((qb: RawQueryBuilder) => RawQueryBuilder)): this;
// Window Functions
selectWithWindow(columns: Array<string | WindowColumn>): this;
}
interface WindowColumn {
expr: string; // e.g. "ROW_NUMBER()", "SUM(salary)"
over: { partitionBy?: string; orderBy?: string };
alias: string;
}RowValidator / ArrayValidator
// Row-level: validates each row individually
type RowValidator<TResult> =
| ((row: TResult) => TResult) // Plain function
| { parse(data: unknown): TResult }; // Zod-style (.parse() method)
// Array-level: validates the entire result array
type ArrayValidator<TResult> =
| ((rows: TResult[]) => TResult[])
| { parse(data: unknown): TResult[] };CursorPaginationOption<T> / CursorPaginationResult<T>
interface CursorPaginationOption<T> {
take?: number; // Page size (default: 20)
cursor?: string; // Base64 cursor
orderBy?: keyof T & string; // Sort column (default: PK)
direction?: "ASC" | "DESC"; // Sort direction (default: "ASC")
where?: Partial<T>; // WHERE conditions
}
interface CursorPaginationResult<T> {
data: T[];
hasNextPage: boolean;
nextCursor: string | null;
count: number;
}ExplainResult
interface ExplainResult {
raw: Record<string, unknown>[];
rows: number | null;
type: string | null; // ALL, ref, Seq Scan, etc.
possibleKeys: string[] | null;
key: string | null;
cost: number | null;
}DeleteResult
interface DeleteResult {
affected: number;
}ColumnOption
interface ColumnOption {
name?: string; // Column name (defaults to property name)
type?: ColumnType; // Column type (auto-inferred if omitted)
length?: number;
nullable?: boolean; // Default: false
primary?: boolean;
autoIncrement?: boolean;
default?: unknown; // Column default value (string, number, boolean, or raw SQL in parentheses)
transform?: (raw: unknown) => any;
precision?: number;
scale?: number;
enumValues?: string[]; // PostgreSQL ENUM
enumName?: string; // PostgreSQL ENUM type name
}ColumnType
type ColumnType =
| "varchar" | "char" | "int" | "number" | "float" | "double" | "bigint"
| "boolean" | "datetime" | "timestamp" | "timestamptz" | "date"
| "text" | "longtext" | "blob"
| "json" | "jsonb" | "enum" | "array";Relation Options
interface ManyToOneOption {
joinColumn?: string; // FK column name (auto-detected from @Column if omitted)
references?: string; // Target reference column (defaults to PK)
eager?: boolean;
lazy?: boolean;
cascade?: CascadeOption;
onDelete?: ReferentialAction; // 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION'
onUpdate?: ReferentialAction; // default: 'NO ACTION'
createForeignKeyConstraints?: boolean; // false to skip FK constraint in DDL
transform?: (raw: unknown) => any;
}
interface OneToManyOption<T> {
mappedBy: Extract<keyof T, string> | (string & {}); // IntelliSense supported
cascade?: CascadeOption;
}
interface OneToOneOption<T> {
joinColumn?: string; // FK column name (auto-detected from @Column if omitted)
inverseSide?: Extract<keyof T, string> | (string & {}); // IntelliSense supported
eager?: boolean;
cascade?: CascadeOption;
onDelete?: ReferentialAction; // 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION'
onUpdate?: ReferentialAction; // default: 'NO ACTION'
createForeignKeyConstraints?: boolean; // false to skip FK constraint in DDL
}
interface ManyToManyOption<T> {
joinTable?: { name: string; joinColumn: string; inverseJoinColumn: string };
mappedBy?: Extract<keyof T, string> | (string & {}); // IntelliSense supported
}
type CascadeOption = boolean | ("insert" | "update" | "delete" | "remove")[];
mappedBy와inverseSide는 대상 엔티티의 프로퍼티 이름 자동 완성을 지원해요. 임의의 문자열도 허용돼요.
Configuration Options
interface DatabaseClientOptions {
type: "mysql" | "mariadb" | "postgres" | "sqlite";
host: string;
port: number;
username: string;
password: string;
database: string;
entities: AnyEntity[];
synchronize?: boolean | "safe" | "dry-run";
schema?: string;
charset?: string;
datesStrings?: boolean;
queryTimeout?: number;
pool?: PoolOptions;
retry?: RetryOptions;
logging?: boolean | LoggingOptions;
replication?: ReplicationConfig;
plugins?: StingerloomPlugin[]; // Auto-install plugins on register()
}
interface PoolOptions {
max?: number; // Default: 10
min?: number; // Default: 0
acquireTimeoutMs?: number; // Default: 30000
idleTimeoutMs?: number; // Default: 10000
}
interface RetryOptions {
maxAttempts: number; // Default: 3
backoffMs: number; // Default: 1000
}
interface LoggingOptions {
queries?: boolean;
slowQueryMs?: number;
nPlusOne?: boolean;
}
interface ReplicationConfig {
master: ReplicationNodeConfig;
slaves: ReplicationNodeConfig[];
}
interface ReplicationNodeConfig {
host: string;
port: number;
username: string;
password: string;
database: string;
}EntitySubscriber<T>
interface EntitySubscriber<T = any> {
listenTo(): new (...args: any[]) => T;
afterLoad?(entity: T): void | Promise<void>;
beforeInsert?(event: InsertEvent<T>): void | Promise<void>;
afterInsert?(event: InsertEvent<T>): void | Promise<void>;
beforeUpdate?(event: UpdateEvent<T>): void | Promise<void>;
afterUpdate?(event: UpdateEvent<T>): void | Promise<void>;
beforeDelete?(event: DeleteEvent<T>): void | Promise<void>;
afterDelete?(event: DeleteEvent<T>): void | Promise<void>;
beforeTransactionStart?(): void | Promise<void>;
afterTransactionStart?(): void | Promise<void>;
beforeTransactionCommit?(): void | Promise<void>;
afterTransactionCommit?(): void | Promise<void>;
beforeTransactionRollback?(): void | Promise<void>;
afterTransactionRollback?(): void | Promise<void>;
}
interface InsertEvent<T> { entity: Partial<T>; manager: EntityManager; }
interface UpdateEvent<T> { entity: Partial<T>; manager: EntityManager; }
interface DeleteEvent<T> { entityClass: new (...args: any[]) => T; criteria: any; manager: EntityManager; }EntityEventType
type EntityEventType =
| "beforeInsert" | "afterInsert"
| "beforeUpdate" | "afterUpdate"
| "beforeDelete" | "afterDelete";StingerloomPlugin<TApi>
interface StingerloomPlugin<TApi = {}> {
readonly name: string;
readonly dependencies?: readonly string[];
install(context: PluginContext): TApi | void;
shutdown?(): Promise<void> | void;
}ITenantMigrationRunner
interface ITenantMigrationRunner {
discoverSchemas(): Promise<string[]>;
ensureSchema(tenantId: string): Promise<void>;
syncTenantSchemas(tenantIds: string[]): Promise<TenantSyncResult>;
isProvisioned(tenantId: string): boolean;
getProvisionedSchemas(): string[];
reset(): void;
}
interface TenantSyncResult { created: string[]; skipped: string[]; }구현체: PostgresTenantMigrationRunner, MySqlTenantMigrationRunner, SqliteTenantMigrationRunner
Migration
abstract class Migration {
get name(): string;
abstract up(context: MigrationContext): Promise<void>;
abstract down(context: MigrationContext): Promise<void>;
}
interface MigrationContext {
driver: ISqlDriver;
query: (sql: string) => Promise<any>;
}SchemaDiffResult
interface SchemaDiffResult {
addedTables: string[];
droppedTables: string[];
modifiedTables: ModifiedTable[];
renamedColumns?: RenamedColumn[]; // Heuristic-detected column renames
addTableEntityMap?: Record<string, ClazzType<any>>;
}
interface ModifiedTable {
tableName: string;
addedColumns: ColumnChange[];
droppedColumns: ColumnChange[];
}
interface ColumnChange {
columnName: string;
columnType?: string;
nullable?: boolean;
}
interface RenamedColumn {
tableName: string;
oldColumnName: string;
newColumnName: string;
}MigrationCli
class MigrationCli {
constructor(migrations: Migration[], options: DatabaseClientOptions);
connect(): Promise<void>;
close(): Promise<void>;
execute(command: MigrationCommand): Promise<any>;
}
type MigrationCommand = "migrate:run" | "migrate:rollback" | "migrate:status" | "migrate:generate";CLI 실행: npx stingerloom migrate:run|rollback|status|generate
관련 클래스: MigrationRunner, MigrationCli, SchemaDiff, SchemaDiffMigrationGenerator
Errors
모든 ORM 에러는 OrmError를 상속하고, suggestion 필드에 해결 힌트가 포함될 수 있어요. 문서를 찾아보지 않아도 문제를 진단할 수 있어요:
try {
await em.find(UnregisteredEntity);
} catch (e) {
if (e instanceof OrmError) {
console.error(e.message); // "Entity metadata not found for UnregisteredEntity"
console.error(e.suggestion); // "Did you register the entity in the entities array?"
console.error(e.code); // "ORM_ENTITY_METADATA_NOT_FOUND"
}
}ValidationError는 빠른 진단을 위해 actual과 expected 필드도 함께 제공해요:
try {
await em.save(User, { name: "A" }); // @MinLength(2) fails
} catch (e) {
if (e instanceof ValidationError) {
console.error(e.message); // "name must be at least 2 characters long"
console.error(e.actual); // "A"
console.error(e.expected); // "minLength: 2"
}
}| Error | 설명 |
|---|---|
OrmError | 기본 ORM 에러 (code, message, 선택적 suggestion 포함) |
ValidationError | 유효성 검증 실패 (actual, expected 필드 포함) |
InvalidQueryError | 잘못된 쿼리 (선택적 suggestion 포함) |
EntityNotFoundError | 엔티티를 찾을 수 없음 |
EntityMetadataNotFoundError | 메타데이터를 찾을 수 없음 |
PrimaryKeyNotFoundError | PK를 찾을 수 없음 |
DeleteWithoutConditionsError | 조건 없는 삭제 |
QueryTimeoutError | 쿼리 타임아웃 |
TransactionError | 트랜잭션 에러 |
DatabaseNotConnectedError | DB 미연결 |
DatabaseConnectionFailedError | DB 연결 실패 |
NotSupportedDatabaseTypeError | 지원하지 않는 DB 타입 |
OrmErrorCode
enum OrmErrorCode {
CONNECTION_FAILED = "ORM_CONNECTION_FAILED",
NOT_CONNECTED = "ORM_NOT_CONNECTED",
UNSUPPORTED_DATABASE = "ORM_UNSUPPORTED_DATABASE",
ENTITY_NOT_FOUND = "ORM_ENTITY_NOT_FOUND",
ENTITY_METADATA_NOT_FOUND = "ORM_ENTITY_METADATA_NOT_FOUND",
PRIMARY_KEY_NOT_FOUND = "ORM_PRIMARY_KEY_NOT_FOUND",
INVALID_QUERY = "ORM_INVALID_QUERY",
DELETE_WITHOUT_CONDITIONS = "ORM_DELETE_WITHOUT_CONDITIONS",
QUERY_TIMEOUT = "ORM_QUERY_TIMEOUT",
TRANSACTION_FAILED = "ORM_TRANSACTION_FAILED",
TRANSACTION_ROLLBACK_FAILED = "ORM_TRANSACTION_ROLLBACK_FAILED",
VALIDATION_FAILED = "ORM_VALIDATION_FAILED",
UNIQUE_VIOLATION = "ORM_UNIQUE_VIOLATION",
FK_VIOLATION = "ORM_FK_VIOLATION",
PLUGIN_DEPENDENCY_MISSING = "ORM_PLUGIN_DEPENDENCY_MISSING",
PLUGIN_CONFLICT = "ORM_PLUGIN_CONFLICT",
BUFFER_NOT_INSTALLED = "ORM_BUFFER_NOT_INSTALLED",
}EntitySchema (Decorator-Free Entity Definition)
import { EntitySchema, EntitySchemaOptions, ColumnSchemaDef } from "@stingerloom/orm";
const schema = new EntitySchema<T>(options: EntitySchemaOptions<T>);EntitySchemaOptions<T>
interface EntitySchemaOptions<T> {
target: ClazzType<T>; // Entity class
tableName?: string; // Table name (defaults to snake_case of class name)
columns: { [K in keyof T]?: ColumnSchemaDef }; // Column definitions
relations?: { [K in keyof T]?: RelationSchemaDef }; // Relation definitions
uniqueIndexes?: { columns: string[]; name?: string }[]; // Composite unique indexes
hooks?: Partial<Record<HookEvent, Extract<keyof T, string>>>; // Lifecycle hooks
}ColumnSchemaDef
interface ColumnSchemaDef {
type: ColumnType;
primary?: boolean;
autoIncrement?: boolean;
length?: number;
nullable?: boolean;
default?: string | number | boolean | null;
precision?: number;
scale?: number;
enumValues?: string[];
enumName?: string;
name?: string;
transform?: (raw: unknown) => any;
// Special column flags
index?: boolean; // Equivalent to @Index()
createTimestamp?: boolean; // Equivalent to @CreateTimestamp()
updateTimestamp?: boolean; // Equivalent to @UpdateTimestamp()
deletedAt?: boolean; // Equivalent to @DeletedAt()
version?: boolean; // Equivalent to @Version()
// Inline validation
validation?: ValidationDef[];
}
interface ValidationDef {
constraint: "notNull" | "minLength" | "maxLength" | "min" | "max";
value?: number;
message?: string;
}RelationSchemaDef
type RelationSchemaDef =
| { kind: "manyToOne"; target: () => ClazzType; joinColumn?: string; references?: string; eager?: boolean; cascade?: CascadeOption; lazy?: boolean }
| { kind: "oneToMany"; target: () => ClazzType; mappedBy: string; cascade?: CascadeOption }
| { kind: "oneToOne"; target: () => ClazzType; joinColumn?: string; inverseSide?: string; eager?: boolean; cascade?: CascadeOption }
| { kind: "manyToMany"; target: () => ClazzType; joinTable?: JoinTableOption; mappedBy?: string };Utilities
| Export | 설명 |
|---|---|
ClazzType<T> | new (...args: any[]) => T |
EntityResult<T> | Deprecated -- T | T[]였지만, find()는 T[], save()는 T로 대체됐어요 |
DeepPartial<T> | Deep partial 타입 |
WhereClause<T> | { [P in keyof T]?: T[P] } -- 타입 안전한 WHERE 조건 |
FindCondition<T> | Deprecated -- WhereClause<T>를 사용해 주세요 |
RawQueryBuilderFactory | Query builder 팩토리 |
LayeredMetadataStore | 레이어드 메타데이터 |
MetadataContext | AsyncLocalStorage 기반 테넌트 컨텍스트 |
Logger | 내부 로깅 유틸리티 |