@mobtakronio/schemakit
Version:
Dynamic entity management system with runtime schema creation, validation, and CRUD operations for Node.js backends.
1,832 lines (1,806 loc) • 86.9 kB
TypeScript
/**
* Database adapter configuration options
*/
interface DatabaseAdapterConfig {
filename?: string;
host?: string;
port?: number;
database?: string;
user?: string;
password?: string;
ssl?: boolean;
connectionString?: string;
[key: string]: any;
}
/**
* Column definition for table creation
*/
interface ColumnDefinition {
name: string;
type: string;
primaryKey?: boolean;
notNull?: boolean;
unique?: boolean;
default?: any;
references?: {
table: string;
column: string;
onDelete?: 'CASCADE' | 'RESTRICT' | 'SET NULL';
};
}
/**
* Transaction callback function type
*/
type TransactionCallback<T> = (transaction: DatabaseAdapter) => Promise<T>;
/**
* Query filter interface (from EntityKit pattern)
*/
interface QueryFilter$1 {
field: string;
value: any;
operator?: 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte' | 'like' | 'in' | 'nin' | 'contains' | 'startswith' | 'endswith';
}
/**
* Query options interface (from EntityKit pattern)
*/
interface QueryOptions$1 {
orderBy?: {
field: string;
direction: 'ASC' | 'DESC';
}[];
limit?: number;
offset?: number;
}
/**
* Abstract database adapter class
*/
declare abstract class DatabaseAdapter {
protected config: DatabaseAdapterConfig;
/**
* Create a new database adapter
* @param config Configuration options
*/
constructor(config?: DatabaseAdapterConfig);
/**
* Connect to the database
*/
abstract connect(): Promise<void>;
/**
* Disconnect from the database
*/
abstract disconnect(): Promise<void>;
/**
* Check if connected to the database
*/
abstract isConnected(): boolean;
/**
* Execute a query that returns rows
* @param sql SQL query
* @param params Query parameters
*/
abstract query<T = any>(sql: string, params?: any[]): Promise<T[]>;
/**
* Execute a query that doesn't return rows
* @param sql SQL query
* @param params Query parameters
*/
abstract execute(sql: string, params?: any[]): Promise<{
changes: number;
lastInsertId?: string | number;
}>;
/**
* Execute a function within a transaction
* @param callback Function to execute within transaction
*/
abstract transaction<T>(callback: TransactionCallback<T>): Promise<T>;
/**
* Check if a table exists
* @param tableName Table name
*/
abstract tableExists(tableName: string): Promise<boolean>;
/**
* Create a new table
* @param tableName Table name
* @param columns Column definitions
*/
abstract createTable(tableName: string, columns: ColumnDefinition[]): Promise<void>;
/**
* Get column information for a table
* @param tableName Table name
*/
abstract getTableColumns(tableName: string): Promise<ColumnDefinition[]>;
/**
* Select records from a table
* @param table Table name
* @param filters Query filters
* @param options Query options
*/
abstract select(table: string, filters: QueryFilter$1[], options: QueryOptions$1): Promise<any[]>;
/**
* Insert a record into a table
* @param table Table name
* @param data Data to insert
*/
abstract insert(table: string, data: Record<string, any>): Promise<any>;
/**
* Update a record in a table
* @param table Table name
* @param id Record ID
* @param data Data to update
*/
abstract update(table: string, id: string, data: Record<string, any>): Promise<any>;
/**
* Delete a record from a table
* @param table Table name
* @param id Record ID
*/
abstract delete(table: string, id: string): Promise<void>;
/**
* Count records in a table
* @param table Table name
* @param filters Query filters
*/
abstract count(table: string, filters: QueryFilter$1[]): Promise<number>;
/**
* Find a record by ID
* @param table Table name
* @param id Record ID
*/
abstract findById(table: string, id: string): Promise<any | null>;
/**
* Create a database schema (for multi-tenancy)
* @param schemaName Schema name
*/
abstract createSchema(schemaName: string): Promise<void>;
/**
* Drop a database schema
* @param schemaName Schema name
*/
abstract dropSchema(schemaName: string): Promise<void>;
/**
* List all database schemas
*/
abstract listSchemas(): Promise<string[]>;
/**
* Create a database adapter instance
* @param type Adapter type ('sqlite', 'postgres', or 'inmemory')
* @param config Configuration options
*/
static create(type?: string, config?: DatabaseAdapterConfig): Promise<DatabaseAdapter>;
/**
* Create a database adapter instance synchronously (for backward compatibility)
* @param type Adapter type ('sqlite', 'postgres', or 'inmemory')
* @param config Configuration options
*/
static createSync(type?: string, config?: DatabaseAdapterConfig): DatabaseAdapter;
}
type WhereClause = Record<string, any>;
interface DBOptions {
adapter: string;
tenantId: string;
config?: any;
multiTenancy?: MultiTenancyConfig;
}
interface MultiTenancyConfig {
strategy: 'schema' | 'table-prefix' | 'column' | 'none';
columnName?: string;
separator?: string;
}
declare class DB {
private adapter;
private adapterConfig;
private tenantId;
private multiTenancy;
private _isQueryBuilder;
private _select;
private _from;
private _where;
private _orWhere;
private _orderBy;
private _limit?;
private _offset?;
constructor(opts: DBOptions);
/**
* Initialize the database adapter
* Must be called before using the DB instance
*/
init(): Promise<void>;
/**
* Ensure adapter is initialized
*/
private ensureAdapter;
/**
* Resolve table name based on multi-tenancy strategy
*/
private resolveTableName;
/**
* Add tenant filter based on multi-tenancy strategy
*/
private addTenantFilter;
/**
* Add tenant data to insert/update operations
*/
private addTenantData;
/**
* Create a forked query builder to avoid mutating shared DB state across concurrent queries
*/
private fork;
private asBuilder;
select(fields: string | string[]): this;
from(table: string): this;
where(conditions: WhereClause): this;
orWhere(conditions: WhereClause): this;
orderBy(field: string, dir?: "ASC" | "DESC"): this;
limit(n: number): this;
/**
* Set result offset for pagination
*/
offset(n: number): this;
/**
* Helper to convert where/orWhere to QueryFilter[]
*/
private buildFilters;
/**
* Helper to convert orderBy/limit to QueryOptions
*/
private buildOptions;
get(table?: string): Promise<any>;
insert(table: string, data: Record<string, any>): Promise<any>;
update(table: string, data: Record<string, any>): Promise<any>;
delete(table: string): Promise<any>;
reset(): this;
/**
* Get the underlying adapter instance (escape hatch)
* @warning This bypasses some SchemaKit features. Use with caution.
*/
getAdapter(): Promise<DatabaseAdapter>;
/**
* Execute a raw query
* @warning This bypasses multi-tenancy and other SchemaKit features
*/
raw<T = any>(sql: string, params?: any[]): Promise<T[]>;
/**
* Execute within a transaction
*/
transaction<T>(callback: (trx: DB) => Promise<T>): Promise<T>;
/**
* Create a new DB instance with a different tenant
*/
withTenant(tenantId: string): DB;
/**
* Get current tenant ID
*/
getTenantId(): string;
/**
* Get multi-tenancy configuration
*/
getMultiTenancyConfig(): MultiTenancyConfig;
/**
* Create schema for tenant (if using schema-based multi-tenancy)
*/
createTenantSchema(tenantId: string): Promise<void>;
/**
* Drop schema for tenant (if using schema-based multi-tenancy)
*/
dropTenantSchema(tenantId: string): Promise<void>;
/**
* List all tenant schemas
*/
listTenantSchemas(): Promise<string[]>;
}
/**
* Common base types used across SchemaKit
*
* These fundamental types provide the building blocks for all SchemaKit functionality.
* @since 0.1.0
*/
/**
* Supported field data types in SchemaKit entities
*
* @example
* ```typescript
* const field: FieldDefinition = {
* name: 'email',
* type: 'string', // FieldType
* is_required: true
* };
* ```
*
* @since 0.1.0
*/
type FieldType = 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'datetime' | 'json' | 'object' | 'array' | 'reference' | 'computed';
/**
* Standard CRUD operations supported by SchemaKit
*
* @example
* ```typescript
* const permission: PermissionDefinition = {
* role: 'user',
* action: 'read', // OperationType
* is_allowed: true
* };
* ```
*
* @since 0.1.0
*/
type OperationType = 'create' | 'read' | 'update' | 'delete' | 'list';
/**
* Sort direction for query ordering
*
* @example
* ```typescript
* const sort: SortDefinition = {
* field: 'created_at',
* direction: 'desc' // SortDirection - newest first
* };
* ```
*
* @since 0.1.0
*/
type SortDirection = 'asc' | 'desc';
/**
* SQL JOIN types for entity relationships
*
* @example
* ```typescript
* const join: JoinDefinition = {
* entity: 'users',
* type: 'left', // JoinType - include all records from left table
* on: 'orders.user_id = users.id'
* };
* ```
*
* @since 0.1.0
*/
type JoinType = 'inner' | 'left' | 'right';
/**
* Events that can trigger workflow execution
*
* @example
* ```typescript
* const workflow: WorkflowDefinition = {
* trigger_event: 'create', // WorkflowTrigger - run after record creation
* actions: [{ type: 'send-email', config: {...} }]
* };
* ```
*
* @since 0.1.0
*/
type WorkflowTrigger = 'create' | 'update' | 'delete' | 'field_change';
/**
* Core SchemaKit configuration and context types
*
* These types define how SchemaKit is configured and how execution context
* is passed throughout the system for permissions and multi-tenancy.
*
* @since 0.1.0
*/
/**
* Configuration options for initializing SchemaKit
*
* @example
* ```typescript
* const schemaKit = new SchemaKit({
* adapter: {
* type: 'postgres',
* config: {
* connectionString: process.env.DATABASE_URL
* }
* },
* cache: {
* enabled: true,
* ttl: 300 // 5 minutes
* }
* });
* ```
*
* @since 0.1.0
*/
interface SchemaKitOptions {
/** Database adapter configuration */
adapter?: {
/** Adapter type: 'postgres', 'sqlite', 'inmemory' */
type?: string;
/** Adapter-specific configuration options */
config?: Record<string, any>;
};
/** Entity caching configuration */
cache?: {
/** Whether to enable entity definition caching */
enabled?: boolean;
/** Cache time-to-live in seconds */
ttl?: number;
};
}
/**
* Execution context for operations, permissions, and multi-tenancy
*
* Context flows through all SchemaKit operations to provide user information,
* tenant isolation, and request metadata for permissions and auditing.
*
* @example
* ```typescript
* const context: Context = {
* user: {
* id: 'user-123',
* roles: ['manager', 'user'],
* department: 'sales'
* },
* tenantId: 'company-abc',
* request: {
* ip: '192.168.1.1',
* userAgent: 'Mozilla/5.0...'
* }
* };
*
* // Context is used throughout operations
* await entity.create(data, context);
* await entity.view('sales-report', {}, context);
* ```
*
* @since 0.1.0
*/
interface Context {
/** Current user information for permissions and RLS */
user?: {
/** Unique user identifier */
id?: string;
/** User roles for permission checking */
roles?: string[];
/** Direct permissions granted to user */
permissions?: string[];
/** Additional user properties (department, etc.) */
[key: string]: any;
};
/** Tenant ID for multi-tenant data isolation */
tenantId?: string;
/** HTTP request information for auditing */
request?: {
/** Client IP address */
ip?: string;
/** Client user agent string */
userAgent?: string;
/** Request timestamp */
timestamp?: string;
/** Additional request metadata */
[key: string]: any;
};
/** User session information */
session?: {
/** Session identifier */
id?: string;
/** Session expiration time */
expires?: string;
/** Additional session data */
[key: string]: any;
};
/** Additional context properties */
[key: string]: any;
}
/**
* Workflow and automation types
*
* Workflows in SchemaKit provide automated responses to entity lifecycle
* events, enabling business process automation and integration.
*
* @since 0.1.0
*/
/**
* Single action to execute in a workflow
*
* @example
* ```typescript
* // Send email action
* const emailAction: WorkflowAction = {
* type: 'send-email',
* config: {
* to: '{{user.email}}',
* subject: 'Welcome to {{app.name}}',
* template: 'welcome-email',
* data: {
* userName: '{{user.name}}',
* activationLink: '{{user.activationUrl}}'
* }
* }
* };
*
* // API call action
* const webhookAction: WorkflowAction = {
* type: 'webhook',
* config: {
* url: 'https://api.example.com/user-created',
* method: 'POST',
* headers: {
* 'Authorization': 'Bearer {{app.apiKey}}'
* },
* body: {
* userId: '{{record.id}}',
* email: '{{record.email}}'
* }
* }
* };
*
* // Database action
* const updateAction: WorkflowAction = {
* type: 'update-record',
* config: {
* entity: 'user_stats',
* where: { user_id: '{{record.id}}' },
* data: {
* last_login: '{{now}}',
* login_count: '{{increment}}'
* }
* }
* };
* ```
*
* @since 0.1.0
*/
interface WorkflowAction {
/** Action type (e.g., 'send-email', 'webhook', 'update-record') */
type: string;
/** Action-specific configuration with template support */
config: Record<string, any>;
}
/**
* Complete workflow definition stored in system_workflows table
*
* Workflows define automated processes that run in response to entity
* lifecycle events, with optional conditions and multiple actions.
*
* @example
* ```typescript
* // User welcome workflow
* const welcomeWorkflow: WorkflowDefinition = {
* id: 'wf_001',
* entity_id: 'ent_users_001',
* name: 'user-welcome',
* trigger_event: 'create',
* conditions: {
* // Only for active users
* is_active: true,
* // Only for specific user types
* user_type: { $in: ['customer', 'premium'] }
* },
* actions: [
* {
* type: 'send-email',
* config: {
* template: 'welcome-email',
* to: '{{record.email}}',
* data: { name: '{{record.name}}' }
* }
* },
* {
* type: 'webhook',
* config: {
* url: 'https://api.crm.com/users',
* method: 'POST',
* body: '{{record}}'
* }
* }
* ],
* is_active: true,
* order_index: 1,
* metadata: {
* description: 'Welcome new users and sync with CRM',
* created_by: 'admin',
* version: '1.0'
* }
* };
*
* // Order processing workflow
* const orderWorkflow: WorkflowDefinition = {
* id: 'wf_002',
* entity_id: 'ent_orders_001',
* name: 'order-processing',
* trigger_event: 'update',
* conditions: {
* // Only when status changes to 'paid'
* status: 'paid',
* // Only for orders over $100
* total: { $gte: 100 }
* },
* actions: [
* {
* type: 'update-inventory',
* config: {
* items: '{{record.items}}',
* operation: 'decrease'
* }
* },
* {
* type: 'send-email',
* config: {
* template: 'order-confirmation',
* to: '{{record.customer_email}}'
* }
* }
* ],
* is_active: true,
* order_index: 1
* };
* ```
*
* @since 0.1.0
*/
interface WorkflowDefinition {
/** Unique identifier for this workflow */
workflow_id: string;
/** Entity this workflow applies to */
workflow_entity_id: string;
/** Workflow name for reference */
workflow_name: string;
/** Event that triggers this workflow */
workflow_trigger_event: WorkflowTrigger;
/** Optional conditions that must be met to execute */
workflow_conditions?: Record<string, any>;
/** Array of actions to execute in order */
workflow_actions: WorkflowAction[];
/** Whether this workflow is currently active */
workflow_is_active: boolean;
/** Execution order when multiple workflows match */
workflow_order_index: number;
/** Additional metadata */
workflow_metadata?: Record<string, any>;
}
/**
* Definition for joining related entities in views
*
* @example
* ```typescript
* const join: JoinDefinition = {
* entity: 'users',
* type: 'left',
* on: 'orders.user_id = users.id',
* alias: 'creator'
* };
* ```
*
* @since 0.1.0
*/
interface JoinDefinition {
/** Entity name to join with */
entity: string;
/** Type of join (default: 'inner') */
type?: JoinType;
/** JOIN condition (e.g., 'orders.user_id = users.id') */
on: string;
/** Optional alias for the joined entity */
alias?: string;
}
/**
* Sorting configuration for views
*
* @example
* ```typescript
* const sort: SortDefinition = {
* field: 'created_at',
* direction: 'desc' // Newest first
* };
* ```
*
* @since 0.1.0
*/
interface SortDefinition {
/** Field name to sort by */
field: string;
/** Sort direction */
direction: SortDirection;
}
/**
* Pagination defaults for views
*
* @example
* ```typescript
* const pagination: PaginationDefinition = {
* default_limit: 20,
* max_limit: 100
* };
* ```
*
* @since 0.1.0
*/
interface PaginationDefinition {
/** Default number of records per page */
default_limit: number;
/** Maximum allowed records per page */
max_limit: number;
}
/**
* Complete view definition stored in system_views table
*
* Views provide pre-configured query templates that users can execute
* with optional runtime filters and pagination.
*
* @example
* ```typescript
* const view: ViewDefinition = {
* id: 'view_001',
* entity_id: 'ent_orders_001',
* name: 'recent-orders',
* query_config: {
* filters: {
* status: 'active',
* created_at: { $gte: '2024-01-01' }
* },
* sorting: [
* { field: 'created_at', direction: 'desc' },
* { field: 'priority', direction: 'asc' }
* ],
* joins: [{
* entity: 'users',
* type: 'left',
* on: 'orders.user_id = users.id'
* }],
* pagination: {
* default_limit: 20,
* max_limit: 100
* }
* },
* fields: ['id', 'title', 'status', 'created_at', 'users.name'],
* is_default: false,
* created_by: 'admin',
* is_public: true,
* metadata: { description: 'Recent active orders with user info' }
* };
* ```
*
* @since 0.1.0
*/
interface ViewDefinition {
/** Unique identifier for this view */
view_id: string;
/** Entity this view applies to */
view_entity_id: string;
/** View name for reference */
view_name: string;
/** Audit fields */
view_created_at?: string;
view_updated_at?: string;
view_created_by?: string;
view_modified_by?: string;
/** Ordering and placement */
view_weight?: number;
view_status?: string;
view_slot?: number;
/** Fields to include in results (empty = all fields) */
view_fields?: string[];
/** Fixed filters applied to all executions */
view_filters?: Record<string, any>;
/** Entity joins to include related data */
view_joins?: JoinDefinition[];
/** Default sorting order */
view_sort?: SortDefinition[];
/** Optional display title */
view_title?: string;
/** Optional: retained for compatibility with older data; ignored by new code */
view_query_config?: {
filters?: Record<string, any>;
joins?: JoinDefinition[];
sorting?: SortDefinition[];
pagination?: PaginationDefinition;
};
view_is_default?: boolean;
view_is_public?: boolean;
view_metadata?: Record<string, any>;
}
/**
* Runtime options for executing views
*
* @example
* ```typescript
* const options: ViewOptions = {
* filters: {
* priority: 'high',
* assignee: 'john-doe'
* },
* pagination: {
* page: 2,
* limit: 50
* }
* };
*
* const result = await entity.view('active-tasks', options, context);
* ```
*
* @since 0.1.0
*/
interface ViewOptions {
/** Additional filters to apply at runtime */
filters?: Record<string, any>;
/** Pagination options */
pagination?: {
/** Page number (1-based) */
page: number;
/** Records per page */
limit: number;
};
/** Whether to compute totals/stats (grouped counts) */
stats?: boolean;
}
/**
* Result from executing a view
*
* @example
* ```typescript
* const result: ViewResult = {
* results: [
* { id: 1, name: 'John', department: 'Engineering' },
* { id: 2, name: 'Jane', department: 'Marketing' }
* ],
* total: 2,
* fields: [
* { name: 'id', type: 'integer', display_name: 'ID' },
* { name: 'name', type: 'string', display_name: 'Full Name' }
* ],
* meta: {
* entityName: 'users',
* viewName: 'active-users',
* query: 'SELECT id, name FROM users WHERE status = ?'
* }
* };
* ```
*
* @since 0.1.0
*/
interface ViewResult {
/** Array of records returned by the view */
results: any[];
/** Total number of matching records (before pagination) */
total: number;
/** Field definitions for the returned columns */
fields: FieldDefinition[];
/** Execution metadata */
meta: {
/** Entity name the view was executed on */
entityName: string;
/** Name of the view that was executed */
viewName: string;
/** Generated SQL query (for debugging) */
query?: string;
};
/** Optional: stats array similar to legacy temp output */
stats?: any[];
defaultField?: string;
modelName?: string;
modelSystemName?: string;
isProcessOn?: boolean;
addAllowed?: boolean;
modifyAllowed?: boolean;
manageAllowed?: boolean;
}
/**
* Pagination types for queries and views
*/
interface PaginationOptions {
page?: number;
pageSize?: number;
limit?: number;
offset?: number;
}
interface PaginationResult<T = any> {
data: T[];
pagination?: {
current_page: number;
per_page: number;
total: number;
total_pages: number;
has_previous: boolean;
has_next: boolean;
};
meta?: {
total: number;
page: number;
per_page: number;
has_more: boolean;
total_pages: number;
};
}
/**
* Permission and authorization types
*
* These types define role-based access control (RBAC) and field-level
* permissions for entities in SchemaKit.
*
* @since 0.1.0
*/
/**
* Permission rule for entity access control
*
* Stored in the `system_permissions` table, defines what roles can perform
* which operations on entities, with optional conditions and field restrictions.
*
* @example
* ```typescript
* // Basic permission - managers can read customer records
* const permission: PermissionDefinition = {
* id: 'perm_001',
* entity_id: 'ent_customers_001',
* role: 'manager',
* action: 'read',
* is_allowed: true,
* created_at: '2024-01-01T00:00:00Z'
* };
*
* // Conditional permission - users can only update their own records
* const conditionalPerm: PermissionDefinition = {
* id: 'perm_002',
* entity_id: 'ent_orders_001',
* role: 'user',
* action: 'update',
* conditions: {
* field: 'created_by',
* operator: 'eq',
* value: '$user.id'
* },
* is_allowed: true,
* created_at: '2024-01-01T00:00:00Z'
* };
*
* // Field-level permissions - hide sensitive fields from regular users
* const fieldPerm: PermissionDefinition = {
* id: 'perm_003',
* entity_id: 'ent_users_001',
* role: 'user',
* action: 'read',
* is_allowed: true,
* field_permissions: {
* 'salary': false, // Hide salary field
* 'email': true, // Show email field
* 'phone_read': true, // Can read phone
* 'phone_write': false // Cannot modify phone
* },
* created_at: '2024-01-01T00:00:00Z'
* };
* ```
*
* @since 0.1.0
*/
interface PermissionDefinition {
/** Unique identifier for this permission rule */
permission_id: string;
/** ID of the entity this permission applies to */
permission_entity_id: string;
/** Role name this permission is granted to */
permission_role: string;
/** Operation type this permission controls */
permission_action: OperationType;
/** Optional conditions that must be met (JSON object) */
permission_conditions?: Record<string, any>;
/** Whether this permission grants (true) or denies (false) access */
permission_is_allowed: boolean;
/** Whether this permission rule is currently active */
permission_is_active?: boolean;
/** ISO timestamp when permission was created */
permission_created_at: string;
/** Field-level permissions (field_name: allowed, field_name_read: read_only) */
permission_field_permissions?: Record<string, boolean>;
}
/**
* Row Level Security (RLS) types
*
* These types implement SchemaKit's advanced row-level security system,
* allowing fine-grained control over which records users can access.
*
* @since 0.1.0
*/
/**
* Single condition for row-level security filtering
*
* Defines a filter condition that can be applied to queries to restrict
* which rows a user can access based on their role and context.
*
* @example
* ```typescript
* // Users can only see records in their department
* const departmentCondition: RLSCondition = {
* field: 'department',
* op: 'eq',
* value: 'currentUser.department',
* exposed: false // Hidden from user, automatically applied
* };
*
* // Analysts can filter by priority (user-modifiable)
* const priorityCondition: RLSCondition = {
* field: 'priority',
* op: 'in',
* value: ['high', 'urgent'],
* exposed: true, // User can modify this filter
* metadata: {
* type: 'string',
* options: ['low', 'medium', 'high', 'urgent']
* }
* };
* ```
*
* @since 0.1.0
*/
interface RLSCondition {
/** Field name to filter on */
field: string;
/** Comparison operator */
op: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'notIn' | 'like' | 'isNull' | 'notNull';
/** Value to compare against (can include context variables like 'currentUser.id') */
value: any;
/** Whether users can see and modify this condition */
exposed?: boolean;
/** Metadata for exposed conditions (validation, UI hints) */
metadata?: {
/** Data type for validation */
type: 'number' | 'string';
/** Minimum value (for numbers) */
min?: number;
/** Maximum value (for numbers) */
max?: number;
/** Allowed values (for dropdowns) */
options?: string[];
};
}
/**
* Group of conditions combined with AND/OR logic
*
* @example
* ```typescript
* // Manager role: can see active records in their department
* const managerRule: RLSRule = {
* conditions: [
* { field: 'department', op: 'eq', value: 'currentUser.department' },
* { field: 'status', op: 'eq', value: 'active' }
* ],
* combinator: 'AND' // Both conditions must be true
* };
* ```
*
* @since 0.1.0
*/
interface RLSRule {
/** List of conditions to evaluate */
conditions: RLSCondition[];
/** How to combine conditions (AND = all must be true, OR = any can be true) */
combinator: 'AND' | 'OR';
}
/**
* Complete RLS configuration mapping roles to their restrictions
*
* @example
* ```typescript
* const restrictions: RoleRestrictions = {
* // Admins see everything in their department
* 'admin': [{
* conditions: [{ field: 'department', op: 'eq', value: 'currentUser.department' }],
* combinator: 'AND'
* }],
*
* // Users only see their own records
* 'user': [{
* conditions: [{ field: 'created_by', op: 'eq', value: 'currentUser.id' }],
* combinator: 'AND'
* }],
*
* // Analysts have complex filtering options
* 'analyst': [{
* conditions: [
* { field: 'priority', op: 'in', value: ['high'], exposed: true },
* { field: 'created_at', op: 'gte', value: '2024-01-01' }
* ],
* combinator: 'AND'
* }]
* };
* ```
*
* @since 0.1.0
*/
interface RoleRestrictions {
/** Role name mapped to array of rules (multiple rules are combined with OR) */
[role: string]: RLSRule[];
}
/**
* Persistent RLS definition stored in system_rls table
*
* @example
* ```typescript
* const rlsDef: RLSDefinition = {
* id: 'rls_001',
* entity_id: 'ent_orders_001',
* role: 'manager',
* rls_config: {
* relationbetweenconditions: 'and',
* conditions: [
* { field: 'department', op: 'eq', value: '$context.user.department' }
* ]
* },
* is_active: true,
* created_at: '2024-01-01T00:00:00Z',
* updated_at: '2024-01-01T00:00:00Z'
* };
* ```
*
* @since 0.1.0
*/
interface RLSDefinition {
/** Unique identifier for this RLS rule */
rls_id: string;
/** Entity this rule applies to */
rls_entity_id: string;
/** Role this rule applies to */
rls_role: string;
/** Optional view this rule is specific to */
rls_view_id?: string;
/** RLS configuration */
rls_config: {
/** How to combine multiple conditions */
relationbetweenconditions: 'and' | 'or';
/** Array of filter conditions */
conditions: RLSCondition[];
};
/** Whether this rule is currently active */
rls_is_active: boolean;
/** ISO timestamp when rule was created */
rls_created_at: string;
/** ISO timestamp when rule was last updated */
rls_updated_at: string;
}
/**
* Runtime RLS conditions (internal use)
*
* @internal
* @since 0.1.0
*/
interface RLSConditions {
/** Generated SQL WHERE clause (legacy) */
sql?: string;
/** SQL parameters (legacy) */
params?: any[];
/** Structured conditions (new format) */
conditions?: Array<{
field: string;
operator: string;
value: any;
}>;
}
/**
* Complete definition of a SchemaKit entity (table)
*
* Stored in the `system_entities` table, this defines a business entity
* with its metadata, table name, and display information.
*
* @example
* ```typescript
* const entity: EntityDefinition = {
* entity_id: 'ent_customers_001',
* entity_name: 'customers',
* entity_table_name: 'app_customers',
* entity_display_name: 'Customers',
* entity_description: 'Customer database records',
* entity_is_active: true,
* entity_created_at: '2024-01-01T00:00:00Z',
* entity_updated_at: '2024-01-01T00:00:00Z',
* entity_metadata: { version: '1.0' }
* };
* ```
*
* @since 0.1.0
*/
interface EntityDefinition {
/** Unique identifier for this entity */
entity_id: string;
/** Entity name used in code (e.g., 'users', 'orders') */
entity_name: string;
/** Actual database table name (e.g., 'app_users') */
entity_table_name: string;
/** Human-readable display name */
entity_display_name: string;
/** Description of what this entity represents */
entity_description: string;
/** Whether this entity is currently active */
entity_is_active: boolean;
/** ISO timestamp when entity was created */
entity_created_at: string;
/** ISO timestamp when entity was last updated */
entity_updated_at: string;
/** Additional metadata as JSON object */
entity_metadata?: Record<string, any>;
}
/**
* Definition of a field (column) within an entity
*
* Stored in the `system_fields` table, this defines individual columns
* with their types, constraints, and display information.
*
* @example
* ```typescript
* const field: FieldDefinition = {
* id: 'fld_email_001',
* entity_id: 'ent_users_001',
* name: 'email',
* type: 'string',
* is_required: true,
* is_unique: true,
* display_name: 'Email Address',
* description: 'User email for login',
* order_index: 2,
* is_active: true,
* validation_rules: {
* format: 'email',
* maxLength: 255
* }
* };
* ```
*
* @since 0.1.0
*/
interface FieldDefinition {
/** Unique identifier for this field */
field_id: string;
/** ID of the entity this field belongs to */
field_entity_id: string;
/** Field name used in code (e.g., 'email', 'created_at') */
field_name: string;
/** Data type of this field */
field_type: FieldType;
/** Whether this field must have a value */
field_is_required: boolean;
/** Whether values in this field must be unique */
field_is_unique: boolean;
/** Whether this field is part of the primary key */
field_is_primary_key?: boolean;
/** Default value for new records (as string) */
field_default_value?: string;
/** Validation rules as JSON object */
field_validation_rules?: Record<string, any>;
/** Human-readable display name */
field_display_name: string;
/** Description of what this field represents */
field_description?: string;
/** Display order (0-based index) */
field_order_index: number;
/** Whether this field is currently active */
field_is_active: boolean;
/** Entity name this field references (for foreign keys) */
field_reference_entity?: string;
/** Additional metadata as JSON object */
field_metadata?: Record<string, any>;
}
/**
* Complete configuration for an entity including all related definitions
*
* This aggregates all the components that define an entity: the entity itself,
* its fields, permissions, views, workflows, and RLS rules.
*
* @example
* ```typescript
* const config: EntityConfiguration = {
* entity: entityDef,
* fields: [emailField, nameField],
* permissions: [readPermission, writePermission],
* views: [listView, detailView],
* workflows: [welcomeEmail],
* rls: [departmentRule]
* };
*
* // Used internally by SchemaKit to represent complete entity structure
* const entity = new Entity('users', 'tenant-123', db);
* await entity.initialize(); // Loads EntityConfiguration
* ```
*
* @since 0.1.0
*/
interface EntityConfiguration {
/** The entity definition */
entity: EntityDefinition;
/** All fields defined for this entity */
fields: FieldDefinition[];
/** Permission rules for this entity */
permissions: PermissionDefinition[];
/** View definitions for this entity */
views: ViewDefinition[];
/** Workflow definitions for this entity */
workflows: WorkflowDefinition[];
/** Row-level security rules for this entity */
rls: RLSDefinition[];
}
/**
* ViewManager - Execute views using loaded Entity metadata
*/
declare class ViewManager {
private db;
private entityName;
private tableName;
private fields;
private views;
private rlsManager;
constructor(db: DB, entityName: string, tableName: string, fields: FieldDefinition[], views: ViewDefinition[]);
/**
* Set RLS restrictions for role-based filtering
*/
setRLSRestrictions(restrictions: RoleRestrictions): void;
/**
* Execute a view by name
*/
executeView(viewName: string, context?: Context, options?: ViewOptions): Promise<ViewResult>;
/**
* Apply view field selection to query
*/
private applyViewSelect;
/**
* Apply view-defined filters to query
*/
private applyViewFilters;
/**
* Apply user-provided filters to query
*/
private applyUserFilters;
/**
* Apply sorting from view definition
*/
private applySorting;
/**
* Apply pagination from user options
*/
private resolvePagination;
private buildDefaultStats;
private buildCountFilters;
/**
* Apply RLS restrictions to query
*/
private applyRLSRestrictions;
/**
* Get exposed conditions for user filtering
*/
getExposedConditions(context: Context): any[];
}
type UnknownFieldPolicy = 'allow' | 'strip' | 'error';
type ValidationAction = 'create' | 'update';
interface ValidationIssue {
path: string;
message: string;
code?: string;
}
interface ValidationResult$1<T = any> {
ok: boolean;
data?: T;
errors?: ValidationIssue[];
}
/**
* Opaque compiled schema representation for a specific entity.
*/
interface CompiledSchema {
entityId: string;
fieldMap: Record<string, {
type: string;
required: boolean;
}>;
allowedKeys: Set<string>;
unknownFieldPolicy: UnknownFieldPolicy;
}
interface ValidationAdapter {
name: string;
buildSchema(entityId: string, fields: FieldDefinition[], options?: {
unknownFieldPolicy?: UnknownFieldPolicy;
}): CompiledSchema;
validate(action: ValidationAction, schema: CompiledSchema, input: unknown): ValidationResult$1<Record<string, any>>;
sanitizeFilters?(schema: CompiledSchema, filters: Record<string, any>): {
filters: Record<string, any>;
errors?: ValidationIssue[];
};
}
declare class Entity {
private static cache;
private readonly entityName;
private readonly tenantId;
private readonly db;
private initialized;
private validationAdapter;
private compiledSchema;
private unknownFieldPolicy;
fields: FieldDefinition[];
permissions: PermissionDefinition[];
workflow: WorkflowDefinition[];
rls: RLSDefinition[];
views: ViewDefinition[];
private entityDefinition;
private tableName;
viewManager: ViewManager | null;
static create(entityName: string, tenantId: string, db: DB): Entity;
/**
* Determine the primary key field name for this entity's table
*/
private getPrimaryKeyFieldName;
private constructor();
get isInitialized(): boolean;
get name(): string;
get tenant(): string;
/**
* Configure validation for this entity instance. Safe to call multiple times; will rebuild schema on next initialize.
*/
setValidation(adapter: ValidationAdapter, unknownFieldPolicy?: UnknownFieldPolicy): void;
initialize(context?: Context): Promise<void>;
/**
* Execute a view by name
*/
view(viewName: string, options?: ViewOptions, context?: Context): Promise<ViewResult>;
/**
* Read records with optional filters
*/
get(filters?: Record<string, any>, context?: Context): Promise<Record<string, any>[]>;
/**
* Find a record by ID
*/
getById(id: string | number, context?: Context): Promise<Record<string, any> | null>;
/**
* Create a new record
*/
insert(data: Record<string, any>, context?: Context): Promise<Record<string, any>>;
/**
* Update a record by ID
*/
update(id: string | number, data: Record<string, any>, context?: Context): Promise<Record<string, any>>;
/**
* Delete a record by ID
*/
delete(id: string | number, context?: Context): Promise<boolean>;
private ensureInitialized;
private loadEntityDefinition;
private loadFields;
private loadPermissions;
private loadWorkflows;
private loadRLS;
private loadViews;
private checkPermission;
private validateData;
private validateFieldType;
private buildRLSConditions;
private executeWorkflows;
private ensureTable;
static clearCache(entityName?: string, tenantId?: string): void;
static getCacheStats(): {
size: number;
entities: string[];
};
}
type SchemaKitInitOptions = {
adapter?: string;
config?: any;
multiTenancy?: MultiTenancyConfig;
cache?: {
enabled?: boolean;
ttl?: number;
};
validation?: {
adapter?: ValidationAdapter;
unknownFieldPolicy?: UnknownFieldPolicy;
};
};
declare class SchemaKit {
private readonly options;
readonly db: DB;
private validationAdapter;
constructor(options?: SchemaKitInitOptions);
/**
* Get or create an Entity instance
* @param name Entity name
* @param tenantId Tenant identifier (defaults to 'public')
*/
entity(name: string, tenantId?: string): Promise<Entity>;
}
/**
* Validation types for data integrity and business rules
*
* SchemaKit provides comprehensive validation for entity fields,
* supporting both built-in validators and custom business rules.
*
* @since 0.1.0
*/
/**
* Single validation error for a field
*
* @example
* ```typescript
* const error: ValidationError = {
* field: 'email',
* code: 'INVALID_FORMAT',
* message: 'Email address must be valid',
* value: 'invalid-email'
* };
* ```
*
* @since 0.1.0
*/
interface ValidationError {
/** Field name that failed validation */
field: string;
/** Error code for programmatic handling */
code: string;
/** Human-readable error message */
message: string;
/** The value that failed validation */
value?: any;
}
/**
* Non-blocking validation warning
*
* @example
* ```typescript
* const warning: ValidationWarning = {
* field: 'phone',
* message: 'Phone number format may not be recognized internationally',
* value: '555-1234'
* };
* ```
*
* @since 0.1.0
*/
interface ValidationWarning {
/** Field name with the warning */
field: string;
/** Warning message */
message: string;
/** The value that triggered the warning */
value?: any;
}
/**
* Complete validation result for data
*
* @example
* ```typescript
* // Successful validation
* const result: ValidationResult = {
* isValid: true,
* errors: [],
* warnings: []
* };
*
* // Failed validation
* const failedResult: ValidationResult = {
* isValid: false,
* errors: [
* {
* field: 'email',
* code: 'REQUIRED',
* message: 'Email is required',
* value: undefined
* }
* ],
* warnings: [
* {
* field: 'phone',
* message: 'Consider using international format',
* value: '555-1234'
* }
* ]
* };
*
* // Usage in entity operations
* const validation = await entity.validate(data);
* if (!validation.isValid) {
* throw new ValidationError('Data validation failed', validation.errors);
* }
* ```
*
* @since 0.1.0
*/
interface ValidationResult {
/** Whether all validations passed */
isValid: boolean;
/** Array of validation errors (blocks operation if any) */
errors: ValidationError[];
/** Array of validation warnings (informational only) */
warnings?: ValidationWarning[];
}
/**
* ValidationManager - Simplified
* Essential validation functionality only
*/
/**
* Simplified ValidationManager class
* Essential validation only
*/
declare class ValidationManager {
/**
* Validate entity data against schema
*/
validate(entityConfig: EntityConfiguration, data: Record<string, any>, operation: 'create' | 'update'): Promise<ValidationResult>;
/**
* Validate a single field
*/
private validateField;
/**
* Validate field type
*/
private validateFieldType;
/**
* Validate custom rules
*/
private validateCustomRules;
}
/**
* TypeValidators
* Handles type-specific validation logic
*/
declare class TypeValidators {
/**
* Validate field type
*/
static validateFieldType(field: FieldDefinition, value: any): ValidationError[];
/**
* Check if value is a valid date
*/
private static isValidDate;
/**
* Check if value is valid JSON
*/
private static isValidJson;
}
/**
* PermissionManager
* Responsible for handling permissions and RLS
*/
/**
* PermissionManager class
* Single responsibility: Handle permissions and RLS
*/
declare class PermissionManager {
private databaseAdapter;
/**
* Create a new PermissionManager instance
* @param databaseAdapter Database adapter
*/
constructor(databaseAdapter: DatabaseAdapter);
/**
* Check if user has permission for action
* @param entityConfig Entity configuration
* @param action Action name
* @param context User context
* @returns True if user has permission
*/
checkPermission(entityConfig: EntityConfiguration, action: string, context?: Context): Promise<boolean>;
/**
* Get entity permissions for user
* @param entityConfig Entity configuration
* @param context User context
*/
getEntityPermissions(entityConfig: EntityConfiguration, context?: Context): Promise<Record<string, boolean>>;
/**
* Build RLS (Row Level Security) conditions
* @param entityConfig Entity configuration
* @param context User context
* @returns RLS conditions
*/
buildRLSConditions(entityConfig: EntityConfiguration, context: Context): RLSConditions;
/**
* Check field-level permissions
* @param entityConfig Entity configuration
* @param fieldName Field name
* @param action Action (read, write)
* @param context User context
*/
checkFieldPermission(entityConfig: EntityConfiguration, fieldName: string, action: 'read' | 'write', context?: Context): boolean;
/**
* Filter fields based on permissions
* @param entityConfig Entity configuration
* @param data Data object
* @param action Action (read, write)
* @param context User context
* @returns Filtered data object
*/
filterFieldsByPermissions(entityConfig: EntityConfiguration, data: Record<string, any>, action: 'read' | 'write', context?: Context): Record<string, any>;
/**
* Evaluate permission conditions
* @param conditions Permission conditions
* @param context User context
* @returns True if conditions are met
* @private
*/
private evaluatePermissionConditions;
/**
* Evaluate a single condition
* @param condition Single condition object
* @param context User context
* @returns True if condition is met
* @private
*/
private evaluateSingleCondition;
}
/**
* WorkflowManager
* Responsible for workflow execution
*/
/**
* WorkflowManager class
* Single responsibility: Handle workflow execution
*/
declare class WorkflowManager {
private databaseAdapter;
/**
* Create a new WorkflowManager instance
* @param databaseAdapter Database adapter
*/
constructor(databaseAdapter: DatabaseAdapter);
/**
* Execute workflows for an entity event
* @param entityConfig Entity configuration
* @param event Trigger event
* @param oldData Old data (for update/delete)
* @param newData New data (for create/update)
* @param context User context
*/
executeWorkflows(entityConfig: EntityConfiguration, event: string, oldData: Record<string, any> | null, newData: Record<string, any> | null, context: Context): Promise<void>;
/**
* Execute a single workflow
* @param workflow Workflow definition
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @private
*/
private executeWorkflow;
/**
* Evaluate workflow conditions
* @param conditions Workflow conditions
* @param oldData Old data
* @param newData New data
* @param context User context
* @returns True if conditions are met
* @private
*/
private evaluateWorkflowConditions;
/**
* Execute workflow actions
* @param actions Array of workflow actions
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @private
*/
private executeWorkflowActions;
/**
* Execute a single workflow action
* @param action Workflow action
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @private
*/
private executeWorkflowAction;
/**
* Evaluate a single condition
* @param condition Single condition object
* @param oldData Old data
* @param newData New data
* @param context User context
* @returns True if condition is met
* @private
*/
private evaluateSingleCondition;
/**
* Execute log action
* @param action Action configuration
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @private
*/
private executeLogAction;
/**
* Execute email action
* @param action Action configuration
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @private
*/
private executeEmailAction;
/**
* Execute webhook action
* @param action Action configuration
* @param event Trigger event
* @param oldData Old data
* @param newData New data
* @param context User context
* @