- Updated functions.json files in theme_editor, ui_auth, ui_footer, ui_header, ui_home, ui_intro, ui_level2, ui_level3, ui_level4, ui_level5, ui_level6, ui_login, ui_pages, ui_permissions, user_manager, and workflow_editor packages. - Removed luaScript entries from function definitions, retaining only category and other relevant metadata. - Adjusted documentation in prisma/README.md to remove LuaScript entity reference.
20 KiB
MetaBuilder
An ultra-generic, data-driven platform where everything flows through the database.
No hardcoded routes, no component imports, no assumptions. The entire application structure lives in the database, rendered by a generic JSON-to-React engine.
Table of Contents
- Core Concept
- Quick Start
- Architecture
- Routing System
- Package System
- Permission System
- Deployment
- Database
- Development
- Project Structure
Core Concept
The Mental Model
Browser URL → Database Query → JSON Component → Generic Renderer → React → User
Zero hardcoded connections. Everything is a database lookup:
// ❌ Traditional way (hardcoded)
import HomePage from './HomePage'
<Route path="/" component={HomePage} />
// ✅ MetaBuilder way (data-driven)
const route = await db.query('PageConfig', { path: '/' })
const component = await loadPackage(route.packageId)
return renderJSONComponent(component)
Key Principles
- No Hardcoded Routes: Routes live in
PageConfigtable - No Component Imports: Components are JSON definitions in package files
- No Direct Database Access: Everything goes through DBAL (Database Abstraction Layer)
- Complete Loose Coupling: Frontend knows nothing about packages
Quick Start
One-Command Deployment
# Deploy everything (PostgreSQL, DBAL, Next.js, Media daemon, Redis, Nginx)
./deployment/deploy.sh all --bootstrap
This spins up:
- PostgreSQL database
- C++ DBAL daemon (database abstraction)
- Next.js frontend
- C++ media processing daemon
- Redis cache
- Nginx reverse proxy
- Monitoring stack (optional)
Then visit: http://localhost:3000
Development Setup
# Clone and install
git clone <repo>
cd metabuilder
# Option 1: Run from root (recommended for quick setup)
npm install
npm run db:generate
npm run db:push
# Option 2: Run from frontends/nextjs
cd frontends/nextjs
npm install
npm run db:generate
npm run db:push
# Bootstrap seed data
cd ../../deployment
./scripts/bootstrap-system.sh
# Start development (from root)
cd ..
npm run dev
# Or from frontends/nextjs
cd frontends/nextjs
npm run dev
Prerequisites
- Docker & Docker Compose
- Node.js 18+
- Build tools: cmake, ninja, g++/clang (for C++ daemons)
Architecture
System Components
┌─────────────┐
│ Browser │
└──────┬──────┘
│ HTTP
┌──────▼──────┐ ┌───────────┐
│ Nginx │────▶│ Redis │
└──────┬──────┘ └───────────┘
│
┌──────▼──────┐ ┌───────────┐
│ Next.js │────▶│ DBAL │
│ Frontend │ │ Daemon │
└──────┬──────┘ │ (C++) │
│ └─────┬─────┘
│ │
│ ┌─────▼─────┐
│ │ PostgreSQL│
│ └───────────┘
│
┌──────▼──────┐ ┌───────────┐
│ Media │────▶│ HLS │
│ Daemon │ │ Streams │
│ (C++) │ └───────────┘
└─────────────┘
Data Flow
- User visits URL (
/,/dashboard,/about) - Next.js queries PageConfig table for that path
- Database returns packageId + component reference
- Frontend loads JSON package from
/packages/{packageId}/ - Generic renderer converts JSON → React elements
- User sees the rendered page
Zero hardcoded assumptions!
Routing System
Priority-Based Routing
Routes have two priority levels:
Priority 1: God Panel Routes (PageConfig table)
- User-configurable through admin UI
- God/supergod users can add/remove/modify routes
- Highest priority, overrides everything
Priority 2: Package Routes (InstalledPackage.config)
- Package-defined defaults
- Set during package installation
- Fallback when god panel has no override
Example: Root Route
Database seed (seed/database/installed_packages.yaml:44-54):
- packageId: ui_home
config: |
{
"defaultRoute": "/",
"publicAccess": true
}
Frontend code (frontends/nextjs/src/app/page.tsx):
// Check god panel routes first
const godRoute = await db.query('PageConfig', { path: '/', isPublished: true })
if (godRoute) {
return renderComponent(godRoute.packageId, godRoute.component)
}
// Fall back to package default routes
const pkg = await db.query('InstalledPackage', { 'config.defaultRoute': '/' })
return renderComponent(pkg.packageId, 'HomePage')
God Panel Override
Supergod users can remap any route:
-- Remap "/" to dashboard instead of ui_home
INSERT INTO "PageConfig" (
id, path, packageId, component, level, requiresAuth, isPublished
) VALUES (
'root_route', '/', 'dashboard', 'DashboardPage', 1, true, true
);
No code changes required!
Route Schema
model PageConfig {
path String // URL: "/", "/dashboard", "/about"
packageId String? // Which package: "ui_home", "dashboard"
component String? // Which component: "HomePage", "DashboardPage"
componentTree String // Or full JSON component tree
level Int // Permission level (0-5)
requiresAuth Boolean
isPublished Boolean
}
Package System
Package Structure
Packages are completely self-contained JSON + optional static files:
packages/{packageId}/
├── package.json # Metadata
├── components/
│ └── ui.json # Declarative JSON components
├── styles/
│ └── tokens.json # Design tokens (colors, spacing, shadows, etc.)
└── static/
└── assets/ # Images, fonts, etc.
JSON Component Example
From packages/ui_home/components/ui.json:
{
"components": [
{
"id": "home_page",
"name": "HomePage",
"description": "Main home page layout",
"render": {
"template": {
"type": "Box",
"component": "main",
"className": "home-page",
"children": [
{ "$ref": "hero_section" },
{ "$ref": "features_section" }
]
}
}
},
{
"id": "hero_section",
"name": "HeroSection",
"props": [
{ "name": "title", "type": "string", "default": "Build Anything, Visually" }
],
"render": {
"template": {
"type": "Box",
"component": "section",
"className": "hero-section",
"children": [
{
"type": "Text",
"variant": "h1",
"children": "{{title}}"
}
]
}
}
}
]
}
Generic Renderer
The frontend never imports package components. It uses a generic renderer:
import { renderJSONComponent } from '@/lib/packages/json/render-json-component'
// Load component from JSON
const pkg = await loadJSONPackage('/packages/ui_home')
const component = pkg.components.find(c => c.id === 'home_page')
// Render using generic renderer (no hardcoded React components!)
return renderJSONComponent(component, {}, {})
Available Packages
| Package ID | Description |
|---|---|
ui_home |
Landing page with hero, features, about sections |
ui_header |
Application header/navbar |
ui_footer |
Application footer |
ui_auth |
Authentication UI components |
ui_login |
Login page |
dashboard |
User dashboard |
user_manager |
User management (admin) |
package_manager |
Package installation UI (god) |
database_manager |
Database management (supergod) |
schema_editor |
Schema editor (supergod) |
Permission System
6-Level Hierarchy
| Level | Role | Access | Example Route |
|---|---|---|---|
| 0 | Public | Unauthenticated | / (landing page) |
| 1 | User | Personal dashboard | /dashboard |
| 2 | Moderator | Content moderation | /moderator |
| 3 | Admin | User management | /admin |
| 4 | God | Package installation, workflows | /builder |
| 5 | Supergod | Full system control | /supergod |
Each level inherits all permissions from levels below.
Permission Checks
Database records control access:
# seed/database/package_permissions.yaml
- packageId: ui_home
role: public # Level 0 - anyone
permission: read
granted: true
- packageId: dashboard
role: user # Level 1 - authenticated
permission: read
granted: true
- packageId: database_manager
role: supergod # Level 5 - full control
permission: admin
granted: true
Deployment
One-Command Deploy
# Full stack with bootstrap
./deployment/deploy.sh all --bootstrap
# Individual stacks
./deployment/deploy.sh production # Main services only
./deployment/deploy.sh development # Dev environment
./deployment/deploy.sh monitoring # Monitoring stack
What Gets Deployed
Production Stack (docker-compose.production.yml):
- PostgreSQL 16
- C++ DBAL daemon (Conan + CMake)
- Next.js app (Node 18)
- C++ Media daemon (FFmpeg, ImageMagick)
- Redis 7
- Nginx (SSL, caching)
Monitoring Stack (docker-compose.monitoring.yml):
- Prometheus (metrics)
- Grafana (dashboards)
- Loki (logs)
- Promtail (log collector)
- Exporters (PostgreSQL, Redis, Node, Nginx)
Bootstrap Process
The bootstrap-system.sh script runs in 7 phases:
- Wait for database to be ready
- Run Prisma migrations (schema setup)
- Check bootstrap status (idempotent)
- Seed database from
/seed/database/*.yaml - Install core packages (12 packages in priority order)
- Verify installation (health checks)
- Run post-hooks (custom initialization)
Core Packages Bootstrap Order
Phase 1: package_manager (required first)
Phase 2: ui_header, ui_footer, ui_home, ui_auth, ui_login
Phase 3: dashboard
Phase 4: user_manager, role_editor
Phase 5: admin_dialog, database_manager, schema_editor
See seed/packages/core-packages.yaml for full configuration.
Environment Variables
# Database
POSTGRES_PASSWORD=changeme_prod_password
DATABASE_URL=postgresql://metabuilder:password@postgres:5432/metabuilder
# DBAL
DBAL_API_KEY=your_api_key_here
# Media Daemon
ICECAST_PASSWORD=hackme
REDIS_PASSWORD=changeme_redis_password
Docker Build Process
C++ Components (DBAL, Media daemon):
- Multi-stage builds (builder + runtime)
- Conan for dependency management
- CMake + Ninja build system
- Optimized production images (~200MB final)
Next.js:
- Node 18 Alpine base
- Production build with output standalone
- Minimal runtime dependencies
Database
Schema Overview
Core Tables:
User- User accounts, permission levelsTenant- Multi-tenant isolationInstalledPackage- Installed packages, configPackagePermission- Package access controlPageConfig- God panel route definitionsWorkflow- Workflow definitions
Example: Installed Package
model InstalledPackage {
packageId String @id
tenantId String?
version String
enabled Boolean
config String @db.Text // JSON config
installedAt BigInt?
}
Seed data (seed/database/installed_packages.yaml:44-54):
- packageId: ui_home
config: |
{
"systemPackage": true,
"defaultRoute": "/", # Maps root URL to this package
"publicAccess": true # Level 0 permission
}
DBAL (Database Abstraction Layer)
Why? Credentials never touch the frontend. All database operations go through the DBAL daemon.
Architecture:
- TypeScript SDK (
dbal/development/) - Development, fast iteration - C++ Daemon (
dbal/production/) - Production, credential protection
Usage:
import { getAdapter } from '@/lib/db/core/dbal-client'
const adapter = getAdapter()
const result = await adapter.list('InstalledPackage', {
filters: { enabled: true }
})
Database Commands
# Development (can be run from root or frontends/nextjs)
npm run db:generate # Generate Prisma client
npm run db:push # Apply schema changes
npm run db:migrate # Create migration
# From root directory
npm run db:generate # Delegates to frontends/nextjs
# From frontends/nextjs directory
npm run db:generate # Uses --schema=../../prisma/schema.prisma
# Production (via Docker)
docker exec -it metabuilder-postgres-prod psql -U metabuilder
Development
Prerequisites
# Install Node.js dependencies
cd frontends/nextjs
npm install
# Install C++ build tools (for DBAL/Media daemons)
# Ubuntu/Debian:
sudo apt install cmake ninja-build g++ libssl-dev libpq-dev
# macOS:
brew install cmake ninja conan
Development Workflow
# Terminal 1: Frontend
cd frontends/nextjs
npm run dev
# Terminal 2: DBAL daemon (optional, TypeScript version)
cd dbal/development
npm install
npm run dev
# Terminal 3: PostgreSQL (via Docker)
docker run -p 5432:5432 -e POSTGRES_PASSWORD=dev postgres:16
Code Conventions
CRITICAL RULES:
- ❌ NO hardcoded routes - use PageConfig
- ❌ NO component imports from packages - use generic renderer
- ❌ NO direct database access - use DBAL adapter
- ✅ Material-UI only for UI components
- ✅ Absolute imports with
@/path - ✅ One lambda per file pattern
Testing
# From frontends/nextjs/
npm run test:unit # Vitest unit tests
npm run test:unit -- --run # Run once (no watch)
npm run test:e2e # Playwright E2E tests
npm run lint # ESLint
npm run typecheck # TypeScript validation
npm run build # Production build
Project Structure
metabuilder/
├── frontends/nextjs/ # Next.js 14 App Router frontend
│ ├── src/
│ │ ├── app/ # App routes (minimal, generic)
│ │ │ └── page.tsx # Root route (database-driven)
│ │ ├── components/ # Generic renderers only
│ │ ├── lib/
│ │ │ ├── db/ # DBAL client
│ │ │ ├── packages/ # Package loaders
│ │ │ └── rendering/ # JSON→React renderer
│ │ └── hooks/
│ └── package.json
│
├── packages/ # Self-contained packages (JSON)
│ ├── ui_home/
│ │ ├── package.json
│ │ ├── components/ui.json
│ │ └── styles/
│ ├── ui_header/
│ ├── dashboard/
│ └── ... (12+ packages)
│
├── seed/ # Bootstrap data
│ ├── packages/
│ │ └── core-packages.yaml
│ └── database/
│ ├── installed_packages.yaml
│ └── package_permissions.yaml
│
├── dbal/ # Database Abstraction Layer
│ ├── development/ # TypeScript (dev)
│ └── production/ # C++ daemon (prod)
│ ├── src/
│ ├── include/
│ └── build-config/
│
├── services/
│ └── media_daemon/ # C++ media processing (FFmpeg, HLS)
│ ├── src/
│ ├── include/
│ └── Dockerfile
│
├── deployment/ # Docker deployment
│ ├── docker/
│ │ ├── docker-compose.production.yml
│ │ ├── docker-compose.development.yml
│ │ └── docker-compose.monitoring.yml
│ ├── scripts/
│ │ ├── deploy.sh
│ │ ├── bootstrap-system.sh
│ │ └── backup-database.sh
│ └── config/ # Service configs (Nginx, Prometheus, etc.)
│
├── prisma/
│ └── schema.prisma # Database schema
│
└── docs/ # Documentation
Key Concepts Recap
1. Ultra-Generic Routing
Traditional:
// Hardcoded route mapping
<Route path="/" component={HomePage} />
<Route path="/dashboard" component={Dashboard} />
MetaBuilder:
// Database-driven routing
const route = await db.query('PageConfig', { path: req.url })
const pkg = await loadPackage(route.packageId)
const component = pkg.components.find(c => c.id === route.component)
return renderJSONComponent(component)
2. Zero Hardcoded Imports
Traditional:
import HomePage from '@/components/HomePage'
import Dashboard from '@/components/Dashboard'
MetaBuilder:
// All components loaded from JSON
const pkg = await loadJSONPackage('/packages/ui_home')
const component = pkg.components.find(c => c.name === 'HomePage')
3. God Panel Control
Supergod users can remap the entire application without touching code:
-- Remap root page
UPDATE "InstalledPackage"
SET config = '{"defaultRoute": "/"}'
WHERE "packageId" = 'dashboard';
-- Override with god panel
INSERT INTO "PageConfig" (path, packageId, component, ...)
VALUES ('/', 'custom_package', 'CustomPage', ...);
4. Complete Loose Coupling
Database (Source of Truth)
↓
JSON Packages (Data)
↓
Generic Renderer (Engine)
↓
React (Output)
No layer knows about the layers above or below.
Trade-offs
Pros
✅ Infinite flexibility - remap anything via database ✅ Zero coupling - no hardcoded dependencies ✅ God users control everything - no code deploys needed ✅ Package isolation - packages don't know about each other ✅ Multi-tenant ready - tenant-specific route overrides
Cons
❌ Steep learning curve - "where does this render?" ❌ Harder debugging - trace through database → JSON → renderer ❌ Performance overhead - runtime JSON parsing ❌ Type safety loss - JSON components aren't type-checked ❌ Tooling challenges - no IDE autocomplete for package components
The trade-off is flexibility vs. simplicity.
Troubleshooting
Common Issues
| Problem | Solution |
|---|---|
"Page not found" at / |
Check InstalledPackage has defaultRoute: "/" config |
| Package not rendering | Verify package installed: SELECT * FROM "InstalledPackage" |
| Permission denied | Check PackagePermission table for correct role |
| Database connection failed | Check DATABASE_URL env var, run npm run db:push |
| DBAL daemon won't start | Check PostgreSQL is running, credentials correct |
Debug Steps
# 1. Check database
docker exec -it metabuilder-postgres-prod psql -U metabuilder -c "SELECT * FROM \"InstalledPackage\" WHERE \"packageId\" = 'ui_home';"
# 2. Check package files exist
ls -la packages/ui_home/components/
# 3. Check bootstrap logs
docker logs metabuilder-app-prod | grep bootstrap
# 4. View all routes
curl http://localhost:8080/api/list?entity=PageConfig
License
MIT License - See LICENSE file
Learn More
- Deployment Guide - Detailed deployment documentation
- Package Structure - How to create packages
- DBAL Documentation - Database abstraction layer
- Prisma Schema - Database schema reference
Built with the philosophy: Everything is data, nothing is hardcoded.