# How to Use the OpenMAIC DSL for Defining Contracts

> Learn to define contracts with the OpenMAIC DSL. This zero-dependency TypeScript system ensures type-safe definition, migration, normalization, and validation for your OpenMAIC ecosystem documents.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: how-to-guide
- Published: 2026-08-30

---

**The OpenMAIC DSL is a zero-dependency TypeScript contract system that enables type-safe definition, migration, normalization, and validation of course documents across the OpenMAIC ecosystem.**

The OpenMAIC Domain-Specific Language (DSL) serves as the immutable architectural backbone for the entire SDK family. Located in the `@openmaic/dsl` package, this contract-first layer defines the shape of **Stage** documents and their nested entities—Slides, Actions, and Scenes—ensuring structural consistency across renderers, importers, and storage backends without imposing runtime dependencies on consuming packages.

## Architecture of the OpenMAIC DSL Contract

The DSL implements a layered architecture that separates type definitions from operational logic, ensuring that contracts remain pure and tree-shakeable.

### Object Model Layer

The foundational types reside in dedicated source files that declare the immutable structure of course content. The root [`Stage`](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/stage.ts) type defines the document envelope, while [[`slides.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/slides.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/slides.ts) and [[`action.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/action.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/action.ts) provide discriminated unions for content elements and playback verbs. These interfaces enforce compile-time safety through TypeScript’s strict type system.

### Validation and Normalization Layer

Pure functions in [[`validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/validate.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/validate.ts) implement structural verification that walks the object graph without short-circuiting, collecting all constraint violations into a single `ValidationResult`. Complementing this, [[`normalize.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/normalize.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/normalize.ts) exports repair-oriented helpers that fill missing defaults and derive computed geometry while preserving object immutability.

### Version Migration Layer

The [[`version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/version.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/version.ts) module exports `DSL_VERSION`—a semantic constant representing the current contract revision—and `DSL_MIGRATIONS`, an ordered ladder of transformation functions. The `migrate()` utility inspects a document’s `dslVersion` envelope and idempotently upgrades legacy schemas to the current specification, leaving forward-compatible documents untouched.

## Runtime Contract Workflow

Processing a course document through the OpenMAIC DSL follows a strict four-phase pipeline that guarantees canonical output.

1. **Ingest**: Accept raw JSON payloads from importers, LLM generators, or persisted storage.
2. **Migrate**: Invoke `migrate(doc)` to traverse `DSL_MIGRATIONS` and stamp the document with the latest `DSL_VERSION`.
3. **Normalize**: Execute `normalizeStage()` to inject default values (such as slide backgrounds) and derive layout geometry.
4. **Validate**: Optionally run `validateStage()` to perform a full structural check, returning aggregated errors for every contract violation.

Because the DSL carries **zero runtime dependencies**, this pipeline executes efficiently in both Node.js and browser environments without bloating bundle sizes.

## Practical Implementation Examples

### Defining a Type-Safe Stage

The `Stage` type guarantees that `dslVersion`, `title`, and `scenes` exist and that each scene’s `type` discriminator matches its `content` variant.

```typescript
import type { Stage } from '@openmaic/dsl';
import { DSL_VERSION } from '@openmaic/dsl';

const myStage: Stage = {
  dslVersion: DSL_VERSION,
  title: 'Intro to Quantum Mechanics',
  scenes: [
    {
      id: 'scene-1',
      type: 'slide',
      content: {
        type: 'slide',
        slide: {
          elements: [],
          title: 'What is Quantum?',
        },
      },
    },
  ],
};

```

### Migrating Legacy Documents

When accepting documents from external systems or older SDK versions, use the migration utility to ensure schema conformity before processing.

```typescript
import { migrate } from '@openmaic/dsl';
import type { Stage } from '@openmaic/dsl';

// oldDoc may lack dslVersion or use deprecated shape
const migrated: Stage = migrate(oldDoc);

```

The `migrate` function walks the `DSL_MIGRATIONS` array defined in [[`version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/version.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/version.ts) and returns a document stamped with the current `DSL_VERSION`.

### Normalizing and Validating Documents

Normalization prepares a document for rendering by filling optional fields with defaults, while validation confirms structural integrity.

```typescript
import { normalizeStage, validateStage } from '@openmaic/dsl';

const canonical = normalizeStage(migrated);
const result = validateStage(canonical);

if (!result.valid) {
  console.error('Stage contract violations:', result.errors);
}

```

The `normalizeStage` function handles default injection and geometry derivation, whereas `validateStage` confirms every required discriminant and field shape according to the definitions in [[`validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/validate.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/validate.ts).

### Consuming JSON-Schema in Other Languages

The package generates JSON-Schema artifacts at build time, enabling contract validation in Python, Java, or Go environments.

```typescript
import stageSchema from '@openmaic/dsl/schema/stage.schema.json' with { type: 'json' };
import Ajv from 'ajv';

const ajv = new Ajv();
const validate = ajv.compile(stageSchema);
const ok = validate(someJson);

if (!ok) console.log(validate.errors);

```

This schema mirrors the TypeScript types exactly, providing language-agnostic validation without requiring a TypeScript runtime.

## Core DSL Source Files

Understanding the repository layout helps navigate the contract implementation:

- **[[`src/version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/version.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/version.ts)**: Exports `DSL_VERSION`, `DSL_MIGRATIONS`, and the `migrate` helper for schema evolution.
- **[[`src/stage.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/stage.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/stage.ts)**: Defines the top-level `Stage` interface, `StageMode` enum, and `Scene<TAction, TContent>` generic container.
- **[[`src/slides.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/slides.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/slides.ts)**: Describes slide objects, element variants, and `ELEMENT_DEFAULTS` constants.
- **[[`src/action.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/action.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/action.ts)**: Enumerates playback verbs including `spotlight`, `laser`, and `widget_*` categories.
- **[[`src/validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/validate.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/validate.ts)**: Implements pure validators such as `validateStage`, `validateScene`, and `validateAction`.
- **[[`src/normalize.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/normalize.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/normalize.ts)**: Provides default-filling and geometry-deriving normalizers like `normalizeSlide` and `normalizeElement`.

## Summary

- The OpenMAIC DSL is a **zero-dependency TypeScript package** that serves as the single source of truth for course document contracts.
- Contract enforcement relies on **pure functions** for migration, normalization, and validation rather than class-based state management.
- The **migration ladder** in [`version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/version.ts) ensures forward compatibility with legacy documents through idempotent transformations.
- **JSON-Schema generation** enables cross-language validation while maintaining type parity with TypeScript definitions.
- All core logic resides in `packages/@openmaic/dsl/src/`, with distinct modules for object models, validation, normalization, and versioning.

## Frequently Asked Questions

### What is the structure of an OpenMAIC DSL contract?

An OpenMAIC DSL contract centers on the **Stage** type, a root object containing `dslVersion`, metadata, and a `scenes` array. Each scene uses a discriminated union pattern where the `type` field determines the shape of the `content` property—such as `slide` content referencing the `Slide` interface or `action` content referencing `Action` variants defined in [[`action.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/action.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/action.ts).

### How does document migration work in OpenMAIC?

Document migration uses the `migrate()` function exported from [[`version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/version.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/version.ts). This function inspects the `dslVersion` field of an incoming document, iterates through the `DSL_MIGRATIONS` array to apply sequential transformations, and returns a document stamped with the current `DSL_VERSION`. The process is idempotent and forward-compatible, leaving newer documents untouched.

### What is the difference between normalizing and validating a Stage?

**Normalization** (via `normalizeStage` in [[`normalize.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/normalize.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/normalize.ts)) is a constructive process that fills missing fields with defaults and derives computed properties like geometry. **Validation** (via `validateStage` in [[`validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/validate.ts)](https://github.com/THU-MAIC/OpenMAIC/blob/main/packages/@openmaic/dsl/src/validate.ts)) is an analytic process that checks structural constraints and returns a `ValidationResult` listing all errors without modifying the input object.

### Can I validate OpenMAIC DSL contracts in Python or Java?

Yes. The build process generates JSON-Schema artifacts—such as [`stage.schema.json`](https://github.com/THU-MAIC/OpenMAIC/blob/main/stage.schema.json)—that exactly mirror the TypeScript types. These schemas can be consumed by validation libraries like Python’s `jsonschema` or Java’s `Everit JSON Schema` to enforce the same structural constraints without requiring TypeScript tooling.