@echoes-io/models
v1.2.0
Published
TypeScript models and validation schemas for Echoes - Multi-POV storytelling platform. Provides shared types, interfaces, and Zod schemas.
Downloads
344
Maintainers
Readme
@echoes-io/models
TypeScript models and validation schemas for Echoes - a multi-POV digital storytelling platform.
Table of Contents
- Overview
- Architecture Context
- Content Structure
- Models
- Compatibility with @echoes-io/utils
- Installation
- Models Reference
- Usage
- Development
- Integration
- License
Overview
This package provides shared TypeScript interfaces, types, and validation schemas used across the Echoes ecosystem:
- Timeline repositories (content storage)
- Tracker service (content management and API)
- RAG service (semantic search and AI context)
- Web application (frontend)
- CLI tools and utilities
Architecture Context
Echoes is organized as a multi-repository system:
@echoes-io/utils # Shared utilities (markdown parsing, text stats)
@echoes-io/models # This package - shared types and schemas
@echoes-io/tracker # Content management API and database
@echoes-io/rag # Semantic search and AI context
echoes-timeline-* # Individual timeline content repositories
echoes-web-app # Frontend applicationContent Structure
Echoes organizes storytelling content in a hierarchical structure:
erDiagram
Timeline ||--o{ Arc : contains
Arc ||--o{ Episode : contains
Episode ||--o{ Part : contains
Episode ||--o{ Chapter : contains
Part ||--o{ Chapter : "belongs to"
Timeline {
string name PK
string description
}
Arc {
string timelineName FK
string name PK
int number
string description
}
Episode {
string timelineName FK
string arcName FK
int number PK
string slug
string title
string description
}
Part {
string timelineName FK
string arcName FK
int episodeNumber FK
int number PK
string slug
string title
string description
}
Chapter {
string timelineName FK
string arcName FK
int episodeNumber FK
int partNumber FK
int number PK
string pov
string title
string date
string summary
string location
string outfit "optional"
string kink "optional"
int words
int characters
int charactersNoSpaces
int paragraphs
int sentences
int readingTimeMinutes
}Hierarchy
Timeline
└─ Arc (ordered)
└─ Episode (numbered)
├─ Part (numbered, optional subdivision)
└─ Chapter (numbered, continuous within episode)Note: Chapter numbering is continuous within an episode and does NOT reset per part.
File Structure Convention
timeline-repo/
├── content/
│ ├── <arc-name>/
│ │ └── <ep01-episode-title>/
│ │ └── <ep01-ch001-pov-title>.mdNaming Conventions:
- Episode numbers: 2-digit padding (
ep01,ep02,ep12) - Chapter numbers: 3-digit padding (
ch001,ch005,ch123) - Slugification: lowercase, hyphens, no special characters
- Example:
content/introduction-arc/ep01-first-meeting/ep01-ch005-alice-first-meeting.md
Models
This package defines TypeScript interfaces for:
Core Content Models
Timeline- Root story container with name and descriptionArc- Story arc within timeline with orderingEpisode- Episode within arc with numbering, slug, title, descriptionPart- Part within episode with numbering, slug, title, descriptionChapter- Individual content file with metadata and stats (content stored in .md files)
Metadata Models
ChapterMetadata- Frontmatter structure for .md files:- Required fields:
pov,title,date,timeline,arc,episode(number),part(number),chapter(number),summary,location - Optional fields:
outfit,kink
- Required fields:
TextStats- Text statistics from content analysis:words,characters,charactersNoSpaces,paragraphs,sentences,readingTimeMinutes
Compatibility with @echoes-io/utils
This package provides the same interfaces as @echoes-io/utils but adds:
- Database-ready models with IDs and relationships
- Zod validation schemas for runtime type checking
- API request/response types for the tracker service
- Extended metadata for content management
// These interfaces match @echoes-io/utils exactly
import type { ChapterMetadata, TextStats } from '@echoes-io/models';
// These are new database models
import type { Timeline, Arc, Episode, Chapter } from '@echoes-io/models';
// These are new validation schemas
import { validateChapterMetadata, validateTimeline } from '@echoes-io/models';Installation
npm install @echoes-io/modelsModels Reference
Timeline
Root container for a story universe.
| Field | Type | Key | Description |
|-------|------|-----|-------------|
| name | string | PK | Timeline name (unique identifier) |
| description | string | | Timeline description |
Example:
const timeline: Timeline = {
name: 'main-story',
description: 'The primary storyline',
};Arc
Story arc within a timeline. Represents major story phases (e.g., "Introduction", "Rising Action").
| Field | Type | Key | Description |
|-------|------|-----|-------------|
| timelineName | string | FK, PK | Timeline name (foreign key) |
| name | string | PK | Arc name |
| number | number | | Arc order/number within timeline |
| description | string | | Arc description |
Primary Key: (timelineName, name)
Example:
const arc: Arc = {
timelineName: 'main-story',
name: 'introduction',
number: 1,
description: 'The beginning of the story',
};Episode
Episode within an arc. Represents story events or time periods.
| Field | Type | Key | Description |
|-------|------|-----|-------------|
| timelineName | string | FK, PK | Timeline name (foreign key) |
| arcName | string | FK, PK | Arc name (foreign key) |
| number | number | PK | Episode number (e.g., 1, 2, 3) |
| slug | string | | Episode slug (URL-friendly) |
| title | string | | Episode title |
| description | string | | Episode description |
Primary Key: (timelineName, arcName, number)
Example:
const episode: Episode = {
timelineName: 'main-story',
arcName: 'introduction',
number: 1,
slug: 'first-meeting',
title: 'First Meeting',
description: 'Alice and Bob meet for the first time',
};Part
Part within an episode. Used for organizing longer episodes into subdivisions.
| Field | Type | Key | Description |
|-------|------|-----|-------------|
| timelineName | string | FK, PK | Timeline name (foreign key) |
| arcName | string | FK, PK | Arc name (foreign key) |
| episodeNumber | number | FK, PK | Episode number (foreign key) |
| number | number | PK | Part number within episode |
| slug | string | | Part slug (URL-friendly) |
| title | string | | Part title |
| description | string | | Part description |
Primary Key: (timelineName, arcName, episodeNumber, number)
Example:
const part: Part = {
timelineName: 'main-story',
arcName: 'introduction',
episodeNumber: 1,
number: 1,
slug: 'morning',
title: 'Morning',
description: 'The morning scene',
};Chapter
Individual content file (.md with frontmatter). Extends ChapterMetadata and TextStats from @echoes-io/utils.
Note: Chapter numbering is continuous within an episode and does NOT reset per part.
Structure:
interface Chapter extends ChapterMetadata, TextStats {
timelineName: string;
arcName: string;
episodeNumber: number;
partNumber: number;
number: number;
}| Field | Type | Key | Required | Description |
|-------|------|-----|----------|-------------|
| Database Keys | | | | |
| timelineName | string | FK, PK | ✓ | Timeline name (foreign key) |
| arcName | string | FK, PK | ✓ | Arc name (foreign key) |
| episodeNumber | number | FK, PK | ✓ | Episode number (foreign key) |
| partNumber | number | FK | ✓ | Part number (indicates which part the chapter belongs to) |
| number | number | PK | ✓ | Chapter number (unique within episode) |
| From ChapterMetadata | | | | |
| pov | string | | ✓ | Point of view character |
| title | string | | ✓ | Chapter title |
| date | string | | ✓ | Chapter date (free text, e.g., "2025-01-01 Monday") |
| timeline | string | | ✓ | Timeline name (from frontmatter) |
| arc | string | | ✓ | Arc name (from frontmatter) |
| episode | number | | ✓ | Episode number (from frontmatter) |
| part | number | | ✓ | Part number (from frontmatter) |
| chapter | number | | ✓ | Chapter number (from frontmatter) |
| summary | string | | ✓ | Brief chapter description |
| location | string | | ✓ | Chapter location/setting |
| outfit | string | | | Character outfit description (optional) |
| kink | string | | | Content tags/kinks (optional) |
| From TextStats | | | | |
| words | number | | ✓ | Total word count |
| characters | number | | ✓ | Total character count (including spaces) |
| charactersNoSpaces | number | | ✓ | Character count excluding spaces |
| paragraphs | number | | ✓ | Number of paragraphs |
| sentences | number | | ✓ | Number of sentences |
| readingTimeMinutes | number | | ✓ | Estimated reading time in minutes (200 words/min) |
Primary Key: (timelineName, arcName, episodeNumber, number)
Example:
const chapter: Chapter = {
// Database keys
timelineName: 'main-story',
arcName: 'introduction',
episodeNumber: 1,
partNumber: 1,
number: 1,
// Metadata (from ChapterMetadata)
pov: 'Alice',
title: 'First Meeting',
date: '2025-01-01 Monday',
timeline: 'main-story',
arc: 'introduction',
episode: 1,
part: 1,
chapter: 1,
summary: 'Alice meets Bob at the coffee shop',
location: 'Coffee Shop',
outfit: 'Blue dress', // optional
kink: 'romance', // optional
// Text statistics (from TextStats)
words: 1000,
characters: 5000,
charactersNoSpaces: 4000,
paragraphs: 10,
sentences: 50,
readingTimeMinutes: 5,
};ChapterMetadata
Frontmatter structure for .md files. This is the source of truth that gets replicated into the database.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| pov | string | ✓ | Point of view character |
| title | string | ✓ | Chapter title |
| date | string | ✓ | Chapter date (free text, e.g., "2025-01-01 Monday") |
| timeline | string | ✓ | Timeline name |
| arc | string | ✓ | Arc name |
| episode | number | ✓ | Episode number |
| part | number | ✓ | Part number |
| chapter | number | ✓ | Chapter number |
| summary | string | ✓ | Brief chapter description |
| location | string | ✓ | Chapter location/setting |
| outfit | string | | Character outfit description (optional) |
| kink | string | | Content tags/kinks (optional) |
Example:
const metadata: ChapterMetadata = {
pov: 'Alice',
title: 'First Meeting',
date: '2025-01-01 Monday',
timeline: 'main-story',
arc: 'introduction',
episode: 1,
part: 1,
chapter: 1,
summary: 'Alice meets Bob at the coffee shop',
location: 'Coffee Shop',
};TextStats
Text statistics calculated from content analysis (via @echoes-io/utils).
| Field | Type | Description |
|-------|------|-------------|
| words | number | Total word count |
| characters | number | Total character count (including spaces) |
| charactersNoSpaces | number | Character count excluding spaces |
| paragraphs | number | Number of paragraphs |
| sentences | number | Number of sentences |
| readingTimeMinutes | number | Estimated reading time in minutes (200 words/min) |
Example:
const stats: TextStats = {
words: 1000,
characters: 5000,
charactersNoSpaces: 4000,
paragraphs: 10,
sentences: 50,
readingTimeMinutes: 5,
};Usage
import {
Timeline,
Arc,
Episode,
Part,
Chapter,
ChapterMetadata,
validateTimeline,
validateChapter,
} from '@echoes-io/models';
// Type-safe content structures
const timeline: Timeline = {
name: 'main-story',
description: 'The primary storyline',
};
const arc: Arc = {
timelineName: 'main-story',
name: 'introduction',
number: 1,
description: 'The beginning of the story',
};
const episode: Episode = {
timelineName: 'main-story',
arcName: 'introduction',
number: 1,
slug: 'first-meeting',
title: 'First Meeting',
description: 'Alice and Bob meet for the first time',
};
const part: Part = {
timelineName: 'main-story',
arcName: 'introduction',
episodeNumber: 1,
number: 1,
slug: 'morning',
title: 'Morning',
description: 'The morning scene',
};
const chapter: Chapter = {
timelineName: 'main-story',
arcName: 'introduction',
episodeNumber: 1,
partNumber: 1,
number: 1,
pov: 'Alice',
title: 'First Meeting',
date: '2025-01-01 Monday',
timeline: 'main-story',
arc: 'introduction',
episode: 1,
part: 1,
chapter: 1,
summary: 'Alice meets Bob at the coffee shop',
location: 'Coffee Shop',
words: 1000,
characters: 5000,
charactersNoSpaces: 4000,
paragraphs: 10,
sentences: 50,
readingTimeMinutes: 5,
};
// Validation with Zod
try {
validateTimeline(timeline);
validateChapter(chapter);
console.log('Valid!');
} catch (error) {
console.error('Validation error:', error);
}Development
Tech Stack
- Language: TypeScript (strict mode)
- Validation: Zod (planned)
- Testing: Vitest
- Linting: Biome
Scripts
# Run tests
npm test
# Type checking
npm run type-check
# Lint code
npm run lintIntegration
With @echoes-io/utils
- Uses
ChapterMetadatainterface from this package - Provides validation for parsed frontmatter
With @echoes-io/tracker
- Implements these models in database schema
- Uses types for API endpoints and operations
With @echoes-io/rag
- Uses models for content indexing
- Provides types for semantic search results
License
MIT
