@warriorteam/dynamic-table
Version:
NestJS SDK for Dynamic Table System with PostgreSQL + JSONB - Build Airtable/Notion-like applications easily
859 lines (832 loc) • 21 kB
text/typescript
import { ModuleMetadata, Type, DynamicModule } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
/**
* Enum định nghĩa các loại trường dữ liệu
*/
declare enum FieldType {
TEXT = "text",
LONG_TEXT = "long_text",
EMAIL = "email",
PHONE = "phone",
URL = "url",
NUMBER = "number",
CURRENCY = "currency",
PERCENT = "percent",
RATING = "rating",
AUTONUMBER = "autonumber",
SELECT = "select",
MULTI_SELECT = "multi_select",
BOOLEAN = "boolean",
DATE = "date",
DATETIME = "datetime",
DURATION = "duration",
RELATION = "relation",
LOOKUP = "lookup",
ROLLUP = "rollup",
FORMULA = "formula",
CREATED_TIME = "created_time",
MODIFIED_TIME = "modified_time",
CREATED_BY = "created_by",
MODIFIED_BY = "modified_by",
ATTACHMENT = "attachment",
USER = "user"
}
/**
* Danh sách các loại trường tự động tính toán (chỉ đọc)
*/
declare const COMPUTED_FIELD_TYPES: FieldType[];
/**
* Các toán tử lọc dữ liệu
*/
type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'in' | 'not_in' | 'is_empty' | 'is_not_empty' | 'is_before' | 'is_after' | 'is_on_or_before' | 'is_on_or_after' | 'is_within';
/**
* Rollup functions
*/
type RollupFunction = 'sum' | 'avg' | 'min' | 'max' | 'count' | 'counta' | 'countall';
/**
* Metadata cho từng loại trường (dùng cho UI)
*/
interface FieldTypeMetadata {
label: string;
icon: string;
isComputed: boolean;
defaultConfig?: Record<string, any>;
}
declare const FIELD_TYPE_METADATA: Record<FieldType, FieldTypeMetadata>;
/**
* Select option item
*/
interface SelectOption {
id: string;
label: string;
color?: string;
}
/**
* Cấu hình chi tiết cho từng loại trường
*/
interface FieldConfig {
options?: SelectOption[];
precision?: number;
currencySymbol?: string;
currencyCode?: string;
percentFormat?: 'decimal' | 'whole';
maxRating?: number;
ratingIcon?: 'star' | 'heart' | 'thumb';
prefix?: string;
startNumber?: number;
digitCount?: number;
dateFormat?: string;
timeFormat?: '12h' | '24h';
includeTime?: boolean;
timezone?: string;
durationFormat?: 'h:mm' | 'h:mm:ss' | 'days';
targetTableId?: string;
allowMultiple?: boolean;
symmetricFieldId?: string;
relationFieldId?: string;
lookupFieldId?: string;
rollupRelationFieldId?: string;
rollupFieldId?: string;
rollupFunction?: RollupFunction;
formulaExpression?: string;
outputType?: 'number' | 'text' | 'date' | 'boolean';
enableRichText?: boolean;
maxLength?: number;
defaultCountryCode?: string;
urlType?: 'any' | 'image' | 'video';
allowMultipleUsers?: boolean;
notifyOnAssign?: boolean;
allowedFileTypes?: string[];
maxFileSize?: number;
maxFiles?: number;
}
/**
* Interface định nghĩa trường dữ liệu
*/
interface TableField {
id: string;
tableId: string;
name: string;
keyName: string;
type: FieldType;
config: FieldConfig;
isPrimary: boolean;
isRequired: boolean;
orderIndex: number;
createdAt?: Date;
}
/**
* DTO để tạo field mới
*/
interface CreateFieldDto {
tableId: string;
name: string;
keyName: string;
type: FieldType;
config?: FieldConfig;
isPrimary?: boolean;
isRequired?: boolean;
orderIndex?: number;
}
/**
* DTO để cập nhật field
*/
interface UpdateFieldDto {
name?: string;
keyName?: string;
type?: FieldType;
config?: FieldConfig;
isPrimary?: boolean;
isRequired?: boolean;
orderIndex?: number;
}
/**
* Interface tham số lọc dữ liệu
*/
interface FilterParams {
column: string;
operator: FilterOperator;
value: any;
dataType?: FieldType;
}
/**
* Interface tham số sắp xếp
*/
interface SortParams {
fieldKey: string;
order: 'ASC' | 'DESC';
}
/**
* Interface tham số phân trang
*/
interface PaginationParams {
page?: number;
limit?: number;
offset?: number;
}
/**
* Interface query options
*/
interface QueryOptions {
filters?: FilterParams[];
sort?: SortParams;
pagination?: PaginationParams;
search?: {
query: string;
fields?: string[];
};
}
/**
* Interface kết quả phân trang
*/
interface PaginatedResult<T> {
data: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
hasNextPage: boolean;
hasPrevPage: boolean;
};
}
/**
* Database configuration options
*/
interface DatabaseConfig {
host: string;
port: number;
database: string;
username: string;
password: string;
ssl?: boolean;
synchronize?: boolean;
logging?: boolean;
}
/**
* Dynamic Table Module options
*/
interface DynamicTableModuleOptions {
/**
* Use existing TypeORM connection
* If true, will use the default TypeORM connection
*/
useExistingConnection?: boolean;
/**
* Custom connection name to use
*/
connectionName?: string;
/**
* Database configuration (if not using existing connection)
*/
database?: DatabaseConfig;
/**
* Existing DataSource instance
*/
dataSource?: DataSource;
/**
* Table name prefix for all dynamic table entities
* @default ''
*/
tablePrefix?: string;
/**
* Enable auto-sync database schema
* WARNING: Should be false in production
* @default false
*/
synchronize?: boolean;
/**
* Enable query logging
* @default false
*/
logging?: boolean;
/**
* Custom user ID resolver for audit fields
*/
userIdResolver?: () => string | Promise<string>;
}
/**
* Factory interface for async options
*/
interface DynamicTableOptionsFactory {
createDynamicTableOptions(): Promise<DynamicTableModuleOptions> | DynamicTableModuleOptions;
}
/**
* Async module options
*/
interface DynamicTableModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
/**
* Use existing provider
*/
useExisting?: Type<DynamicTableOptionsFactory>;
/**
* Use class provider
*/
useClass?: Type<DynamicTableOptionsFactory>;
/**
* Use factory function
*/
useFactory?: (...args: any[]) => Promise<DynamicTableModuleOptions> | DynamicTableModuleOptions;
/**
* Inject dependencies for factory
*/
inject?: any[];
}
/**
* Interface định nghĩa bản ghi
*/
interface TableRecord {
id: string;
tableId: string;
data: Record<string, any>;
createdBy?: string;
updatedBy?: string;
createdAt: Date;
updatedAt: Date;
[formulaKey: string]: any;
}
/**
* DTO để tạo record mới
*/
interface CreateRecordDto {
tableId: string;
data: Record<string, any>;
createdBy?: string;
}
/**
* DTO để cập nhật record
*/
interface UpdateRecordDto {
data?: Record<string, any>;
updatedBy?: string;
}
/**
* Batch create records
*/
interface BatchCreateRecordDto {
tableId: string;
records: Array<{
data: Record<string, any>;
}>;
createdBy?: string;
}
/**
* Record history entry
*/
interface RecordHistoryEntry {
id: string;
recordId: string;
changedBy?: string;
changedAt: Date;
changes: Record<string, {
old: any;
new: any;
}>;
}
/**
* Interface định nghĩa Table
*/
interface DynamicTable {
id: string;
workspaceId: string;
name: string;
slug: string;
description?: string;
settings?: Record<string, any>;
createdAt: Date;
}
/**
* DTO để tạo table mới
*/
interface CreateTableDto {
workspaceId: string;
name: string;
slug: string;
description?: string;
settings?: Record<string, any>;
}
/**
* DTO để cập nhật table
*/
interface UpdateTableDto {
name?: string;
slug?: string;
description?: string;
settings?: Record<string, any>;
}
/**
* View type
*/
type ViewType = 'grid' | 'kanban' | 'calendar' | 'gallery' | 'form';
/**
* View config
*/
interface ViewConfig {
filters?: FilterParams[];
sort?: SortParams[];
hiddenFields?: string[];
fieldOrder?: string[];
groupBy?: string;
kanbanFieldId?: string;
calendarFieldId?: string;
coverFieldId?: string;
}
/**
* Interface định nghĩa View
*/
interface TableView {
id: string;
tableId: string;
name: string;
type: ViewType;
config: ViewConfig;
isDefault: boolean;
}
/**
* DTO để tạo view mới
*/
interface CreateViewDto {
tableId: string;
name: string;
type?: ViewType;
config?: ViewConfig;
isDefault?: boolean;
}
/**
* DTO để cập nhật view
*/
interface UpdateViewDto {
name?: string;
type?: ViewType;
config?: ViewConfig;
isDefault?: boolean;
}
/**
* Interface định nghĩa Workspace
*/
interface Workspace {
id: string;
name: string;
slug: string;
createdAt: Date;
}
/**
* DTO để tạo workspace mới
*/
interface CreateWorkspaceDto {
name: string;
slug: string;
}
/**
* DTO để cập nhật workspace
*/
interface UpdateWorkspaceDto {
name?: string;
slug?: string;
}
declare const DYNAMIC_TABLE_OPTIONS = "DYNAMIC_TABLE_OPTIONS";
declare class DynamicTableModule {
/**
* Register module with synchronous options
*/
static forRoot(options?: DynamicTableModuleOptions): DynamicModule;
/**
* Register module with asynchronous options
*/
static forRootAsync(options: DynamicTableModuleAsyncOptions): DynamicModule;
/**
* Create async providers
*/
private static createAsyncProviders;
/**
* Create async options provider
*/
private static createAsyncOptionsProvider;
}
declare class SysField {
id: string;
tableId: string;
name: string;
keyName: string;
type: FieldType;
config: FieldConfig;
isPrimary: boolean;
isRequired: boolean;
orderIndex: number;
createdAt: Date;
table: SysTable;
}
declare class SysView {
id: string;
tableId: string;
name: string;
type: ViewType;
config: ViewConfig;
isDefault: boolean;
table: SysTable;
}
declare class UsrRecordHistory {
id: string;
recordId: string;
changedBy: string;
changedAt: Date;
changes: Record<string, {
old: any;
new: any;
}>;
record: UsrRecord;
}
declare class UsrRecord {
id: string;
tableId: string;
data: Record<string, any>;
createdBy: string;
updatedBy: string;
createdAt: Date;
updatedAt: Date;
table: SysTable;
history: UsrRecordHistory[];
}
declare class SysTable {
id: string;
workspaceId: string;
name: string;
slug: string;
description: string;
settings: Record<string, any>;
createdAt: Date;
workspace: SysWorkspace;
fields: SysField[];
views: SysView[];
records: UsrRecord[];
}
declare class SysWorkspace {
id: string;
name: string;
slug: string;
createdAt: Date;
tables: SysTable[];
}
/**
* All entities for TypeORM registration
*/
declare const DYNAMIC_TABLE_ENTITIES: (typeof SysField | typeof SysTable | typeof SysView | typeof UsrRecordHistory | typeof UsrRecord | typeof SysWorkspace)[];
declare class WorkspaceService {
private readonly workspaceRepo;
constructor(workspaceRepo: Repository<SysWorkspace>);
/**
* Create a new workspace
*/
create(dto: CreateWorkspaceDto): Promise<SysWorkspace>;
/**
* Find all workspaces
*/
findAll(): Promise<SysWorkspace[]>;
/**
* Find workspace by ID
*/
findById(id: string): Promise<SysWorkspace | null>;
/**
* Find workspace by slug
*/
findBySlug(slug: string): Promise<SysWorkspace | null>;
/**
* Update workspace
*/
update(id: string, dto: UpdateWorkspaceDto): Promise<SysWorkspace | null>;
/**
* Delete workspace
*/
delete(id: string): Promise<boolean>;
}
declare class TableService {
private readonly tableRepo;
constructor(tableRepo: Repository<SysTable>);
/**
* Create a new table
*/
create(dto: CreateTableDto): Promise<SysTable>;
/**
* Find all tables in a workspace
*/
findByWorkspace(workspaceId: string): Promise<SysTable[]>;
/**
* Find table by ID
*/
findById(id: string): Promise<SysTable | null>;
/**
* Find table by slug within a workspace
*/
findBySlug(workspaceId: string, slug: string): Promise<SysTable | null>;
/**
* Find table with all relations
*/
findByIdWithRelations(id: string): Promise<SysTable | null>;
/**
* Update table
*/
update(id: string, dto: UpdateTableDto): Promise<SysTable | null>;
/**
* Delete table
*/
delete(id: string): Promise<boolean>;
}
declare class FieldService {
private readonly fieldRepo;
constructor(fieldRepo: Repository<SysField>);
/**
* Create a new field
*/
create(dto: CreateFieldDto): Promise<SysField>;
/**
* Find all fields of a table
*/
findByTable(tableId: string): Promise<SysField[]>;
/**
* Find field by ID
*/
findById(id: string): Promise<SysField | null>;
/**
* Find field by keyName within a table
*/
findByKeyName(tableId: string, keyName: string): Promise<SysField | null>;
/**
* Update field
*/
update(id: string, dto: UpdateFieldDto): Promise<SysField | null>;
/**
* Delete field
*/
delete(id: string): Promise<boolean>;
/**
* Reorder fields
*/
reorder(tableId: string, fieldIds: string[]): Promise<void>;
/**
* Convert entity to interface
*/
toInterface(entity: SysField): TableField;
}
declare class ViewService {
private readonly viewRepo;
constructor(viewRepo: Repository<SysView>);
/**
* Create a new view
*/
create(dto: CreateViewDto): Promise<SysView>;
/**
* Find all views of a table
*/
findByTable(tableId: string): Promise<SysView[]>;
/**
* Find view by ID
*/
findById(id: string): Promise<SysView | null>;
/**
* Find default view of a table
*/
findDefault(tableId: string): Promise<SysView | null>;
/**
* Update view
*/
update(id: string, dto: UpdateViewDto): Promise<SysView | null>;
/**
* Delete view
*/
delete(id: string): Promise<boolean>;
}
/**
* Service để parse và xử lý công thức (Formula)
*/
declare class FormulaService {
/**
* Parse formula expression thành SQL expression
* Input: "{price} * {qty}"
* Output: "COALESCE((record.data->>'price')::numeric, 0) * COALESCE((record.data->>'qty')::numeric, 0)"
*/
parseToSQL(expression: string, tableAlias?: string): string;
/**
* Parse formula với type casting dựa trên field type
*/
parseToSQLWithType(expression: string, fields: TableField[], tableAlias?: string): string;
/**
* Validate formula expression
*/
validate(expression: string, availableFields: string[]): {
valid: boolean;
errors: string[];
};
/**
* Extract field references from formula
*/
extractFieldReferences(expression: string): string[];
/**
* Get PostgreSQL cast type for field type
*/
getPostgresCastType(type?: FieldType): string;
/**
* Get default value for field type
*/
getDefaultValue(type?: FieldType): string;
/**
* Build SQL expression for computed field
*/
buildComputedFieldSQL(field: TableField, tableAlias?: string): string | null;
}
declare class RecordQueryService {
private readonly recordRepo;
private readonly formulaService;
constructor(recordRepo: Repository<UsrRecord>, formulaService: FormulaService);
/**
* Get records with dynamic filters, sorting, and pagination
*/
getRecords(tableId: string, fields: TableField[], options?: QueryOptions): Promise<PaginatedResult<Record<string, any>>>;
/**
* Get single record by ID
*/
getRecordById(recordId: string, fields: TableField[]): Promise<Record<string, any> | null>;
/**
* Add formula fields as computed columns
*/
private addFormulaSelects;
/**
* Apply filters to query
*/
private applyFilters;
/**
* Apply single filter
*/
private applySingleFilter;
/**
* Apply full-text search
*/
private applySearch;
/**
* Apply sorting
*/
private applySort;
/**
* Normalize pagination params
*/
private normalizePagination;
/**
* Build interval expression for is_within filter
*/
private buildIntervalExpression;
/**
* Map raw query results to clean response
*/
private mapRawResults;
/**
* Map single raw result
*/
private mapSingleRawResult;
}
declare class RecordService {
private readonly recordRepo;
private readonly historyRepo;
private readonly dataSource;
private readonly recordQueryService;
private readonly fieldService;
constructor(recordRepo: Repository<UsrRecord>, historyRepo: Repository<UsrRecordHistory>, dataSource: DataSource, recordQueryService: RecordQueryService, fieldService: FieldService);
/**
* Create a new record
*/
create(dto: CreateRecordDto): Promise<UsrRecord>;
/**
* Batch create records
*/
batchCreate(dto: BatchCreateRecordDto): Promise<UsrRecord[]>;
/**
* Find all records with query options
*/
findAll(tableId: string, options?: QueryOptions): Promise<PaginatedResult<Record<string, any>>>;
/**
* Find record by ID
*/
findById(id: string): Promise<UsrRecord | null>;
/**
* Find record by ID with computed fields
*/
findByIdWithComputed(id: string, tableId: string): Promise<Record<string, any> | null>;
/**
* Update record
*/
update(id: string, dto: UpdateRecordDto, trackHistory?: boolean): Promise<UsrRecord | null>;
/**
* Patch record (partial update)
*/
patch(id: string, data: Record<string, any>, updatedBy?: string): Promise<UsrRecord | null>;
/**
* Delete record
*/
delete(id: string): Promise<boolean>;
/**
* Batch delete records
*/
batchDelete(ids: string[]): Promise<number>;
/**
* Get record history
*/
getHistory(recordId: string): Promise<UsrRecordHistory[]>;
/**
* Filter out computed fields from data
*/
private filterComputedFields;
}
interface ValidationError {
field: string;
message: string;
code: string;
}
interface ValidationResult {
valid: boolean;
errors: ValidationError[];
}
declare class ValidationService {
/**
* Validate record data against field definitions
*/
validate(data: Record<string, any>, fields: TableField[]): ValidationResult;
/**
* Validate single field value
*/
validateField(value: any, field: TableField): ValidationError[];
/**
* Check if value is empty
*/
private isEmpty;
/**
* Validate email format
*/
private isValidEmail;
/**
* Validate URL format
*/
private isValidUrl;
/**
* Validate phone number format
*/
private isValidPhone;
/**
* Validate number
*/
private isValidNumber;
/**
* Validate rating
*/
private isValidRating;
/**
* Validate date
*/
private isValidDate;
/**
* Validate select value
*/
private isValidSelect;
/**
* Validate multi-select values
*/
private isValidMultiSelect;
}
export { type BatchCreateRecordDto, COMPUTED_FIELD_TYPES, type CreateFieldDto, type CreateRecordDto, type CreateTableDto, type CreateViewDto, type CreateWorkspaceDto, DYNAMIC_TABLE_ENTITIES, DYNAMIC_TABLE_OPTIONS, type DatabaseConfig, type DynamicTable, DynamicTableModule, type DynamicTableModuleAsyncOptions, type DynamicTableModuleOptions, type DynamicTableOptionsFactory, FIELD_TYPE_METADATA, type FieldConfig, FieldService, FieldType, type FieldTypeMetadata, type FilterOperator, type FilterParams, FormulaService, type PaginatedResult, type PaginationParams, type QueryOptions, type RecordHistoryEntry, RecordQueryService, RecordService, type RollupFunction, type SelectOption, type SortParams, SysField, SysTable, SysView, SysWorkspace, type TableField, type TableRecord, TableService, type TableView, type UpdateFieldDto, type UpdateRecordDto, type UpdateTableDto, type UpdateViewDto, type UpdateWorkspaceDto, UsrRecord, UsrRecordHistory, type ValidationError, type ValidationResult, ValidationService, type ViewConfig, ViewService, type ViewType, type Workspace, WorkspaceService };