
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@echoes-io/models
Advanced tools
TypeScript models and validation schemas for Echoes - Multi-POV storytelling platform. Provides shared types, interfaces, and Zod schemas.
TypeScript models and validation schemas for Echoes - a multi-POV digital storytelling platform.
This package provides shared TypeScript interfaces, types, and validation schemas used across the Echoes ecosystem:
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 application
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
}
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.
timeline-repo/
├── content/
│ ├── <arc-name>/
│ │ └── <ep01-episode-title>/
│ │ └── <ep01-ch001-pov-title>.md
Naming Conventions:
ep01, ep02, ep12)ch001, ch005, ch123)content/introduction-arc/ep01-first-meeting/ep01-ch005-alice-first-meeting.mdThis package defines TypeScript interfaces for:
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)ChapterMetadata - Frontmatter structure for .md files:
pov, title, date, timeline, arc, episode (number), part (number), chapter (number), summary, locationoutfit, kinkTextStats - Text statistics from content analysis:
words, characters, charactersNoSpaces, paragraphs, sentences, readingTimeMinutesThis package provides the same interfaces as @echoes-io/utils but adds:
// 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';
npm install @echoes-io/models
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',
};
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 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 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',
};
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,
};
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',
};
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,
};
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);
}
# Run tests
npm test
# Type checking
npm run type-check
# Lint code
npm run lint
ChapterMetadata interface from this packageMIT
FAQs
TypeScript models and validation schemas for Echoes - Multi-POV storytelling platform. Provides shared types, interfaces, and Zod schemas.
We found that @echoes-io/models demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.