Domain Specific Language (DSL) System
Last Updated: May 11, 2025
The DSL system is a core architectural component of the Typus Development Framework that serves as a single source of truth for application components. It defines entities, relationships, and metadata that can be used to generate backend and frontend artifacts automatically.
System Architecture
The DSL system spans across three main components:
Component Relationships
Shared → Backend:
- Models are imported by the backend
- Registry is used to access model definitions
- Types are used for validation
Shared → Frontend:
- Interfaces are imported by the frontend
- Types are used for type checking
- Constants are used for field references
Backend → Frontend:
- API endpoints are called by the frontend client
- Data is returned in a consistent format
- Operations are validated against model definitions
Core Concepts
Types
The DSL system is built on TypeScript types that define the structure of models, fields, and relationships:
export interface DslField {
name: string;
type: string;
required?: boolean;
unique?: boolean;
default?: any;
primaryKey?: boolean;
autoincrement?: boolean;
ui?: {
isSearchable?: boolean;
[key: string]: any;
};
[key: string]: any;
}
export interface DslRelation {
name: string;
type: 'hasMany' | 'belongsTo' | 'hasOne' | 'manyToMany';
target: string;
foreignKey?: string;
inverseSide?: string;
[key: string]: any;
}
export interface DslModel {
name: string;
module?: string;
tableName?: string;
fields: DslField[];
relations?: DslRelation[];
[key: string]: any;
}
Models
Models are defined using the DSL types and represent the entities in the application. For example:
export const UserModel: DslModel = {
name: 'User',
module: 'auth',
tableName: 'users',
fields: [
{
name: 'id',
type: 'number',
required: true,
unique: true
},
{
name: 'email',
type: 'string',
required: true,
unique: true,
validation: [
{ type: 'required' },
{ type: 'email' }
],
ui: {
label: 'Email',
component: 'dxInput'
}
},
],
relations: [
{
name: 'roles',
type: 'manyToMany',
target: 'Role',
inverseSide: 'users'
}
]
};
Models can be organized by modules (e.g., 'auth', 'notifications') to match the application's structure.
Registry
The registry is a central access point for all model definitions:
import { registry } from './registry';
import { UserModel, RoleModel } from './models';
registry.registerMany([UserModel, RoleModel]);
const userModel = registry.getModel('User');
const allModels = registry.getAllModels();
Code Generation
The DSL system can generate various artifacts from model definitions:
1. Prisma Schema Generation
The system generates Prisma schema files from DSL model definitions:
import { prismaGenerator } from './generators';
import { registerAllModels } from './register-models';
// Register models
registerAllModels();
// Generate Prisma schema
const schema = prismaGenerator.generate();
2. TypeScript Interface Generation
The system generates TypeScript interfaces from DSL model definitions:
import { interfaceGenerator } from './generators';
import { registerAllModels } from './register-models';
// Register models
registerAllModels();
// Generate TypeScript interfaces
const interfaces = interfaceGenerator.generate();
Database Migration Process
The DSL system includes a robust database migration process:
- Define/Update Models: Create or modify DSL models in
shared/dsl/models/
- Generate Prisma Schema: Run generator to create Prisma schema from DSL models
- Check for Changes: Compare DSL models with Prisma schema
- Prepare Migration: Create migration files for schema changes
- Apply Migration: Execute migration against the database
Auto-Generated vs Manual Models
The system supports both auto-generated and manually created models:
- Auto-generated models: Created from DSL definitions, saved with
auto.
prefix - Manual models: Created or modified by hand
When generating the final schema:
- Manual models take precedence over auto-generated ones
- Both are combined into a single schema file
- Source information is preserved in comments
Module-Based Organization
Models are organized by modules, which:
- Group related models together
- Determine table naming with module prefix (e.g.,
auth.users
) - Match the application's logical structure
Benefits
- Single Source of Truth: All definitions in one place
- Type Safety: TypeScript provides compile-time checks
- Reduced Duplication: Generate code instead of writing it
- Consistency: Frontend and backend share the same definitions
- Centralized Access: Registry provides a single point of access to models
- Dependency Management: Registry can handle relationships between models
- Flexibility: Support for both auto-generated and manual models
- Modularity: Models organized by application modules
- Environment-Aware: Configuration via environment variables