Attachments

Attachments associate documents (data) with keys (concept instances). They are the primary mechanism for storing information about your domain entities.

Basic Attachments

An attachment declares that a concept can have associated data:

concept User;

struct Login {
    string nickname;
    string password;
};

attachment<User, Login> login;

This creates a mapping: each User key can have a Login document attached.

Single-Attachment Pattern

When translating an existing C++ codebase to DSM, the natural result is a single attachment per concept — mirroring the original C++ structures:

// From Raptor Editor: Single attachment per concept (translated from C++)
concept Material;

struct MaterialStandardProperties {
    string name = "MaterialStandard";
    optional<key<Thumbnail>> thumbnailKey;
    MaterialStandardType materialType = .diffuseSpecular;
    Vector diffuseColor;
    Vector ambientColor;
    Vector illuminationColor;
    float diffuseIntensity;
    float illuminationIntensity;
    optional<key<Texture>> diffuseMapKey;
    bool diffuseMapEnabled;
    // ... 50+ more fields ...
};

attachment<MaterialStandard, MaterialStandardProperties> properties;

Problems with Monolithic Structures

Problem

Impact

All-or-nothing

Must load entire structure even for one field

Migration pain

Adding a field requires data migration

Field bloat

Structures grow unwieldy over time

Tight coupling

Changes affect all code using the structure

Schema Evolution: Adding Features

With Single Attachment

// Original
struct UserProperties {
    string username;
    string password_hash;
};

// V2: Must migrate ALL existing data to add email
struct UserProperties {
    string username;
    string password_hash;
    string email;           // New field - requires migration
    optional<blob_id> avatar;  // New field - requires migration
};

Attachment Types

Attachments can use any DSM type as the document:

// Structure
attachment<Graph, GraphTopology> topology;

// Map
attachment<Graph, map<string, string>> tags;

// Collection
attachment<Graph, xarray<string>> comments;

// Simple type
attachment<User, blob_id> avatar;

// Any type (dynamic)
attachment<User, any> metadata;

Optional vs Required

Attachments are always optional at the storage level. Your code decides whether an attachment is logically required:

# Read an attachment
topology = commit.get(a_topology, graph_key)

if topology.is_nil():
    # Handle missing attachment
    topology = GraphTopology()
    mutating.set(a_topology, graph_key, topology)

Real-World Comparison

Raptor Editor (Single-Attachment)

// Surface MUST have all properties defined
// No way to have a "lightweight" surface

struct SurfaceProperties {
    string name = "Surface";
    map<key<LightingLayer>, key<Texture>> lightmaps;
    key<Mesh> meshKey;
    Vector color = {1.0, 1.0, 1.0};
    set<string> tags;
    SurfaceBillboardMode billboardMode;
    optional<key<MeshAnimation>> meshAnimationKey;
    SurfaceMirrorPlane mirrorPlane;
};

attachment<Surface, SurfaceProperties> properties;

Design Guidelines

Do

  • Separate concerns: One attachment per logical domain

  • Use multiple attachments: They’re cheap and enable evolution

  • Name attachments clearly: topology, selection, visual not data, props

  • Keep structures focused: 5-10 fields per structure is ideal

Don’t

  • Create monolithic structures: Avoid 50+ field Properties structures

  • Mix concerns: Don’t put UI state in data structures

  • Use single attachment: Unless you have a specific reason

Migration Path

If you inherit a single-attachment design:

  1. Keep the existing properties attachment

  2. Add new features as new attachments

  3. Gradually migrate fields to new attachments

  4. Deprecate unused fields (don’t delete - maintains compatibility)

// Existing single-attachment
attachment<Vertex, VertexProperties> properties;

// New features - separate attachments
attachment<Vertex, VertexPhysics> physics;      // New feature
attachment<Vertex, VertexAnnotation> annotation; // New feature

What’s Next