mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
15 KiB
15 KiB
JSON Orchestration System - Complete Guide
Overview
The JSON Orchestration System allows you to define entire pages, components, data sources, and actions using JSON schemas. This enables:
- Zero-code page creation: Build pages without writing React components
- Dynamic configuration: Change page structure without rebuilding
- Type safety: Full TypeScript validation of schemas
- Testability: JSON schemas are easy to validate and test
- Rapid prototyping: Create new pages by editing JSON files
Architecture
┌─────────────────────────────────────────────────┐
│ JSON Page Schema │
│ (Structure, Data Sources, Actions, Components) │
└───────────────┬─────────────────────────────────┘
│
┌───────▼──────┐
│ PageRenderer │
└───────┬──────┘
│
┌───────────┼───────────┐
│ │ │
┌───▼────┐ ┌───▼─────┐ ┌──▼────────┐
│ Data │ │ Action │ │ Component │
│ Source │ │ Executor│ │ Registry │
│ Manager│ │ │ │ │
└────────┘ └─────────┘ └───────────┘
Core Concepts
1. Page Schema
A complete page definition:
interface PageSchema {
id: string // Unique identifier
name: string // Display name
description?: string // Optional description
icon?: string // Icon name
layout: Layout // Layout configuration
components: ComponentDef[] // Component tree
dataSources?: DataSource[] // Data sources
actions?: Action[] // Available actions
hooks?: HookConfig[] // Custom hooks
seedData?: Record<string, any> // Initial data
}
2. Data Sources
Define where data comes from and how it's managed:
interface DataSource {
id: string // Unique ID for referencing
type: 'kv' | 'api' | 'computed' | 'static'
key?: string // KV store key
endpoint?: string // API endpoint
transform?: string // Transform function name
defaultValue?: any // Default/fallback value
dependencies?: string[] // Other data sources needed
}
Example:
{
"dataSources": [
{
"id": "todos",
"type": "kv",
"key": "user-todos",
"defaultValue": []
},
{
"id": "user",
"type": "api",
"endpoint": "/api/user",
"defaultValue": null
},
{
"id": "stats",
"type": "computed",
"dependencies": ["todos"],
"transform": "calculateStats"
}
]
}
3. Actions
Define what happens when users interact:
interface Action {
id: string // Action identifier
type: 'create' | 'update' | 'delete' | 'navigate'
| 'api' | 'transform' | 'custom'
target?: string // Target data source
payload?: Record<string, any> // Action payload
handler?: string // Custom handler name
}
Action Types:
create: Add new item to data sourceupdate: Modify existing itemdelete: Remove itemnavigate: Change route/tabapi: Make HTTP requesttransform: Transform data using custom functioncustom: Execute custom handler
Example:
{
"actions": [
{
"id": "add-todo",
"type": "create",
"target": "todos"
},
{
"id": "complete-todo",
"type": "update",
"target": "todos"
},
{
"id": "delete-todo",
"type": "delete",
"target": "todos"
},
{
"id": "refresh-data",
"type": "api",
"target": "todos",
"payload": {
"endpoint": "/api/todos",
"method": "GET"
}
},
{
"id": "export",
"type": "custom",
"handler": "handleExport"
}
]
}
4. Components
Define the component tree structure:
interface ComponentDef {
id: string // Unique component ID
type: string // Component name (from registry)
props?: Record<string, any> // Component props
children?: ComponentDef[] // Child components
dataBinding?: string // Bind to data source
eventHandlers?: Record<string, string> // Event → Action mapping
}
Example:
{
"components": [
{
"id": "todo-list",
"type": "Card",
"props": {
"className": "max-w-2xl mx-auto"
},
"children": [
{
"id": "header",
"type": "CardHeader",
"children": [
{
"id": "title",
"type": "CardTitle",
"props": {
"children": "My Todos"
}
}
]
},
{
"id": "content",
"type": "CardContent",
"dataBinding": "todos",
"children": [
{
"id": "add-button",
"type": "Button",
"props": {
"children": "Add Todo"
},
"eventHandlers": {
"onClick": "add-todo"
}
}
]
}
]
}
]
}
5. Layout
Define how components are arranged:
interface Layout {
type: 'single' | 'split' | 'grid' | 'tabs' | 'flex'
direction?: 'horizontal' | 'vertical' | 'row' | 'column'
panels?: PanelConfig[]
}
interface PanelConfig {
id: string
minSize?: number
maxSize?: number
defaultSize?: number
}
Example:
{
"layout": {
"type": "split",
"direction": "horizontal",
"panels": [
{
"id": "sidebar",
"minSize": 15,
"maxSize": 30,
"defaultSize": 20
},
{
"id": "main",
"defaultSize": 80
}
]
}
}
Complete Examples
Example 1: Simple Todo List Page
{
"id": "todo-list",
"name": "Todo List",
"description": "Simple todo list application",
"icon": "CheckSquare",
"layout": {
"type": "single"
},
"dataSources": [
{
"id": "todos",
"type": "kv",
"key": "user-todos",
"defaultValue": []
}
],
"components": [
{
"id": "root",
"type": "Card",
"props": {
"className": "max-w-2xl mx-auto mt-8"
},
"children": [
{
"id": "header",
"type": "CardHeader",
"children": [
{
"id": "title",
"type": "CardTitle",
"props": {
"children": "My Tasks"
}
}
]
},
{
"id": "content",
"type": "CardContent",
"props": {
"className": "space-y-4"
},
"children": [
{
"id": "input",
"type": "Input",
"props": {
"placeholder": "What needs to be done?",
"id": "todo-input"
},
"eventHandlers": {
"onKeyDown": "add-on-enter"
}
},
{
"id": "add-button",
"type": "Button",
"props": {
"children": "Add Task",
"className": "w-full"
},
"eventHandlers": {
"onClick": "add-todo"
}
}
]
}
]
}
],
"actions": [
{
"id": "add-todo",
"type": "create",
"target": "todos"
},
{
"id": "toggle-todo",
"type": "update",
"target": "todos"
},
{
"id": "delete-todo",
"type": "delete",
"target": "todos"
},
{
"id": "add-on-enter",
"type": "custom",
"handler": "handleEnterKey"
}
],
"seedData": {
"todos": [
{
"id": "1",
"text": "Welcome to JSON-driven pages!",
"completed": false
}
]
}
}
Example 2: Dashboard with Multiple Data Sources
{
"id": "dashboard",
"name": "Project Dashboard",
"description": "Overview of project metrics",
"icon": "ChartBar",
"layout": {
"type": "grid"
},
"dataSources": [
{
"id": "files",
"type": "kv",
"key": "project-files",
"defaultValue": []
},
{
"id": "models",
"type": "kv",
"key": "project-models",
"defaultValue": []
},
{
"id": "components",
"type": "kv",
"key": "project-components",
"defaultValue": []
},
{
"id": "stats",
"type": "computed",
"dependencies": ["files", "models", "components"],
"transform": "calculateProjectStats"
}
],
"components": [
{
"id": "stats-grid",
"type": "div",
"props": {
"className": "grid grid-cols-3 gap-4 p-4"
},
"children": [
{
"id": "files-card",
"type": "Card",
"children": [
{
"id": "files-content",
"type": "CardContent",
"props": {
"className": "pt-6"
},
"dataBinding": "files",
"children": [
{
"id": "files-count",
"type": "div",
"props": {
"className": "text-2xl font-bold"
}
},
{
"id": "files-label",
"type": "div",
"props": {
"children": "Files",
"className": "text-muted-foreground"
}
}
]
}
]
},
{
"id": "models-card",
"type": "Card",
"children": [
{
"id": "models-content",
"type": "CardContent",
"props": {
"className": "pt-6"
},
"dataBinding": "models"
}
]
},
{
"id": "components-card",
"type": "Card",
"children": [
{
"id": "components-content",
"type": "CardContent",
"props": {
"className": "pt-6"
},
"dataBinding": "components"
}
]
}
]
}
],
"actions": [
{
"id": "navigate-to-files",
"type": "navigate",
"target": "code"
},
{
"id": "navigate-to-models",
"type": "navigate",
"target": "models"
}
]
}
Using the PageRenderer
In your application:
import { PageRenderer } from '@/config/orchestration'
import dashboardSchema from '@/config/pages/dashboard.json'
function DashboardPage() {
const handleNavigate = (path: string) => {
// Your navigation logic
router.push(path)
}
const customHandlers = {
handleExport: async () => {
// Custom export logic
const data = await exportData()
downloadFile(data)
},
calculateStats: (todos: any[]) => {
return {
total: todos.length,
completed: todos.filter(t => t.completed).length
}
}
}
return (
<PageRenderer
schema={dashboardSchema}
onNavigate={handleNavigate}
customHandlers={customHandlers}
/>
)
}
Component Registry
Register all components that can be used in JSON schemas:
// src/config/orchestration/component-registry.ts
import { ComponentType } from 'react'
import { Button } from '@/components/ui/button'
import { MyCustomComponent } from '@/components/MyCustomComponent'
export const ComponentRegistry: Record<string, ComponentType<any>> = {
Button,
Input,
Card,
// ... shadcn components
// Custom components
MyCustomComponent,
TodoList,
Dashboard,
}
Advanced Patterns
Conditional Rendering
Use computed data sources:
{
"dataSources": [
{
"id": "user",
"type": "kv",
"key": "current-user"
},
{
"id": "isAdmin",
"type": "computed",
"dependencies": ["user"],
"transform": "checkIsAdmin"
}
]
}
Nested Data Binding
Bind to nested properties:
{
"id": "email-input",
"type": "Input",
"dataBinding": "user.profile.email"
}
Batch Actions
Execute multiple actions:
{
"id": "save-all",
"type": "custom",
"handler": "saveAllChanges",
"payload": {
"actions": ["save-files", "save-models", "save-components"]
}
}
Best Practices
- Keep schemas focused: One page = one schema
- Use meaningful IDs: Makes debugging easier
- Leverage seed data: For examples and testing
- Document custom handlers: In the schema description
- Validate schemas: Use Zod validation before runtime
- Version your schemas: Track changes over time
- Test with different data: Ensure schemas work with various inputs
Migration Strategy
To migrate existing components to JSON:
- Identify static structure: What doesn't change?
- Extract data dependencies: What data does it need?
- Map actions: What can users do?
- Create JSON schema: Follow the structure
- Test with PageRenderer: Verify functionality
- Add custom handlers: For complex logic
- Remove old component: Once verified
Performance Considerations
- JSON schemas are parsed once
- Component registry lookups are O(1)
- Data sources use React hooks (memoized)
- Actions are executed asynchronously
- Large schemas can be code-split
Debugging
Enable debug mode:
<PageRenderer
schema={schema}
debug={true} // Logs all actions and data changes
/>
TypeScript Support
All schemas are fully typed:
import { PageSchema } from '@/config/orchestration'
const mySchema: PageSchema = {
// Full type checking and autocomplete
}
Testing
Test schemas independently:
import { PageSchemaDefinition } from '@/config/orchestration/schema'
import dashboardSchema from '@/config/pages/dashboard.json'
test('dashboard schema is valid', () => {
const result = PageSchemaDefinition.safeParse(dashboardSchema)
expect(result.success).toBe(true)
})
Future Enhancements
- Visual schema editor
- Schema hot-reloading
- A/B testing support
- Schema versioning
- Analytics integration
- Performance profiling