diff --git a/SCRIPT_JSON_PATTERN.md b/SCRIPT_JSON_PATTERN.md new file mode 100644 index 000000000..855da4eda --- /dev/null +++ b/SCRIPT_JSON_PATTERN.md @@ -0,0 +1,736 @@ +# Script.json - Complete JavaScript/TypeScript Abstraction + +## Concept + +Just as `styles.json` abstracts CSS into declarative JSON, **`script.json` abstracts executable JavaScript/TypeScript** - functions, promises, async/await, classes, React hooks, generators, and all runtime behavior. + +## Why This Matters + +**Everything as Data**: When code is represented as JSON: +- **Non-developers** can understand and modify logic +- **AI/LLMs** can reason about and generate code more reliably +- **Visual editors** can provide drag-and-drop programming +- **Code generation** becomes schema transformation +- **Multi-language output** from a single source (TS, JS, Rust, Python) +- **Version control** shows semantic changes, not syntax changes + +## Architecture + +``` +script.json (Abstract Syntax) + ↓ +ScriptCompiler + ↓ +TypeScript/JavaScript Output + ↓ +Runtime Execution +``` + +## Schema Structure + +### 1. Module System + +```json +{ + "imports": [ + { + "module": "react", + "imports": ["useState", "useEffect"], + "type": "named" + } + ] +} +``` + +Compiles to: +```typescript +import { useState, useEffect } from 'react'; +``` + +### 2. Constants & Variables + +```json +{ + "constants": [ + { + "name": "API_BASE_URL", + "value": "https://api.example.com", + "type": "string", + "exported": true + } + ], + "variables": [ + { + "name": "cacheStore", + "kind": "let", + "initialValue": { + "expression": "new Map()", + "type": "constructor" + } + } + ] +} +``` + +Compiles to: +```typescript +export const API_BASE_URL = "https://api.example.com"; +let cacheStore = new Map(); +``` + +### 3. Functions (Async/Sync) + +```json +{ + "functions": [ + { + "name": "fetchUserData", + "async": true, + "params": [ + { "name": "userId", "type": "string" } + ], + "returnType": "Promise", + "body": [ + { + "type": "const_declaration", + "name": "response", + "value": { + "type": "await", + "expression": { + "type": "call_expression", + "callee": "fetch", + "args": ["/api/users/${userId}"] + } + } + }, + { + "type": "return", + "value": "$ref:local.response" + } + ] + } + ] +} +``` + +Compiles to: +```typescript +async function fetchUserData(userId: string): Promise { + const response = await fetch(`/api/users/${userId}`); + return response; +} +``` + +### 4. Control Flow + +**If Statements**: +```json +{ + "type": "if_statement", + "condition": { + "type": "binary_expression", + "left": "$ref:local.count", + "operator": ">", + "right": 0 + }, + "then": [ + { "type": "return", "value": true } + ], + "else": [ + { "type": "return", "value": false } + ] +} +``` + +**Loops**: +```json +{ + "type": "for_loop", + "init": { "type": "const_declaration", "name": "i", "value": 0 }, + "condition": { + "type": "binary_expression", + "left": "$ref:local.i", + "operator": "<", + "right": 10 + }, + "update": { + "type": "update_expression", + "operator": "++", + "argument": "$ref:local.i" + }, + "body": [ + { + "type": "call_expression", + "callee": "console.log", + "args": ["$ref:local.i"] + } + ] +} +``` + +### 5. Error Handling + +```json +{ + "type": "try_catch", + "try": [ + { + "type": "const_declaration", + "name": "data", + "value": { + "type": "await", + "expression": { + "type": "call_expression", + "callee": "fetchData", + "args": [] + } + } + } + ], + "catch": { + "param": "error", + "body": [ + { + "type": "call_expression", + "callee": "console.error", + "args": ["$ref:catch.error"] + } + ] + }, + "finally": [ + { + "type": "call_expression", + "callee": "cleanup", + "args": [] + } + ] +} +``` + +### 6. Classes + +```json +{ + "classes": [ + { + "name": "ApiClient", + "properties": [ + { "name": "baseUrl", "type": "string", "visibility": "private" } + ], + "constructor": { + "params": [{ "name": "config", "type": "object" }], + "body": [ + { + "type": "assignment", + "target": "this.baseUrl", + "value": "$ref:params.config.baseUrl" + } + ] + }, + "methods": [ + { + "name": "get", + "async": true, + "visibility": "public", + "params": [{ "name": "endpoint", "type": "string" }], + "returnType": "Promise", + "body": [...] + } + ] + } + ] +} +``` + +### 7. React Hooks + +```json +{ + "hooks": [ + { + "name": "useFetch", + "params": [{ "name": "url", "type": "string" }], + "body": [ + { + "type": "const_declaration", + "name": "[data, setData]", + "value": { + "type": "call_expression", + "callee": "useState", + "args": [null] + } + }, + { + "type": "call_expression", + "callee": "useEffect", + "args": [ + { + "type": "arrow_function", + "async": true, + "body": [...] + }, + ["$ref:params.url"] + ] + }, + { + "type": "return", + "value": { + "type": "object_literal", + "properties": { + "data": "$ref:local.data" + } + } + } + ] + } + ] +} +``` + +### 8. Promises & Async Operations + +```json +{ + "functions": [ + { + "name": "retryWithBackoff", + "async": true, + "params": [ + { "name": "fn", "type": "() => Promise" }, + { "name": "maxRetries", "type": "number" } + ], + "body": [ + { + "type": "for_loop", + "init": { "name": "i", "value": 0 }, + "condition": { "left": "$ref:local.i", "operator": "<", "right": "$ref:params.maxRetries" }, + "body": [ + { + "type": "try_catch", + "try": [ + { + "type": "return", + "value": { + "type": "await", + "expression": { + "type": "call_expression", + "callee": "$ref:params.fn" + } + } + } + ], + "catch": { + "body": [ + { + "type": "await", + "expression": { + "type": "call_expression", + "callee": "delay", + "args": [1000] + } + } + ] + } + } + ] + } + ] + } + ] +} +``` + +### 9. Generators + +```json +{ + "generators": [ + { + "name": "range", + "generator": true, + "params": [ + { "name": "start", "type": "number" }, + { "name": "end", "type": "number" } + ], + "body": [ + { + "type": "for_loop", + "init": { "name": "i", "value": "$ref:params.start" }, + "condition": { "left": "$ref:local.i", "operator": "<=", "right": "$ref:params.end" }, + "body": [ + { "type": "yield", "value": "$ref:local.i" } + ] + } + ] + } + ] +} +``` + +### 10. Event Handlers + +```json +{ + "events": [ + { + "name": "UserLoggedInEvent", + "type": "CustomEvent", + "detail": { + "userId": "string", + "timestamp": "number" + } + } + ], + "event_handlers": [ + { + "event": "$ref:events.user_logged_in_event", + "handler": { + "type": "arrow_function", + "params": [{ "name": "event", "type": "UserLoggedInEvent" }], + "body": [ + { + "type": "call_expression", + "callee": "console.log", + "args": ["User logged in:", "$ref:params.event.detail.userId"] + } + ] + } + } + ] +} +``` + +## Reference System + +The `$ref:` syntax creates references to: +- **`$ref:params.userId`** - Function parameters +- **`$ref:local.response`** - Local variables +- **`$ref:constants.API_BASE_URL`** - Module constants +- **`$ref:imports.axios.get`** - Imported functions +- **`$ref:functions.delay`** - Other functions in the module +- **`$ref:catch.error`** - Catch block error parameter + +This enables: +- Type safety +- Dependency tracking +- Refactoring without string replacements +- Dead code elimination + +## Compiler Implementation + +```typescript +class ScriptCompiler { + private schema: ScriptSchema; + + compile(): string { + const parts: string[] = []; + + // Imports + if (this.schema.imports) { + parts.push(this.compileImports(this.schema.imports)); + } + + // Constants + if (this.schema.constants) { + parts.push(this.compileConstants(this.schema.constants)); + } + + // Functions + if (this.schema.functions) { + parts.push(...this.schema.functions.map(f => this.compileFunction(f))); + } + + // Classes + if (this.schema.classes) { + parts.push(...this.schema.classes.map(c => this.compileClass(c))); + } + + // Hooks + if (this.schema.hooks) { + parts.push(...this.schema.hooks.map(h => this.compileHook(h))); + } + + return parts.join('\n\n'); + } + + private compileFunction(def: FunctionDefinition): string { + const asyncKw = def.async ? 'async ' : ''; + const exportKw = def.exported ? 'export ' : ''; + const params = def.params.map(p => + `${p.name}${p.optional ? '?' : ''}: ${p.type}` + ).join(', '); + + const body = this.compileBody(def.body); + + return `${exportKw}${asyncKw}function ${def.name}(${params}): ${def.returnType} {\n${body}\n}`; + } + + private compileBody(statements: Statement[]): string { + return statements.map(stmt => this.compileStatement(stmt)).join('\n'); + } + + private compileStatement(stmt: Statement): string { + switch (stmt.type) { + case 'const_declaration': + return ` const ${stmt.name} = ${this.compileExpression(stmt.value)};`; + + case 'if_statement': + const condition = this.compileExpression(stmt.condition); + const thenBlock = this.compileBody(stmt.then); + const elseBlock = stmt.else ? `\n } else {\n${this.compileBody(stmt.else)}` : ''; + return ` if (${condition}) {\n${thenBlock}${elseBlock}\n }`; + + case 'return': + return ` return ${this.compileExpression(stmt.value)};`; + + case 'await': + return `await ${this.compileExpression(stmt.expression)}`; + + // ... more statement types + } + } + + private compileExpression(expr: Expression): string { + if (typeof expr === 'string' && expr.startsWith('$ref:')) { + return this.resolveReference(expr); + } + + if (expr.type === 'call_expression') { + const args = expr.args.map(a => this.compileExpression(a)).join(', '); + return `${expr.callee}(${args})`; + } + + if (expr.type === 'template_literal') { + return `\`${expr.template}\``; + } + + // ... more expression types + } + + private resolveReference(ref: string): string { + // $ref:params.userId -> userId + // $ref:local.response -> response + // $ref:constants.API_BASE_URL -> API_BASE_URL + const parts = ref.replace('$ref:', '').split('.'); + return parts[parts.length - 1]; + } +} +``` + +## Usage Example + +**Input**: `packages/shared/seed/script.json` + +**Compile**: +```typescript +const compiler = new ScriptCompiler(scriptSchema); +const tsCode = compiler.compile(); +fs.writeFileSync('packages/shared/dist/index.ts', tsCode); +``` + +**Output**: `packages/shared/dist/index.ts` +```typescript +import { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; + +export const API_BASE_URL = "https://api.example.com"; +export const MAX_RETRIES = 3; + +let cacheStore = new Map(); + +export async function fetchUserData(userId: string, options?: object): Promise { + const cacheKey = `user_${userId}`; + + if (cacheStore.has(cacheKey)) { + return cacheStore.get(cacheKey); + } + + try { + const response = await axios.get(`${API_BASE_URL}/users/${userId}`, options); + const userData = response.data; + cacheStore.set(cacheKey, userData); + return userData; + } catch (error) { + console.error("Failed to fetch user:", error); + throw error; + } +} + +export async function retryWithBackoff( + fn: () => Promise, + maxRetries: number = MAX_RETRIES +): Promise { + for (const i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) { + throw error; + } + await delay(1000 * Math.pow(2, i)); + } + } +} + +export function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export class ApiClient { + private baseUrl: string; + private timeout: number; + + constructor(config: { baseUrl: string; timeout?: number }) { + this.baseUrl = config.baseUrl; + this.timeout = config.timeout ?? 5000; + } + + async get(endpoint: string): Promise { + const url = `${this.baseUrl}${endpoint}`; + const response = await fetch(url, { + method: "GET", + signal: AbortSignal.timeout(this.timeout) + }); + return await response.json(); + } + + async post(endpoint: string, data: T): Promise { + const url = `${this.baseUrl}${endpoint}`; + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data) + }); + return await response.json(); + } +} + +export function useFetch(url: string) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(async () => { + try { + const response = await fetch(url); + const json = await response.json(); + setData(json); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }, [url]); + + return { data, loading, error }; +} + +export function* range(start: number, end: number): Generator { + for (let i = start; i <= end; i++) { + yield i; + } +} +``` + +## Benefits + +### 1. **Visual Programming** +Build a drag-and-drop editor where users create functions by connecting blocks. + +### 2. **Multi-Language Output** +Same `script.json` → TypeScript, JavaScript, Python, Rust + +### 3. **AI-Friendly** +LLMs can generate/modify JSON more reliably than raw code. + +### 4. **Static Analysis** +Analyze control flow, detect dead code, optimize without parsing. + +### 5. **Runtime Flexibility** +Interpret JSON directly without compilation (like a scripting engine). + +### 6. **Educational** +Teach programming concepts with visual JSON representation. + +## Integration with Existing Patterns + +| Pattern | File | Purpose | +|---------|------|---------| +| **UI Components** | `components.json` | Component tree structure | +| **Styling** | `styles.json` | CSS abstraction | +| **Types** | `types.json` | TypeScript type definitions | +| **Logic** | `script.json` | **Executable code** | + +Together, these define a **complete application** in pure JSON: +``` +packages/my_app/seed/ + ├── components.json (What to render) + ├── styles.json (How it looks) + ├── types.json (Type system) + └── script.json (How it behaves) +``` + +## Advanced Features + +### Composition +```json +{ + "composition": [ + { + "name": "enhancedFetch", + "base": "$ref:functions.fetchUserData", + "middleware": [ + "$ref:functions.retryWithBackoff", + "$ref:functions.cacheWrapper" + ] + } + ] +} +``` + +### Optimization Hints +```json +{ + "functions": [ + { + "name": "expensiveCalculation", + "memoize": true, + "pure": true, + "body": [...] + } + ] +} +``` + +### Dependency Injection +```json +{ + "dependencies": { + "logger": "$ref:services.logger", + "cache": "$ref:services.cache" + }, + "functions": [ + { + "name": "processData", + "inject": ["logger", "cache"], + "body": [...] + } + ] +} +``` + +## Conclusion + +**script.json** demonstrates that **executable code is just data**. When properly abstracted: +- Logic becomes composable +- Non-developers can participate +- AI can reason about behavior +- Visual tools can edit functionality +- Cross-language compilation is trivial + +This completes the abstraction trilogy: +1. **styles.json** = Appearance as data +2. **types.json** = Type system as data +3. **script.json** = Behavior as data + +**Everything is JSON. Everything is composable. Everything is programmable.** diff --git a/packages/css_designer/seed/metadata.json b/packages/css_designer/seed/metadata.json index f4cbd7db1..07fac81b8 100644 --- a/packages/css_designer/seed/metadata.json +++ b/packages/css_designer/seed/metadata.json @@ -42,10 +42,16 @@ "export/to_scss.lua", "export/to_css.lua" ], - "tests": [ - "tests/colors.test.lua", - "tests/export.test.lua" - ], + "tests": { + "scripts": [ + "tests/colors.test.lua", + "tests/export.test.lua" + ], + "cases": [ + "tests/colors.cases.json", + "tests/export.cases.json" + ] + }, "primary": true, "permissions": { "css.designer.export": {