Model & Schema

Deep dive into the relationship between Model and Schema, data transformation, view integration, collaborative editing, and comprehensive node type examples from basic to complex custom schemas.

Overview

The Model represents the actual document data, while the Schema defines what structures are valid. Understanding their relationship is crucial for building robust editors that can transform data, integrate with views, and support collaborative editing.

This guide covers the fundamental concepts and provides 50+ node type examples ranging from basic text nodes to complex custom schemas like cards, tables, and interactive components.

Model & Schema Relationship

The Schema is the contract that defines what your document can contain. It specifies:

  • What node types exist
  • What attributes each node can have
  • What content each node can contain
  • What marks can be applied
  • Validation rules and constraints

The Model is an instance of a document that conforms to the schema. It's the actual data structure representing the current state of the document.

// Schema defines the rules
const schema = {
  nodes: {
    paragraph: {
      content: 'inline*',
      group: 'block'
    },
    heading: {
      content: 'inline*',
      group: 'block',
      attrs: {
        level: { default: 1 }
      }
    }
  }
};

// Model is an instance conforming to the schema
const model = {
  type: 'document',
  children: [
    {
      type: 'heading',
      attrs: { level: 1 },
      children: [
        { type: 'text', text: 'Hello World' }
      ]
    },
    {
      type: 'paragraph',
      children: [
        { type: 'text', text: 'This is a paragraph.' }
      ]
    }
  ]
};

Data Transformation

Data transformation is the process of converting between different representations of your document:

  • Serialization: Model → JSON, HTML, Markdown, etc.
  • Deserialization: JSON, HTML, Markdown → Model
  • Migration: Updating model structure when schema changes
  • Normalization: Ensuring model conforms to schema
// Serialize model to JSON
function serializeToJSON(model) {
  return JSON.stringify(model, null, 2);
}

// Deserialize JSON to model
function deserializeFromJSON(json) {
  const data = JSON.parse(json);
  return normalizeModel(data); // Ensure it conforms to schema
}

// Normalize model to ensure schema compliance
function normalizeModel(model) {
  // Validate structure
  // Fix invalid nesting
  // Add missing required attributes
  // Remove invalid attributes
  return validatedModel;
}

// Migrate old model to new schema version
function migrateModel(oldModel, oldSchema, newSchema) {
  // Transform nodes that changed
  // Update attributes
  // Handle removed node types
  return migratedModel;
}

View Integration

The view layer renders the model and handles user interactions. Integration requires:

  • Rendering: Converting model to DOM
  • Input Handling: Converting DOM changes back to model updates
  • Selection Mapping: Converting between DOM selection and model positions
  • Change Detection: Detecting when model changes and updating view
// Render model to DOM
function renderModel(model, container) {
  container.innerHTML = '';
  model.children.forEach(child => {
    const element = renderNode(child);
    container.appendChild(element);
  });
}

function renderNode(node) {
  switch (node.type) {
    case 'paragraph':
      const p = document.createElement('p');
      node.children.forEach(child => {
        p.appendChild(renderNode(child));
      });
      return p;
    case 'text':
      const text = document.createTextNode(node.text);
      // Apply marks
      if (node.marks && node.marks.length > 0) {
        return wrapWithMarks(text, node.marks);
      }
      return text;
    // ... other node types
  }
}

// Handle DOM changes and update model
function handleInput(domElement, model) {
  // Convert DOM changes to model operations
  const operations = diffDOMToModel(domElement, model);
  operations.forEach(op => {
    applyOperation(model, op);
  });
  return model;
}

// Map DOM selection to model position
function getModelPosition(domSelection) {
  const range = domSelection.getRangeAt(0);
  const path = getPathFromDOMNode(range.startContainer);
  return {
    path: path,
    offset: range.startOffset
  };
}

Schema Migration

As your editor evolves, the schema may change. Migrating existing documents to new schemas requires careful planning and implementation.

Read detailed guide on Schema Migration →

Collaborative Editing

Collaborative editing requires transforming operations to handle concurrent edits. Two main approaches are Operational Transformation (OT) and CRDTs.

Read detailed guide on Collaborative Editing →

Indexing Strategies

Efficient indexing is crucial for performance with large documents. Multiple indexing strategies can be used together.

Read detailed guide on Indexing Strategies →

Advanced Validation

Complex validation algorithms ensure document integrity while maintaining performance.

Read detailed guide on Advanced Validation →

Transaction System

A transaction system ensures atomic operations and provides rollback capabilities.

Read detailed guide on Transaction System →

Node Types

Comprehensive guide to 52+ node types with detailed implementation examples, view integration notes, and common pitfalls. Each node type has its own detailed page covering schema, model representation, HTML mapping, view integration, and common issues.

Browse all node types →