# How to Use the @openmaic/dsl Package in OpenMAIC: Complete Developer's Guide

> Master the @openmaic/dsl package with this developer's guide. Learn to validate normalize and migrate MAIC lesson documents using TypeScript utilities for seamless project integration.

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

---

**The @openmaic/dsl package provides pure, zero-dependency TypeScript utilities for validating, normalizing, and migrating MAIC lesson documents through functions like `validateStage`, `normalizeSlide`, and `enumerateAssetManifest`.**

The `@openmaic/dsl` package defines the core data contract for the [THU-MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC) ecosystem, offering type definitions and pure functions to handle slides, scenes, and playback actions without runtime dependencies. This library enables developers to process lesson skeletons, validate document structure, and enumerate media assets in both Node.js and browser environments.

## Installing the @openmaic/dsl Package

Install the package using your preferred package manager. The library ships as ESM with TypeScript definitions and generated JSON schemas under `dist/schema/`.

```bash

# Using pnpm (repository preferred)

pnpm add @openmaic/dsl

# Using npm

npm install @openmaic/dsl

# Using yarn

yarn add @openmaic/dsl

```

The package contains no runtime dependencies, making it safe to import without pulling in React, PPTX processing libraries, or other heavy frameworks.

## Core Concepts and Architecture

The package organizes functionality around seven primary domains:

- **Slides & Elements** – Object model for slide content (text, images, shapes, tables) defined in [`src/slides.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/slides.ts)
- **Lesson Skeleton** – Hierarchical structure of `Stage` → `Scene` → `SlideContent` defined in [`src/stage.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/stage.ts)
- **Playback Actions** – Union type system for interactions (spotlight, laser, speech) in [`src/action.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/action.ts)
- **Validators** – Structural integrity checks (`validateStage`, `validateScene`) in [`src/validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/validate.ts)
- **Normalizers** – Default value injection and geometry derivation in [`src/normalize.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/normalize.ts)
- **Version & Migration** – Contract versioning (`DSL_VERSION`) and upgrade utilities in [`src/version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/version.ts)
- **Asset Manifest** – Media reference enumeration via `enumerateAssetManifest` in [`src/asset-manifest.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/asset-manifest.ts)

All public APIs are re-exported through the main entry point at [`src/index.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/index.ts).

## Standard Workflow for Processing Documents

When working with MAIC documents, follow this six-step pipeline:

1. **Parse** the incoming JSON document from file, database, or API
2. **Migrate** the document to the current `DSL_VERSION` if it uses an older schema
3. **Normalize** the structure to fill required defaults and derive calculated geometry
4. **Validate** the normalized document to catch structural errors
5. **Consume** the data for rendering, exporting, or execution
6. **Enumerate assets** to identify all media files requiring upload or caching

Each step uses pure functions with no side effects, making them safe for server-side rendering and client-side processing.

## Practical Code Examples

### Importing Types and Utilities

Import type definitions and helper functions from the package entry point:

```typescript
import type {
  Slide,
  PPTElement,
  Action,
  Stage,
  Scene,
} from '@openmaic/dsl';
import {
  validateStage,
  validateScene,
  validateAction,
  normalizeSlide,
  normalizeScene,
  DSL_VERSION,
  migrate,
  enumerateAssetManifest,
} from '@openmaic/dsl';

```

### Migrating Legacy Documents

The `migrate` function brings older documents up to the current contract version:

```typescript
import { migrate, DSL_VERSION, needsMigration } from '@openmaic/dsl';

// Check if migration is needed
if (needsMigration(rawDoc)) {
  const migratedDoc = migrate(rawDoc);
  console.log(`Migrated to DSL version ${DSL_VERSION}`);
}

```

The `migrate` function walks the `DSL_MIGRATIONS` ladder and is idempotent—running it on a current document returns the same object unchanged.

### Normalizing Slides and Scenes

Use normalizers to fill missing required fields and derive geometry:

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

// Fill defaults for a single slide (positions, dimensions, etc.)
const completeSlide = normalizeSlide(partialSlide);

// Normalize an entire scene hierarchy
const completeScene = normalizeScene(partialScene);

// Normalize a full stage document
const completeStage = normalizeStage(partialStage);

```

Normalization applies `ELEMENT_DEFAULTS` and calculates derived values like line endpoints.

### Validating Document Structure

Validate documents before processing to catch structural errors:

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

const result = validateStage(stageDocument);

if (!result.valid) {
  console.error('Validation errors:');
  result.errors.forEach(err => {
    console.error(`  ${err.path}: ${err.message}`);
  });
}

```

The `ValidationResult` type returns `{ valid: true }` on success or `{ valid: false; errors: Array<{path: string, message: string}> }` on failure.

### Enumerating Media Assets

Discover all media references in a stage for upload or caching:

```typescript
import { enumerateAssetManifest } from '@openmaic/dsl';

const manifest = enumerateAssetManifest(stageDoc, {
  metadata(ref, kind) {
    // Optional: Enrich with file metadata (size, mime type) — pure function
    return { size: 0, mimeType: 'image/png' };
  },
});

console.log(`Found ${manifest.entries.length} assets`);
console.log('Reference counts:', manifest.referenceCounts);

```

The manifest contains unique entries for each `(ref, kind)` pair and tracks ownership counts across the document.

### Complete Pipeline Example

Combine all operations in a production workflow:

```typescript
import { readFileSync } from 'fs';
import {
  migrate,
  normalizeStage,
  validateStage,
  enumerateAssetManifest,
} from '@openmaic/dsl';

// 1. Load document
const raw = JSON.parse(readFileSync('lesson.json', 'utf8'));

// 2. Migrate to current version
const migrated = migrate(raw);

// 3. Normalize to fill defaults
const normalized = normalizeStage(migrated);

// 4. Validate structure
const validation = validateStage(normalized);
if (!validation.valid) {
  throw new Error(
    'Invalid stage: ' + 
    validation.errors.map(e => `${e.path}: ${e.message}`).join('; ')
  );
}

// 5. Enumerate assets
const manifest = enumerateAssetManifest(normalized);
manifest.entries.forEach(e => {
  console.log(`Asset: ${e.ref} (${e.kind})`);
});

```

## Key Source Files and Implementation Details

Understanding the internal structure helps when debugging or extending the package:

| File | Responsibility |
|------|----------------|
| [`src/index.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/index.ts) | Public API entry point; re-exports all modules |
| [`src/slides.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/slides.ts) | Type definitions for `Slide`, `PPTElement`, and element variants |
| [`src/stage.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/stage.ts) | Types for `Stage`, `Scene<TAction, TContent>`, `SlideContent`, `QuizContent` |
| [`src/action.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/action.ts) | Union type `Action` and helper constants (`ACTION_TYPES`) |
| [`src/guards.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/guards.ts) | Type guards (`isTextElement`, `PPT_ELEMENT_TYPES`) |
| [`src/validate.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/validate.ts) | Pure validators: `validateStage`, `validateScene`, `validateAction` |
| [`src/normalize.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/normalize.ts) | Normalizers: `normalizeSlide`, `normalizeScene`, `ELEMENT_DEFAULTS` |
| [`src/version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/version.ts) | `DSL_VERSION` constant and migration ladder (`DSL_MIGRATIONS`) |
| [`src/asset-manifest.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/asset-manifest.ts) | `enumerateAssetManifest` and manifest types |
| [`src/runtime.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/runtime.ts) | Runtime envelope types for learner session data |

All validators and normalizers reside in separate files to maintain tree-shakeability.

## Summary

- **@openmaic/dsl** is a zero-dependency contract package defining the MAIC SDK data model
- Use **`migrate()`** and **`DSL_VERSION`** to handle schema evolution safely
- **`validateStage()`** and **`validateScene()`** provide structural integrity checks with detailed error paths
- **`normalizeStage()`** and **`normalizeSlide()`** fill required defaults without mutating inputs
- **`enumerateAssetManifest()`** extracts all media references for asset management pipelines
- All functions are pure and isomorphic, running identically in Node.js and browsers

## Frequently Asked Questions

### How do I check if a document needs migration before processing?

Use the **`needsMigration()`** function from [`src/version.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/src/version.ts). This utility inspects the document's `dslVersion` field and returns a boolean indicating whether the `migrate()` function must run.

```typescript
import { needsMigration, migrate } from '@openmaic/dsl';

const doc = needsMigration(rawDoc) ? migrate(rawDoc) : rawDoc;

```

### What is the difference between validation and normalization in @openmaic/dsl?

**Validation** checks structural integrity and returns errors without modifying the document, while **normalization** returns a new object with filled defaults and derived geometry. Always normalize before validating to ensure required fields exist.

### Can I use @openmaic/dsl in a browser environment without bundling issues?

Yes. The package has **zero runtime dependencies** and ships as ESM. It does not depend on Node.js-specific APIs, React, or heavy processing libraries, making it safe for client-side validation and normalization in browsers.

### How do I enumerate all image assets in a stage for uploading to a CDN?

Call **`enumerateAssetManifest()`** with the stage document. It returns distinct entries for every media reference (images, audio, video) along with reference counts showing how many elements use each asset.

```typescript
const manifest = enumerateAssetManifest(stage);
const images = manifest.entries.filter(e => e.kind === 'image');

```