SkillAgentSearch skills...

Thoth

A domain-specific language that generates TypeScript backend + React frontend from a single spec

Install / Use

/learn @abdufelsayed/Thoth

README

Thoth

<p align="center"> <strong>A Multitier Domain-Specific Language for Full-Stack Web Development</strong> </p>

⚠️ DISCLAIMER

This is a proof-of-concept academic project created as part of a master's dissertation. It is not intended for production use and is currently unmaintained. Use at your own risk.

Table of Contents

Overview

Thoth is a statically-typed domain-specific language (DSL) that enables developers to build complete web applications—database schema, server API, and client UI—in a single, unified language. Write once in Thoth, and the compiler generates clean, human-readable TypeScript code for both your Node.js backend and React frontend.

The Problem

Traditional full-stack web development requires:

  • Multiple programming languages (SQL, TypeScript/JavaScript, potentially others)
  • Separate codebases for database migrations, backend APIs, and frontend UIs
  • Manual synchronization of data models across tiers
  • Extensive boilerplate code for CRUD operations, authentication, and real-time features
  • Context switching between different paradigms and tools

The Solution

Thoth unifies the entire stack into a single declarative language where you define:

  • Data models (compiled to PostgreSQL schema via Prisma)
  • Queries (compiled to Express.js API routes with type-safe validation)
  • Components (compiled to React components with TypeScript)
  • Pages (compiled to React pages with routing)
  • Authentication & Authorization (automatically generated and integrated)

What Makes Thoth Different

1. Truly Multitier

Unlike frameworks that simply share code between tiers, Thoth lets you declare your application's behavior at a higher level of abstraction. The compiler understands the relationships between database models, API endpoints, and UI components, generating all the glue code automatically.

2. Human-Readable Output

Thoth compiles to clean, idiomatic TypeScript code that you can read, understand, modify, and extend. No black-box magic—just well-structured React components, Express routes, and Prisma schemas.

3. Real-Time by Default

Every query automatically supports real-time updates via Server-Sent Events (SSE). Your UI components subscribe to data changes and update automatically—no WebSocket configuration needed.

4. Framework-Agnostic Design

While the default compiler targets Node.js + Express + Prisma + PostgreSQL on the backend and React + Vite on the frontend, the language itself is not tied to these technologies. The architecture supports alternative compilation targets.

5. Type Safety Across Tiers

Changes to your data models automatically propagate through your API contracts and UI components, maintaining type safety across the entire stack.

Key Features

Declarative Data Modeling - Define models with fields, relationships, and attributes
Automatic CRUD Generation - Create, Read, Update, Delete operations with minimal code
Built-in Authentication - User signup, login, logout with session management
Fine-Grained Authorization - Route-level and query-level permissions
Real-Time Synchronization - SSE-based live updates across all connected clients
Component System - Declare forms, buttons, and custom components with styling
JSX-like Syntax - Familiar component rendering with template expressions
TypeScript Integration - Leverage the entire npm ecosystem
Custom Components - Write raw React/TypeScript when needed
Client Dependencies - Specify npm packages to include in your project

Quick Example

Here's a simple todo application in Thoth:

app Todo {
  title: "My Todo App",
  auth: {
    userModel: User,
    idField: id,
    usernameField: username,
    passwordField: password,
    onSuccessRedirectTo: "/",
    onFailRedirectTo: "/login"
  }
}

model User {
  id       Int      @id
  username String   @unique
  password String
  tasks    Task[]
}

model Task {
  id     Int     @id
  title  String
  isDone Boolean @default(false)
  user   User    @relation(userId, id)
  userId Int
}

@model(Task)
@permissions(IsAuth, OwnsRecord)
query<FindMany> getTasks {
  search: [title, isDone]
}

@model(Task)
@permissions(IsAuth)
query<Create> createTask {
  data: {
    fields: [title],
    relationFields: {
      user: connect id with userId
    }
  }
}

component<Create> TaskForm {
  actionQuery: createTask(),
  formInputs: {
    title: {
      input: {
        type: TextInput,
        placeholder: "New task...",
        isVisible: true
      }
    },
    user: {
      input: {
        type: RelationInput,
        isVisible: false,
        defaultValue: connect id with LoggedInUser.id
      }
    }
  },
  formButton: {
    name: "Add Task",
    style: "bg-blue-500 text-white px-4 py-2 rounded"
  }
}

@route("/")
@permissions(IsAuth)
page Home {
  render(
    <div>
      <h1>{"My Tasks"}</h1>
      <TaskForm />
      <TasksComponent />
    </div>
  )
}

This compiles to a complete full-stack application with PostgreSQL database, Express REST API, and React frontend—all with real-time synchronization.

How It Works

┌─────────────────┐
│  .thoth file    │  ← Your application code
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Thoth Compiler │  ← OCaml-based compiler (lexer, parser, type checker, code generator)
└────────┬────────┘
         │
         ├──────────────────┬──────────────────┐
         ▼                  ▼                  ▼
┌────────────────┐  ┌──────────────┐  ┌─────────────────┐
│  TypeScript    │  │  TypeScript  │  │  Prisma Schema  │
│  React Client  │  │  Express API │  │  + Migrations   │
│  (Vite)        │  │  (Node.js)   │  │  (PostgreSQL)   │
└────────────────┘  └──────────────┘  └─────────────────┘

Compilation Pipeline:

  1. Lexical Analysis - Tokenizes the .thoth source file
  2. Parsing - Builds an Abstract Syntax Tree (AST)
  3. Type Checking - Validates models, queries, components, and pages
  4. Specification Generation - Creates intermediate representations for each tier
  5. Code Generation - Uses Jinja2 templates to generate TypeScript and Prisma code
  6. Output - Produces two directories: server/ and client/ with complete applications

Language Features

Models

Define your data schema with Prisma-inspired syntax:

model User {
  id         Int       @id
  email      String    @unique
  username   String
  posts      Post[]
  createdAt  DateTime  @default(Now)
}

model Post {
  id        Int      @id
  title     String
  content   String
  author    User     @relation(authorId, id)
  authorId  Int
  published Boolean  @default(false)
}

Queries

Declare type-safe database operations:

@model(Post)
@permissions(IsAuth)
query<FindMany> getPosts {
  search: [title, content, published]
}

@model(Post)
@permissions(IsAuth, OwnsRecord)
query<Update> updatePost {
  where: id,
  data: {
    fields: [title, content, published]
  }
}

@model(Post)
@permissions(IsAuth, OwnsRecord)
query<Delete> deletePost {
  where: id
}

Supported query types: FindUnique, FindMany, Create, Update, Delete

Components

Build UI components declaratively:

Action Forms:

component<Create> PostForm {
  actionQuery: createPost(),
  formInputs: {
    title: {
      label: { name: "Title" },
      input: {
        type: TextInput,
        placeholder: "Enter title"
      }
    },
    content: {
      label: { name: "Content" },
      input: {
        type: TextInput,
        placeholder: "Write your post..."
      }
    }
  },
  formButton: {
    name: "Publish",
    style: "bg-green-500 text-white px-4 py-2"
  }
}

Data Display Components:

component<FindMany> PostsList {
  findQuery: getPosts() as posts,
  onLoading: render(<div>{"Loading..."}</div>),
  onError: render(<div>{"Error loading posts"}</div>),
  onSuccess: render(
    <>
      [% for post in posts %]
        <PostCard post={post} />
      [% endfor %]
    </>
  )
}

Custom Components:

component<Custom> PostCard(post: Post) {
  imports: [|
    import { useState } from "react";
    import { Post } from "@/types";
  |],
  fn: [|
    const [expanded, setExpanded] = useState(false);
    
    return (
      <div className="border rounded p-4">
        <h3>{post.title}</h3>
        <button onClick={() => setExpanded(!expanded)}>
          {expanded ? "Show less" : "Show more"}
        </button>
        {expanded && <p>{post.content}</p>}
      </div>
    );
  |]
}

Pages

Define routes and page components:

@route("/")
@permissions(IsAuth)
page Home {
  render(
    <div>
      <h1>{"Home Page"}</h1>
      <PostsList />
    </div>
  )
}

@route("/login")
page Login {
  render(
    <div>
      <h1>{"Login"}</h1>
      <LoginForm />
    </div>
  )
}

Authentication

Built-in authentication configuration:

app MyApp {
  title: "My Application",
  auth: {
    userModel: User,
    idField: id,
    isOnlineField: isOnline,      // optional
    lastActiveField: lastActive,  // optional
    usernameField: username,
    passwordField: password,
    onSuccessRedirectTo: "/",
    onFailRedirectTo: "/login"
  }
}

Special components: SignupForm, LoginForm, LogoutButton

Permissions

Control access at the route and query level:

  • IsAuth - User must be authenticated
  • OwnsRecord - User must own the record being accessed (checks foreign key relationships)

View on GitHub
GitHub Stars14
CategoryDevelopment
Updated1mo ago
Forks0

Languages

OCaml

Security Score

95/100

Audited on Feb 17, 2026

No findings