catsys
Version:
Category-theoretic system design framework for scalable, modular systems using mathematical foundations
292 lines (291 loc) • 11.4 kB
JavaScript
;
/**
* Video Streaming Service - Streamlined Example
*
* Demonstrates core category theory framework functionality
* without excessive complexity.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.videoSpec = exports.VideoStreamingSpec = exports.v0 = exports.s0 = void 0;
exports.createVideoService = createVideoService;
exports.createForDevelopment = createForDevelopment;
exports.createForTesting = createForTesting;
exports.createForProduction = createForProduction;
exports.runExample = runExample;
const index_1 = require("../src/index");
const fast_check_1 = __importDefault(require("fast-check"));
// Initial states
exports.s0 = { version: 0, videos: {} };
exports.v0 = { trending: [], lastUpdate: new Date().toISOString() };
// ========================================
// 2. DOMAIN SPECIFICATION
// ========================================
class VideoStreamingSpec extends index_1.DomainSpec {
constructor() {
super(...arguments);
this.initialState = exports.s0;
this.initialView = exports.v0;
}
// Test generators for law verification
generateEvents() {
return fast_check_1.default.array(fast_check_1.default.oneof(fast_check_1.default.record({
kind: fast_check_1.default.constant('VideoUploaded'),
videoId: fast_check_1.default.string(),
metadata: fast_check_1.default.record({ title: fast_check_1.default.string(), description: fast_check_1.default.string() }),
userId: fast_check_1.default.string(),
id: fast_check_1.default.string(),
timestamp: fast_check_1.default.date().map(d => d.toISOString())
}), fast_check_1.default.record({
kind: fast_check_1.default.constant('VideoViewed'),
videoId: fast_check_1.default.string(),
userId: fast_check_1.default.string(),
id: fast_check_1.default.string(),
timestamp: fast_check_1.default.date().map(d => d.toISOString())
})));
}
generateCommands() {
return fast_check_1.default.oneof(fast_check_1.default.record({
kind: fast_check_1.default.constant('UploadVideo'),
metadata: fast_check_1.default.record({ title: fast_check_1.default.string(), description: fast_check_1.default.string() }),
blobId: fast_check_1.default.string(),
userId: fast_check_1.default.string()
}), fast_check_1.default.record({
kind: fast_check_1.default.constant('ViewVideo'),
videoId: fast_check_1.default.string(),
userId: fast_check_1.default.string()
}));
}
generateRaw() {
return fast_check_1.default.record({
videoFile: fast_check_1.default.constant(new ArrayBuffer(1024)),
title: fast_check_1.default.string().filter(s => s.length > 0),
description: fast_check_1.default.string(),
userId: fast_check_1.default.string().filter(s => s.length > 0)
});
}
// Pure business logic functions
normalize(raw) {
if (!raw.videoFile || !raw.title || !raw.userId) {
throw new Error('Invalid video upload data');
}
return {
videoFile: raw.videoFile,
metadata: { title: raw.title, description: raw.description || '' },
userId: raw.userId
};
}
validate(command) {
switch (command.kind) {
case 'UploadVideo': return !!(command.metadata.title && command.userId);
case 'ViewVideo': return !!(command.videoId && command.userId);
default: return false;
}
}
decide(state, command) {
const now = new Date().toISOString();
const eventId = generateId();
switch (command.kind) {
case 'UploadVideo':
return [{
kind: 'VideoUploaded',
videoId: generateId(),
metadata: command.metadata,
userId: command.userId,
id: eventId,
timestamp: now
}];
case 'ViewVideo':
if (!state.videos[command.videoId])
return [];
return [{
kind: 'VideoViewed',
videoId: command.videoId,
userId: command.userId,
id: eventId,
timestamp: now
}];
default: return [];
}
}
evolve(state, event) {
const newState = { ...state, version: state.version + 1 };
switch (event.kind) {
case 'VideoUploaded':
newState.videos[event.videoId] = {
id: event.videoId,
metadata: event.metadata,
userId: event.userId,
views: 0,
createdAt: event.timestamp
};
break;
case 'VideoViewed':
if (newState.videos[event.videoId]) {
newState.videos[event.videoId] = {
...newState.videos[event.videoId],
views: newState.videos[event.videoId].views + 1
};
}
break;
}
return newState;
}
project(view, event) {
const newView = { ...view, lastUpdate: event.timestamp };
switch (event.kind) {
case 'VideoUploaded':
const videoInfo = {
id: event.videoId,
metadata: event.metadata,
userId: event.userId,
views: 0,
createdAt: event.timestamp
};
newView.trending = [videoInfo, ...newView.trending].slice(0, 10);
break;
case 'VideoViewed':
// Update trending list
newView.trending = newView.trending.map(v => v.id === event.videoId ? { ...v, views: v.views + 1 } : v).sort((a, b) => b.views - a.views);
break;
}
return newView;
}
queries(view) {
return {
getTrending: () => view.trending,
getVideo: (id) => view.trending.find(v => v.id === id) || null
};
}
deriveView(state) {
const trending = Object.values(state.videos)
.sort((a, b) => b.views - a.views)
.slice(0, 10);
return {
getTrending: () => trending,
getVideo: (id) => state.videos[id] || null
};
}
}
exports.VideoStreamingSpec = VideoStreamingSpec;
// ========================================
// 3. SERVICE CREATION
// ========================================
const videoSpec = new VideoStreamingSpec();
exports.videoSpec = videoSpec;
function createVideoService(ports) {
const baseService = (0, index_1.createGenericService)(videoSpec, exports.s0, ports);
return {
...baseService,
// Video-specific operations
uploadVideo: (0, index_1.withMetrics)('upload_video', async (metadata, videoFile, userId) => {
const blobId = generateId();
await ports.blob.put(`videos/${blobId}`, videoFile);
const command = {
kind: 'UploadVideo',
metadata,
blobId,
userId
};
return baseService.handle(exports.s0, command);
}),
getAnalytics: (0, index_1.withMetrics)('get_analytics', async () => {
const stats = await ports.sql.query('SELECT COUNT(*) as total_videos FROM videos');
return { totalVideos: stats[0]?.total_videos || 0 };
})
};
}
// ========================================
// 4. DEPLOYMENT CONFIGURATIONS
// ========================================
function createForDevelopment() {
return (0, index_1.createDevelopmentRoot)(createVideoService, index_1.adapterRegistry, {
enableDebugLogging: true,
captureEvents: true
});
}
function createForTesting() {
// Create service without automatic law verification for cleaner tests
const ports = {
sql: index_1.adapters.inMemorySql(),
bus: index_1.adapters.inMemoryBus(),
blob: index_1.adapters.inMemoryBlob()
};
// Create service manually without law verification
const handler = async (state, command) => {
const events = videoSpec.decide(state, command);
// Persist events
await ports.sql.query('INSERT INTO events VALUES ($1)', [JSON.stringify(events)]);
// Publish events
for (const event of events) {
await ports.bus.publish(event);
}
// Return new state
return events.reduce((s, e) => videoSpec.evolve(s, e), state);
};
return {
handle: handler,
uploadVideo: async (metadata, videoFile, userId) => {
const blobId = generateId();
await ports.blob.put(`videos/${blobId}`, videoFile);
const command = {
kind: 'UploadVideo',
metadata,
blobId,
userId
};
return handler(exports.s0, command);
},
getAnalytics: async () => {
const stats = await ports.sql.query('SELECT COUNT(*) as total_videos FROM videos');
return { totalVideos: stats[0]?.total_videos || 0 };
},
healthCheck: async () => ({
status: 'healthy',
timestamp: new Date().toISOString()
}),
queryView: (v) => videoSpec.queries(v)
};
}
function createForProduction() {
return (0, index_1.createFromEnvironment)(createVideoService, {
environment: 'production',
adapters: { sql: 'postgres', bus: 'kafka', blob: 's3' },
config: {
postgres: { host: 'prod-db.company.com' },
kafka: { brokers: ['kafka1.company.com'] },
s3: { bucket: 'production-videos' }
}
}, index_1.adapterRegistry);
}
// ========================================
// 5. EXAMPLE USAGE
// ========================================
async function runExample() {
console.log("🎬 Video Streaming Example");
// Create development service
const service = createForDevelopment();
// Upload a video
const uploadResult = await service.uploadVideo({ title: "Category Theory Explained", description: "Math meets code" }, new Uint8Array(1024), "user123");
console.log("✅ Video uploaded");
// View the video
const videoId = Object.keys(uploadResult.videos)[0];
const viewResult = await service.handle(uploadResult, {
kind: 'ViewVideo',
videoId,
userId: 'viewer456'
});
console.log(`✅ Video viewed: ${viewResult.videos[videoId].views} total views`);
// Get analytics
const analytics = await service.getAnalytics();
console.log(`✅ Analytics: ${analytics.totalVideos} total videos`);
// Health check
const health = await service.healthCheck();
console.log(`✅ Service health: ${health.status}`);
}
// Utility function
function generateId() {
return Math.random().toString(36).substring(2, 15);
}