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

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 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/.


# 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
  • Lesson Skeleton – Hierarchical structure of Stage → Scene → SlideContent defined in src/stage.ts
  • Playback Actions – Union type system for interactions (spotlight, laser, speech) in src/action.ts
  • Validators – Structural integrity checks (validateStage, validateScene) in src/validate.ts
  • Normalizers – Default value injection and geometry derivation in src/normalize.ts
  • Version & Migration – Contract versioning (DSL_VERSION) and upgrade utilities in src/version.ts
  • Asset Manifest – Media reference enumeration via enumerateAssetManifest in src/asset-manifest.ts

All public APIs are re-exported through the main entry point at 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:

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:

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:

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:

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:

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:

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 Public API entry point; re-exports all modules
src/slides.ts Type definitions for Slide, PPTElement, and element variants
src/stage.ts Types for Stage, Scene<TAction, TContent>, SlideContent, QuizContent
src/action.ts Union type Action and helper constants (ACTION_TYPES)
src/guards.ts Type guards (isTextElement, PPT_ELEMENT_TYPES)
src/validate.ts Pure validators: validateStage, validateScene, validateAction
src/normalize.ts Normalizers: normalizeSlide, normalizeScene, ELEMENT_DEFAULTS
src/version.ts DSL_VERSION constant and migration ladder (DSL_MIGRATIONS)
src/asset-manifest.ts enumerateAssetManifest and manifest types
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. This utility inspects the document's dslVersion field and returns a boolean indicating whether the migrate() function must run.

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.

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →