DSL in TypeScript: Creating Your Own Programming Language in an Evening

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Your Project Deserves Its Own Language?

\n\n

Imagine: you are writing code for calculating insurance premiums, and instead of dozens of nested if-else statements and magic numbers, you see a concise construct:

\n\n
const rule = new InsuranceRule()\n  .when(age > 25)\n  .and(experience > 2)\n  .then(premium = base * 0.8);
\n\n

This is not magic. This is a DSL (Domain Specific Language) — a domain-specific language embedded in TypeScript. In this article, we will explore how to create such languages so that your code speaks the language of business, not the language of machines.

\n\n

What is a DSL and Why Do You Need It?

\n\n

A DSL is a language created to solve problems of one specific domain. Unlike general-purpose languages (Java, C#, TypeScript), a DSL does not try to be universal. It sacrifices flexibility for expressiveness.

\n\n

When to use a DSL:

\n
    \n
  • Business logic changes frequently and needs to be editable by analysts
  • \n
  • The project has many repetitive configuration patterns
  • \n
  • You need to isolate complex algorithms from the rest of the code
  • \n
  • Data validation at the grammar level is required
  • \n
\n\n

TypeScript is an ideal choice for creating DSLs. Static typing, a powerful type system, and the ability to compile to JavaScript make it a flexible tool for building both internal and external languages.

\n\n

Internal DSLs: Fluent API and Method Chaining

\n\n

The simplest way to create a DSL in TypeScript is to use a Fluent Interface. This is when each method returns the object itself, allowing you to build method chains.

\n\n

Example: SQL Query Builder

\n\n
class QueryBuilder {\n  private table: string = '';\n  private conditions: string[] = [];\n  private selectedFields: string[] = ['*'];\n\n  select(...fields: string[]): this {\n    this.selectedFields = fields.length ? fields : ['*'];\n    return this;\n  }\n\n  from(table: string): this {\n    this.table = table;\n    return this;\n  }\n\n  where(condition: string): this {\n    this.conditions.push(condition);\n    return this;\n  }\n\n  and(condition: string): this {\n    return this.where(condition);\n  }\n\n  build(): string {\n    let sql = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`;\n    if (this.conditions.length) {\n      sql += ` WHERE ${this.conditions.join(' AND ')}`;\n    }\n    return sql;\n  }\n}\n\n// Usage\nconst query = new QueryBuilder()\n  .select('name', 'age')\n  .from('users')\n  .where('age > 18')\n  .and('status = "active"')\n  .build();\n\nconsole.log(query);\n// SELECT name, age FROM users WHERE age > 18 AND status = "active"
\n\n

This is a classic internal DSL in TypeScript. It does not require a separate parser and works within TypeScript code. The main advantage is full support for autocompletion and typing.

\n\n

Leveling Up: A Typed DSL for Business Rules

\n\n

Let's add strict typing using generics and union types:

\n\n
type ComparisonOperator = '>' | '<' | '=' | '!=';\n\nclass RuleBuilder<T extends Record<string, any>> {\n  private conditions: ((data: T) => boolean)[] = [];\n\n  when<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    this.conditions.push((data: T) => {\n      switch (op) {\n        case '>': return data[field] > value;\n        case '<': return data[field] < value;\n        case '=': return data[field] === value;\n        case '!=': return data[field] !== value;\n      }\n    });\n    return this;\n  }\n\n  and<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    return this.when(field, op, value);\n  }\n\n  evaluate(data: T): boolean {\n    return this.conditions.every(c => c(data));\n  }\n}\n\n// Data type for validation\ninterface UserData                

Blogs

Book Recommendations