JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 34
  • Score
    100M100P100Q69894F
  • License MIT

AI-powered database interface primitives with mdxld conventions

Package Exports

  • ai-database
  • ai-database/tests

Readme

ai-database

AI-generated data shouldn't be disconnected from your schema.

You write one line of AI code. It generates a user. Where does it go? Does it have a company? Does it match your existing customers? Traditional databases don't know. They can't reason about relationships. They can't cascade.

ai-database can.

import { DB } from 'ai-database'

const { db } = DB({
  Lead: { name: 'string', company: 'Company.leads', score: 'number' },
  Company: { name: 'string', industry: 'string' }
})

// One call generates Lead + Company + relationships
const lead = await db.Lead.create({ name: 'Acme Corp' }, { cascade: true })
const company = await lead.company  // Already exists, fully typed

The Problem

Before ai-database: AI generates orphaned data

// Generate a lead...
const lead = await ai`generate a sales lead`

// Now what?
// - Where do you store it?
// - How do you link it to a company?
// - What if the company already exists?
// - How do you query related data?

// You end up with:
const leadId = await db.insert('leads', lead)
const companyId = await db.insert('companies', { name: lead.company })
await db.insert('lead_companies', { leadId, companyId })
// Manual. Fragile. No type safety. N+1 queries everywhere.

After ai-database: AI respects your schema

const { db } = DB({
  Lead: {
    name: 'string',
    company: 'Target company ~>Company',  // Fuzzy match existing or generate
    score: 'number',
  },
  Company: { name: 'string', industry: 'string' }
})

// One line. AI finds existing company or creates one.
const lead = await db.Lead.create({ name: 'John', companyHint: 'enterprise tech' })

// Relationships just work. Batch loaded. Type safe.
const company = await lead.company

Why ai-database?

Pain Solution
N+1 queries loading relationships Promise pipelining - chain without await, batch automatically
AI data disconnected from schema Relationship operators (->, ~>, <-, <~) for AI-native linking
No types for AI-generated data Type-safe schema inference - full TypeScript support
Manual relationship management Cascade generation - create entity graphs in one call

Quick Start

import { DB } from 'ai-database'

const { db } = DB({
  Lead: { name: 'string', company: 'Company.leads' },
  Company: { name: 'string' }
})

// Chain without await
const leads = db.Lead.list()
const qualified = await leads.filter(l => l.score > 80)

// Batch relationship loading
const enriched = await leads.map(lead => ({
  name: lead.name,
  company: lead.company,  // Batch loaded!
}))

Promise Pipelining

Chain database operations without await:

const leads = db.Lead.list()
const topLeads = leads.filter(l => l.score > 80)
const names = topLeads.map(l => l.name)

// Only await when you need the result
const result = await names

Batch Relationship Loading

Eliminate N+1 queries automatically:

// Old way - N+1 queries
const leads = await db.Lead.list()
for (const lead of leads) {
  const company = await db.Company.get(lead.companyId)  // N queries!
}

// New way - batch loaded
const enriched = await db.Lead.list().map(lead => ({
  lead,
  company: lead.company,  // All companies loaded in ONE query
}))

Natural Language Queries

Ask your database questions:

const results = await db.Lead`who closed deals this month?`
const pending = await db.Order`what's stuck in processing?`

Real-World Examples

Sales Pipeline

const { db } = DB({
  Lead: {
    name: 'string',
    email: 'string',
    score: 'number',
    company: 'Company.leads',
  },
  Company: {
    name: 'string',
    industry: 'string',
  }
})

// Find high-value leads with their companies
const qualified = await db.Lead.list()
  .filter(lead => lead.score > 80)
  .map(lead => ({
    lead,
    company: lead.company,
  }))

// Ask questions naturally
const results = await db.Lead`who hasn't responded in 2 weeks?`

Customer Success

const { db } = DB({
  Customer: {
    name: 'string',
    healthScore: 'number',
    mrr: 'number',
    csm: 'User.customers',
  },
  User: { name: 'string' }
})

// At-risk customers with their CSMs
const atRisk = await db.Customer.list()
  .filter(c => c.healthScore < 50)
  .map(c => ({
    customer: c,
    csm: c.csm,
    mrr: c.mrr,
  }))

Order Management

const { db } = DB({
  Order: {
    status: 'string',
    total: 'number',
    customer: 'Customer.orders',
    items: ['OrderItem.order'],
  },
  OrderItem: { product: 'string', quantity: 'number' },
  Customer: { name: 'string' }
})

// Pending orders with all details
const pending = await db.Order
  .find({ status: 'pending' })
  .map(order => ({
    order,
    customer: order.customer,
    items: order.items,
  }))

Schema Definition

Define once, get typed operations everywhere:

const { db, events, actions, nouns, verbs } = DB({
  Post: {
    title: 'string',
    content: 'markdown',
    author: 'Author.posts',  // Creates both directions
  },
  Author: {
    name: 'string',
    // posts: Post[] auto-created from backref
  }
})

Field Types

Type Description
string Text
number Numeric
boolean True/false
date Date only
datetime Date and time
markdown Rich text
json Structured data
url URL string

Relationships

// One-to-many: Post has one Author, Author has many Posts
Post: { author: 'Author.posts' }

// Many-to-many: Post has many Tags, Tag has many Posts
Post: { tags: ['Tag.posts'] }

CRUD Operations

All operations return DBPromise for chaining:

// Read
const lead = await db.Lead.get('lead-123')
const leads = await db.Lead.list()
const first = await db.Lead.first()
const found = await db.Lead.find({ status: 'active' })

// Search
const results = await db.Lead.search('enterprise SaaS')

// Write (returns regular Promise)
const lead = await db.Lead.create({ name: 'Acme Corp' })
await db.Lead.update(lead.$id, { score: 90 })
await db.Lead.delete(lead.$id)

Chainable Methods

db.Lead.list()
  .filter(l => l.score > 50)
  .sort((a, b) => b.score - a.score)
  .limit(10)
  .map(l => ({ name: l.name, score: l.score }))

Events

React to changes in real-time:

events.on('Lead.created', event => {
  notifySlack(`New lead: ${event.data.name}`)
})

events.on('*.updated', event => {
  logChange(event)
})

forEach - Large-Scale Processing

Process thousands of items with concurrency, progress tracking, and error handling:

// Simple iteration
await db.Lead.forEach(lead => {
  console.log(lead.name)
})

// With AI and concurrency
const result = await db.Lead.forEach(async lead => {
  const analysis = await ai`analyze ${lead}`
  await db.Lead.update(lead.$id, { analysis })
}, {
  concurrency: 10,
  onProgress: p => console.log(`${p.completed}/${p.total} (${p.rate.toFixed(1)}/s)`),
})

// With error handling and retries
await db.Order.forEach(async order => {
  await sendInvoice(order)
}, {
  concurrency: 5,
  maxRetries: 3,
  retryDelay: attempt => 1000 * Math.pow(2, attempt),  // Exponential backoff
  onError: (err, order) => err.code === 'RATE_LIMIT' ? 'retry' : 'continue',
})

console.log(`Completed: ${result.completed}, Failed: ${result.failed}`)

forEach Options

Option Type Description
concurrency number Max parallel operations (default: 1)
maxRetries number Retries per item (default: 0)
retryDelay number | fn Delay between retries
onProgress fn Progress callback
onError 'continue' | 'retry' | 'skip' | 'stop' | fn Error handling
timeout number Timeout per item in ms
persist boolean | string Enable durability (string = custom action name)
resume string Resume from action ID

Durable forEach

Persist progress to survive crashes:

// Enable persistence - auto-names action as "Lead.forEach"
const result = await db.Lead.forEach(processLead, {
  concurrency: 10,
  persist: true,
})

console.log(`Action ID: ${result.actionId}`)

Custom action name:

await db.Lead.forEach(processLead, {
  persist: 'analyze-leads',  // Custom action name
})

Resume after a crash:

await db.Lead.forEach(processLead, {
  resume: 'action-123',  // Skips already-processed items
})

Actions

Track long-running operations:

const action = await actions.create({
  type: 'import-leads',
  data: { file: 'leads.csv' },
  total: 1000,
})

await actions.update(action.id, { progress: 500 })
await actions.update(action.id, { status: 'completed' })

Installation

pnpm add ai-database

Configuration

DATABASE_URL=./content         # filesystem (default)
DATABASE_URL=sqlite://./data   # SQLite
DATABASE_URL=:memory:          # in-memory

Documentation

Document Database Interface

In addition to the schema-first graph model, ai-database also exports environment-agnostic types for document-based storage (MDX files with frontmatter). These types are used by @mdxdb/* adapters and work in any JavaScript runtime (Node.js, Bun, Deno, Workers, Browser).

import type {
  DocumentDatabase,
  DocListOptions,
  DocSearchOptions,
  Document,
} from 'ai-database'

// The DocumentDatabase interface
interface DocumentDatabase<TData> {
  list(options?: DocListOptions): Promise<DocListResult<TData>>
  search(options: DocSearchOptions): Promise<DocSearchResult<TData>>
  get(id: string, options?: DocGetOptions): Promise<Document<TData> | null>
  set(id: string, doc: Document<TData>, options?: DocSetOptions): Promise<DocSetResult>
  delete(id: string, options?: DocDeleteOptions): Promise<DocDeleteResult>
  close?(): Promise<void>
}

Document Types

Type Description
Document<TData> MDX document with id, type, context, data, and content
DocumentDatabase<TData> Interface for document storage adapters
DocListOptions Options for listing documents (limit, offset, sortBy, type, prefix)
DocListResult<TData> List result with documents, total, hasMore
DocSearchOptions Search options (query, fields, semantic)
DocSearchResult<TData> Search result with scores
DocGetOptions Get options (includeAst, includeCode)
DocSetOptions Set options (createOnly, updateOnly, version)
DocSetResult Set result (id, version, created)
DocDeleteOptions Delete options (soft, version)
DocDeleteResult Delete result (id, deleted)

View Types

For bi-directional relationship rendering:

Type Description
ViewManager Interface for managing views
ViewDocument View template definition
ViewContext Context for rendering a view
ViewRenderResult Rendered markdown and entities
ViewSyncResult Mutations from extracting edited markdown
DocumentDatabaseWithViews Database with view support

Usage with @mdxdb adapters

// Filesystem adapter
import { createFsDatabase } from '@mdxdb/fs'
const db = createFsDatabase({ root: './content' })

// API adapter
import { createApiDatabase } from '@mdxdb/api'
const db = createApiDatabase({ baseUrl: 'https://api.example.com' })

// SQLite adapter
import { createSqliteDatabase } from '@mdxdb/sqlite'
const db = createSqliteDatabase({ path: './data.db' })

// Same DocumentDatabase interface regardless of backend
const doc = await db.get('posts/hello-world')
await db.set('posts/new', { data: { title: 'New Post' }, content: '# Hello' })

Relationship Operators

ai-database provides four relationship operators that control how entities are linked and how AI generation flows through your schema. These operators combine two dimensions:

  • Direction: Forward (->, ~>) vs Backward (<-, <~)
  • Match Mode: Exact (strict foreign key) vs Fuzzy (semantic/AI-driven matching)

Operator Reference

Operator Direction Match Mode Description
-> forward exact Creates and links to a new entity (strict FK)
~> forward fuzzy Searches existing entities first, generates if no match
<- backward exact References an existing entity by ID
<~ backward fuzzy Finds existing entities via semantic search

1. Forward Exact (->)

The forward exact operator creates one-to-one or one-to-many relationships where the target entity is auto-generated if not provided.

const { db } = DB({
  Startup: {
    name: 'string',
    idea: 'What is the core idea? ->Idea',           // One-to-one, auto-generated
    founders: ['Who are the founders? ->Founder'],   // One-to-many, auto-generated
  },
  Idea: { description: 'string', solution: 'string' },
  Founder: { name: 'string', role: 'string' },
})

// Creating a Startup auto-generates the Idea and Founders
const startup = await db.Startup.create({ name: 'Acme' })

const idea = await startup.idea
// => { $id: '...', $type: 'Idea', description: '...', solution: '...' }

const founders = await startup.founders
// => [{ $id: '...', $type: 'Founder', name: '...', role: '...' }, ...]

Key behaviors:

  • Text before -> is used as the AI generation prompt
  • If a value is provided, it's used instead of generating new
  • Optional fields (->Type?) skip generation when not provided
  • Nested forward exact fields cascade automatically
// Skip generation by providing an ID
const existingIdea = await db.Idea.create({ description: 'My idea' })
const startup = await db.Startup.create({
  name: 'Acme',
  idea: existingIdea.$id  // Uses existing, doesn't generate
})

2. Forward Fuzzy (~>)

The forward fuzzy operator first searches for semantically similar existing entities. If a match is found above the similarity threshold, it reuses that entity. Otherwise, it generates a new one.

const { db } = DB({
  Campaign: {
    name: 'string',
    audience: 'Target audience for campaign ~>Audience',
  },
  Audience: { name: 'string', description: 'string' },
})

// Create some audiences first
await db.Audience.create({
  name: 'Enterprise',
  description: 'Large corporations with 1000+ employees'
})
await db.Audience.create({
  name: 'SMB',
  description: 'Small businesses with less than 50 employees'
})

// This will find the Enterprise audience via semantic match
const campaign = await db.Campaign.create({
  name: 'Enterprise Sales Push',
  audienceHint: 'Big companies with thousands of employees'
})

const audience = await campaign.audience
// => { $id: '...', name: 'Enterprise', ... } (reused existing!)

Key behaviors:

  • Searches existing entities of the target type via semantic similarity
  • ${fieldName}Hint provides context for matching (e.g., audienceHint)
  • If no match exceeds threshold, generates a new entity
  • Generated entities are marked with $generated: true

3. Backward Exact (<-)

The backward exact operator creates inverse relationships, enabling aggregation queries. The edge direction is inverted - child entities point TO the parent.

const { db } = DB({
  Blog: {
    name: 'string',
    posts: ['<-Post'],  // All posts that reference this blog
  },
  Post: {
    title: 'string',
    blog: '->Blog',     // Forward reference to parent
  },
})

// Create the blog, then posts that reference it
const blog = await db.Blog.create({ name: 'Tech Blog' })
await db.Post.create({ title: 'Hello World', blog: blog.$id })
await db.Post.create({ title: 'AI Guide', blog: blog.$id })

// Backward ref enables aggregation queries
const blogPosts = await blog.posts
// => [{ title: 'Hello World', ... }, { title: 'AI Guide', ... }]

Key behaviors:

  • Creates inverted edge direction (Post -> Blog, not Blog -> Post)
  • Enables reverse lookups and aggregation queries
  • Works with explicit backrefs: ['<-Post.blog']
  • Handles self-referential relationships: children: ['<-Node.parent']

4. Backward Fuzzy (<~)

The backward fuzzy operator combines semantic matching with inverted edge direction. Perfect for grounding generated content against reference data.

const { db } = DB({
  ICP: {
    as: 'Who are they? <~Occupation',      // Ground against occupations
    at: 'Where do they work? <~Industry',  // Ground against industries
  },
  Occupation: { title: 'string', description: 'string' },
  Industry: { name: 'string', naicsCode: 'string' },
})

// Create reference data
await db.Occupation.create({ title: 'Software Developer', description: 'Writes code' })
await db.Industry.create({ name: 'Technology', naicsCode: '5112' })

// ICP grounds against existing reference data
const icp = await db.ICP.create({
  asHint: 'Engineers who build software',
  atHint: 'Tech companies'
})

const occupation = await icp.as
// => { title: 'Software Developer', ... } (matched via semantic search)

Key behaviors:

  • Uses AI/embedding similarity to find best match
  • Grounds generated content against curated reference data
  • Returns null if no semantic match found (doesn't generate)
  • Useful for taxonomies, categories, and standardized values

Threshold Syntax

For fuzzy operators (~> and <~), you can configure the similarity threshold that determines when a match is accepted vs when new generation occurs.

Field-Level Thresholds

Append threshold in parentheses after the type name:

const { db } = DB({
  Event: {
    venue: 'Where is the event? ~>Venue(0.9)',     // High threshold - strict match
    sponsor: 'Event sponsor ~>Company(0.5)',       // Low threshold - lenient match
  },
  Venue: { name: 'string', address: 'string' },
  Company: { name: 'string' },
})

Threshold values:

  • 0.9 - Very strict: Only near-exact semantic matches
  • 0.7 - Default: Balanced matching
  • 0.5 - Lenient: Accept loosely related matches

Entity-Level Thresholds

Set a default threshold for all fuzzy fields in an entity:

const { db } = DB({
  Startup: {
    $fuzzyThreshold: 0.85,  // Apply to all ~> and <~ fields
    customer: 'Who is the customer? ~>Customer',
    competitor: 'Main competitor ~>Company',
  },
  Customer: { name: 'string' },
  Company: { name: 'string' },
})

Matching behavior:

  1. If similarity score >= threshold: Reuse existing entity
  2. If similarity score < threshold: Generate new entity (for ~>) or return null (for <~)

Cascade Generation

Cascade generation automatically creates related entities recursively, building complex entity graphs from a single create() call.

Basic Cascade

Enable cascade with the cascade option:

const { db } = DB({
  Company: {
    name: 'string',
    departments: ['What departments exist? ->Department'],
  },
  Department: {
    name: 'string',
    teams: ['What teams work here? ->Team'],
  },
  Team: {
    name: 'string',
    members: ['Who are the team members? ->Employee'],
  },
  Employee: { name: 'string', role: 'string' },
})

const company = await db.Company.create(
  { name: 'TechCorp' },
  { cascade: true, maxDepth: 4 }
)

// Entire org chart generated automatically!
const departments = await company.departments
const teams = await departments[0].teams
const members = await teams[0].members

Cascade Options

Option Type Default Description
cascade boolean false Enable cascade generation
maxDepth number 0 Maximum depth of recursive generation
onProgress function - Callback for progress tracking
onError function - Error handler callback
stopOnError boolean false Stop cascade on first error
cascadeTypes string[] - Only cascade to these types

Depth Control

Control how deep the cascade goes:

// maxDepth: 0 - No cascade (default)
const root = await db.Root.create({ name: 'Test' }, { cascade: true, maxDepth: 0 })
// root.items is empty - not generated

// maxDepth: 1 - Only immediate children
const parent = await db.Parent.create({ name: 'P' }, { cascade: true, maxDepth: 1 })
// parent.children generated, but grandchildren are not

// maxDepth: 3 - Three levels deep
const company = await db.Company.create({ name: 'X' }, { cascade: true, maxDepth: 3 })
// company -> departments -> teams -> employees (stops at employees)

Progress Tracking

Monitor cascade generation progress:

const company = await db.Company.create(
  { name: 'TechCorp' },
  {
    cascade: true,
    maxDepth: 4,
    onProgress: (progress) => {
      console.log(`Phase: ${progress.phase}`)          // 'generating' or 'complete'
      console.log(`Depth: ${progress.depth}`)          // Current depth level
      console.log(`Type: ${progress.currentType}`)     // Type being generated
      console.log(`Total: ${progress.totalEntitiesCreated}`)
    },
  }
)

Selective Cascade

Only cascade to specific types:

const company = await db.Company.create(
  { name: 'TechCorp' },
  {
    cascade: true,
    maxDepth: 3,
    cascadeTypes: ['Department', 'Team'],  // Skip Employee generation
  }
)

Special Variables

$instructions

Entity-level prompting that guides AI generation for all fields:

const { db } = DB({
  Character: {
    $instructions: 'This character is from a medieval fantasy setting',
    name: 'string',
    backstory: 'What is their history?',  // Influenced by $instructions
  },
})

const character = await db.Character.create({ name: 'Sir Aldric' })
// backstory will reference medieval elements (castles, knights, quests)

Template variables resolve against entity data:

const { db } = DB({
  Problem: {
    $instructions: `
      Identify problems for occupation: {task.occupation.title}
      in industry: {task.occupation.industry.name}
    `,
    task: '<-Task',
    description: 'string',
  },
  Task: { name: 'string', occupation: '->Occupation' },
  Occupation: { title: 'string', industry: '->Industry' },
  Industry: { name: 'string' },
})

$context

Explicit context dependencies that are pre-fetched before generation:

const { db } = DB({
  Ad: {
    $context: ['Startup', 'ICP'],  // Pre-fetch these for template resolution
    $instructions: 'Generate ad for {startup.name} targeting {icp.as}',
    startup: '<-Startup',
    headline: 'string (30 chars)',
  },
  Startup: { name: 'string', icp: '->ICP' },
  ICP: { as: 'string' },
})

const icp = await db.ICP.create({ as: 'Software Engineers' })
const startup = await db.Startup.create({ name: 'CodeHelper', icp: icp.$id })
const ad = await db.Ad.create({ startup: startup.$id })

// headline will mention CodeHelper and Software Engineers

Why use $context?

  • Explicitly declares what entities are needed for generation
  • Enables efficient pre-fetching of related data
  • Makes template variable resolution predictable
  • Supports multiple levels of relationship traversal

Complete Examples

Example 1: Startup Generator

A complete startup pitch generator with cascading entity creation:

const { db } = DB({
  Startup: {
    $instructions: 'Generate a B2B SaaS startup',
    name: 'string',
    idea: 'What problem does this solve? ->Idea',
    founders: ['Who are the founding team? ->Founder'],
    customer: 'Who is the target customer? ~>CustomerPersona',
  },
  Idea: {
    problem: 'string',
    solution: 'string',
    differentiator: 'What makes this unique?',
  },
  Founder: {
    name: 'string',
    role: 'string',
    background: 'Previous experience',
  },
  CustomerPersona: {
    title: 'string',
    painPoints: 'string',
    budget: 'string',
  },
})

// Pre-populate customer personas
await db.CustomerPersona.create({
  title: 'VP of Engineering',
  painPoints: 'Managing distributed teams, code quality',
  budget: '$50k-100k annually',
})

// Generate complete startup with one call
const startup = await db.Startup.create(
  { name: 'DevFlow' },
  { cascade: true, maxDepth: 2 }
)

// Access generated entities
const idea = await startup.idea
const founders = await startup.founders
const customer = await startup.customer  // May match existing or generate new

Example 2: Content Management with Grounding

Grounding generated content against reference taxonomies:

const { db } = DB({
  Article: {
    $instructions: 'Write a technical blog post',
    title: 'string',
    content: 'markdown',
    category: 'What category? <~Category',     // Ground against existing
    tags: ['Relevant tags <~Tag'],             // Multiple semantic matches
    author: '->Author',                        // Auto-generate author
  },
  Category: { name: 'string', description: 'string' },
  Tag: { name: 'string' },
  Author: { name: 'string', bio: 'string' },
})

// Set up taxonomy
await db.Category.create({ name: 'Artificial Intelligence', description: 'ML and AI topics' })
await db.Category.create({ name: 'Web Development', description: 'Frontend and backend' })
await db.Tag.create({ name: 'Machine Learning' })
await db.Tag.create({ name: 'Deep Learning' })
await db.Tag.create({ name: 'React' })

// Create article - content grounded against existing categories
const article = await db.Article.create({
  title: 'Introduction to Neural Networks',
  categoryHint: 'AI and machine learning topics',
  tagsHint: ['neural network concepts', 'ML fundamentals'],
})

const category = await article.category
// => { name: 'Artificial Intelligence', ... } - matched existing!

const tags = await article.tags
// => [{ name: 'Machine Learning' }, { name: 'Deep Learning' }] - matched existing!

Example 3: Hierarchical Organization Chart

Building a complete org structure with bidirectional navigation:

const { db } = DB({
  Company: {
    name: 'string',
    ceo: '->Person',
    departments: ['<-Department'],  // Backward ref for aggregation
  },
  Department: {
    name: 'string',
    company: '->Company',           // Forward ref to parent
    head: '->Person',
    employees: ['<-Person'],        // All people in this department
  },
  Person: {
    name: 'string',
    role: 'string',
    department: '->Department?',    // Optional department
    reportsTo: '->Person?',         // Self-referential hierarchy
    directReports: ['<-Person.reportsTo'],
  },
})

// Create company structure
const company = await db.Company.create({ name: 'TechCorp' })
const engineering = await db.Department.create({
  name: 'Engineering',
  company: company.$id,
})

const cto = await db.Person.create({
  name: 'Alice',
  role: 'CTO',
  department: engineering.$id,
})

const engineer = await db.Person.create({
  name: 'Bob',
  role: 'Senior Engineer',
  department: engineering.$id,
  reportsTo: cto.$id,
})

// Navigate bidirectionally
const aliceReports = await cto.directReports
// => [{ name: 'Bob', ... }]

const engineeringTeam = await engineering.employees
// => [{ name: 'Alice', ... }, { name: 'Bob', ... }]

Common Patterns

Union Types for Polymorphic References

const { db } = DB({
  Comment: {
    content: 'string',
    target: '->Post|Article|Video',  // Can reference any of these types
  },
  Post: { title: 'string' },
  Article: { title: 'string' },
  Video: { title: 'string', url: 'url' },
})

const target = await comment.target
console.log(target.$matchedType)  // 'Post', 'Article', or 'Video'

Self-Referential Trees

const { db } = DB({
  Node: {
    value: 'string',
    parent: '->Node?',
    children: ['<-Node.parent'],
  },
})

const root = await db.Node.create({ value: 'Root' })
const child = await db.Node.create({ value: 'Child', parent: root.$id })

const rootChildren = await root.children
// => [{ value: 'Child', ... }]

Symmetric Relationships

const { db } = DB({
  Team: {
    name: 'string',
    members: ['->Member'],
  },
  Member: {
    name: 'string',
    team: '<-Team',  // Points back to team
  },
})

// Creating team generates members
const team = await db.Team.create({ name: 'Engineering' }, { cascade: true })

// Each member can navigate back to team
const member = (await team.members)[0]
const memberTeam = await member.team
// memberTeam.$id === team.$id