Architecture Overview
System Architecture
Inkweld is a monorepo application with distinct frontend and backend services:
Frontend (Angular 21)
Technology Stack
- Framework: Angular 21 with standalone components
- State Management: Service-based with RxJS
- Offline Storage: IndexedDB via y-indexeddb
- Real-Time: Yjs + y-websocket provider
- Editor: ProseMirror with y-prosemirror binding
- Build: Angular CLI + Vite
- Testing: Vitest (unit) + Playwright (e2e)
Modern Angular Patterns
// Dependency injection with inject()
export class MyComponent {
private projectService = inject(ProjectService);
private router = inject(Router);
}
// Modern control flow
@if (project) {
<app-editor [project]="project" />
} @else {
<app-loading />
}
@for (item of items; track item.id) {
<app-item [data]="item" />
}
Key Services
- ProjectStateService - Central project state management
- UnifiedProjectService - Hybrid online/offline operations
- DocumentService - Yjs document lifecycle
- WorldbuildingService - Template/schema system
- AuthService - Authentication and session management
Backend (Bun + Hono)
Technology Stack
- Runtime: Bun (JavaScript runtime built for speed)
- Framework: Hono (lightweight web framework)
- Database: SQLite or D1 via Drizzle ORM
- Document Storage: LevelDB (per-project instances)
- Real-Time: Native WebSocket support
- Testing: Bun's built-in test runner
API Architecture
// Hono route example
app.get('/api/projects', authMiddleware, async (c) => {
const user = c.get('user');
const projects = await projectService.findByUser(user.id);
return c.json(projects);
});
// WebSocket upgrade
app.get('/ws/:projectId', async (c) => {
const projectId = c.req.param('projectId');
return wsHandler.upgrade(c.req.raw, projectId);
});
Key Modules
- auth/ - Session-based authentication
- routes/ - HTTP endpoints
- services/ - Business logic layer
- db/ - Drizzle ORM schema and database setup
- durable-objects/ - (Cloudflare Workers deployment only)
Real-Time Collaboration (Yjs)
CRDT Technology
Yjs uses Conflict-free Replicated Data Types (CRDTs) to enable:
- Concurrent editing without conflicts
- Eventual consistency across all clients
- Offline support with automatic merging
- Fine-grained updates (character-level)
Data Flow
Per-Project Storage
Each project gets its own LevelDB instance:
Benefits:
- Isolation - Projects don't interfere
- Scalability - Independent read/write operations
- Cleanup - Easy to delete project data
- Connection pooling - Automatic idle connection management
Database Schema
Drizzle ORM Schema
Document Storage Architecture
Authentication & Security
Session-Based Auth
- httpOnly cookies for CSRF protection
- Session store backed by Drizzle
- No JWT tokens (intentional design choice)
- Optional GitHub OAuth (configurable)
Security Measures
- CORS configuration via
ALLOWED_ORIGINS - CSRF protection on state-changing requests
- Content Security Policy headers
- Rate limiting (configurable)
- User approval workflow (optional)
Deployment Targets
Bun (Primary)
bun run dev # Development
bun run build # Production build
bun run start # Production server
Node.js (Compatible)
bun run build:node # Transpile for Node
node dist/node-runner.js
Cloudflare Workers (Experimental)
bun run build:worker
npx wrangler deploy
Requires:
- Durable Objects for WebSocket persistence
- D1 database binding
- R2 for file storage (optional)
Docker (Recommended for Production)
docker build -t inkweld .
docker run -p 8333:8333 -v inkweld_data:/data inkweld
Benefits:
- Single ~200MB image with frontend embedded
- Automatic migrations on startup
- Volume mounting for persistence
- Health check endpoint
Build Pipeline
Development
npm start # Runs both frontend and backend
Powered by:
- Concurrently to run multiple processes
- Angular CLI dev server (port 4200)
- Bun runtime (port 8333)
Production
# Frontend
cd frontend
bun run build
bun run compress # Optional
# Backend
cd backend
bun run build
Output:
- frontend/dist/ - Angular production bundle
- backend/dist/ - Bun-optimized backend code
Docker Build
Multi-stage Dockerfile:
- Frontend build stage (Node.js)
- Backend build stage (Bun)
- Runtime stage (minimal Bun image)
- Copies frontend dist to static assets
- Includes migrations for auto-run
- Non-root user for security
Testing Strategy
Frontend Tests
npm test # Vitest unit tests
npm run e2e # Playwright e2e tests
- Unit tests with @ngneat/spectator
- E2E tests with fixtures (authenticatedPage, etc.)
- Mock API handlers in
e2e/mock-api/ - Screenshot tests for visual regression
Backend Tests
bun test
- Unit tests for services and utilities
- Integration tests for API endpoints
- Supertest for HTTP assertions
- In-memory SQLite for test isolation
API Documentation
OpenAPI Specification
Generated from code annotations:
cd backend
bun run generate:openapi
Output: backend/openapi.json
Client Generation
Auto-generate TypeScript client for frontend:
cd backend
bun run generate:angular-client
Output: frontend/src/api-client/
Never edit generated files manually.
Code Quality
Linting
- ESLint with TypeScript support
- Prettier for formatting
- Shared config across frontend/backend
Pre-commit Hooks
Consider adding:
- Lint-staged for fast checks
- Husky for Git hooks
- Prettier format check
CI/CD
GitHub Actions workflow:
- Lint all code
- Test frontend and backend
- Build Docker image
- Publish to GHCR (on main branch)
Performance Considerations
Frontend
- Lazy loading for routes
- OnPush change detection where appropriate
- Virtual scrolling for long lists (consider)
- Service Worker for offline support
Backend
- LevelDB connection pooling with automatic cleanup
- Database indexing on foreign keys
- Pagination for large result sets
- WebSocket connection limits (configurable)
Real-Time
- Debounced updates for UI refresh
- Incremental sync (only changed content)
- Efficient CRDT merging via Yjs
- Binary encoding over WebSocket
Development Workflow
Workspace Structure
NPM Scripts (Root)
bun install # Install all dependencies
npm start # Start dev servers
npm test # Run all tests
npm run lint # Lint all code
Git Workflow
- Create feature branch
- Make changes + add tests
- Run
npm testandnpm run lint - Open PR
- CI validates
- Merge to main
Extensibility
Custom Worldbuilding Templates
Define schemas in database:
interface WorldbuildingSchema {
name: string;
tabs: SchemaTab[];
}
interface SchemaTab {
title: string;
fields: SchemaField[];
}
Plugin System (Future)
Consider:
- Custom document types
- Export format plugins
- Theme customization
- Integration hooks (Discord, Slack)
Next Steps
- Review API documentation
- Read deployment guide
- Check the user guide
- Explore the features