implementation focuses on three pillars: Schema Definition, Asset Pipeline, and Graph Rendering.
1. Schema Definition: The Asset Matrix Model
Define strict TypeScript interfaces using Zod for runtime validation. This ensures data integrity across the ingestion pipeline and frontend rendering.
import { z } from 'zod';
// Base Asset Schema
const AssetSchema = z.object({
id: z.string().uuid(),
type: z.enum(['video', 'text', 'code', 'quiz', 'image']),
version: z.string().semver(),
metadata: z.object({
title: z.string(),
durationSeconds: z.number().optional(),
tags: z.array(z.string()),
accessibility: z.object({
captionsUrl: z.string().url().optional(),
transcript: z.string().optional(),
altText: z.string().optional(),
}),
}),
});
// Learning Node Schema (Graph Vertex)
const LearningNodeSchema = z.object({
id: z.string().uuid(),
assetId: z.string().uuid(),
prerequisites: z.array(z.string().uuid()), // References other Node IDs
interactions: z.array(z.enum(['pause', 'quiz', 'checkpoint'])),
timeEstimate: z.number(),
});
// Course Configuration Schema
const CourseSchema = z.object({
id: z.string().uuid(),
slug: z.string(),
nodes: z.array(LearningNodeSchema),
globalMetadata: z.object({
difficulty: z.enum(['beginner', 'intermediate', 'advanced']),
category: z.string(),
lastUpdated: z.string().datetime(),
}),
});
export type Course = z.infer<typeof CourseSchema>;
export type LearningNode = z.infer<typeof LearningNodeSchema>;
2. Asset Pipeline Architecture
The pipeline must handle ingestion, validation, transcoding, and distribution.
- Ingestion: Clients upload assets to a presigned S3 URL. Metadata is sent to the API.
- Validation: Zod validates metadata against
AssetSchema.
- Processing:
- Video: Triggered via SQS to a transcoder (e.g., AWS MediaConvert) to generate HLS streams and thumbnails.
- Code: Sanitized and stored for sandboxed execution.
- Text: Processed for SEO and accessibility tags.
- Enrichment: Automated generation of transcripts (ASR) and embedding of accessibility metadata.
- Distribution: Assets are published to CDN edge nodes. The asset ID is immutable; updates create new versions.
3. Graph Rendering Engine
Courses are rendered by traversing the DAG of LearningNode objects. This allows for dynamic path resolution.
class CourseGraphEngine {
private nodes: Map<string, LearningNode>;
private completedNodes: Set<string>;
constructor(course: Course, progress: string[]) {
this.nodes = new Map(course.nodes.map(n => [n.id, n]));
this.completedNodes = new Set(progress);
}
getNextNode(userId: string): LearningNode | null {
// Find nodes where all prerequisites are met
const available = Array.from(this.nodes.values()).filter(node => {
if (this.completedNodes.has(node.id)) return false;
return node.prerequisites.every(prereqId =>
this.completedNodes.has(prereqId)
);
});
// Return node with lowest time estimate for optimal path
return available.sort((a, b) => a.timeEstimate - b.timeEstimate)[0] || null;
}
markComplete(nodeId: string): void {
this.completedNodes.add(nodeId);
// Trigger analytics event
analytics.track('node_completed', { nodeId, userId });
}
}
Architecture Decisions:
- Database: Use PostgreSQL for relational integrity of the graph structure and user progress. Use Redis for caching active course graphs.
- Storage: Object storage (S3/GCS) for binary assets. Never store binaries in the database.
- CDN: Configure Cache-Control headers based on asset versioning. Immutable assets can be cached indefinitely; versioned updates bust the cache automatically.
Pitfall Guide
-
The JSON Blob Trap: Storing entire course structures as a single JSON blob in the database.
- Impact: Prevents granular updates, breaks caching strategies, and makes querying specific assets impossible.
- Fix: Normalize the schema. Store nodes and assets separately, linking via foreign keys or references.
-
Ignoring Bandwidth Throttling: Serving raw video files without adaptive bitrate streaming (HLS/DASH).
- Impact: High bounce rates on mobile networks; excessive CDN costs.
- Fix: Implement multi-bitrate transcoding and client-side adaptive streaming.
-
Hardcoded Learning Paths: Embedding logic for prerequisites directly in the frontend code.
- Impact: Inflexible curriculum; requires code deployment to change course flow.
- Fix: Model dependencies in the data graph. The frontend should only render based on the graph state.
-
Missing Accessibility Metadata: Failing to associate captions and transcripts at the asset level.
- Impact: Legal risk; exclusion of users with disabilities; poor SEO.
- Fix: Make accessibility fields mandatory in the
AssetSchema. Automate caption generation in the pipeline.
-
State Desynchronization: Allowing frontend progress tracking without server-side validation.
- Impact: Users can cheat progress; analytics data is unreliable.
- Fix: Implement server-side verification for node completion, especially for quizzes and checkpoints.
-
Over-Engineering the Player vs. Under-Engineering the Analytics: Focusing on UI polish while neglecting xAPI or custom event tracking.
- Impact: Inability to measure learning outcomes or optimize content.
- Fix: Define a comprehensive event schema (
video_play, quiz_attempt, node_view) from day one.
-
Versioning Blindness: Updating assets without creating new versions.
- Impact: Cache poisoning; users see broken content during updates; inability to rollback.
- Fix: Enforce semantic versioning on all assets. URLs must include the version hash.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Small Niche Course (<50 Nodes) | Static Site Generator + JSON Config | Low overhead, fast deployment, minimal infra. | $ |
| Enterprise LMS (10k+ Users) | Graph DB + Microservices | Scalability, complex permissions, real-time analytics. | $$$ |
| Interactive Coding Platform | Sandboxed Execution Env + Asset Matrix | Security isolation, real-time code evaluation. | $$ |
| Multi-Language Content | Structured Assets + Translation API | Text assets can be translated independently of video; reduces re-processing costs. | $$ |
Configuration Template
Use this template to initialize a course configuration in your system.
# course-config.yaml
course:
id: "uuid-v4"
slug: "advanced-typescript-patterns"
metadata:
title: "Advanced TypeScript Patterns"
difficulty: "advanced"
category: "development"
assets:
- id: "asset-001"
type: "video"
source: "s3://bucket/intro.mp4"
metadata:
duration: 300
captions: "s3://bucket/intro.vtt"
nodes:
- id: "node-001"
assetId: "asset-001"
prerequisites: []
interactions: ["checkpoint"]
timeEstimate: 320
- id: "node-002"
assetId: "asset-002"
prerequisites: ["node-001"]
interactions: ["quiz"]
timeEstimate: 600
Quick Start Guide
- Initialize Project:
npx create-codcompass-course@latest my-course-app
cd my-course-app
- Configure Environment:
Set
AWS_S3_BUCKET, DATABASE_URL, and CDN_ENDPOINT in .env.
- Generate Schema:
Run
npm run generate:schema to create TypeScript types and database migrations based on the core models.
- Upload Assets:
Use the CLI to upload content:
npm run upload:asset -- --type video --file ./lesson1.mp4 --title "Intro"
- Deploy Graph:
Push the course configuration to the API:
npm run deploy:course -- --config ./course-config.yaml
The system validates the graph, processes assets, and returns the live course URL within seconds.