TinyEngine DSL Generator
Generate conformant DSL (JSON) for the TinyEngine low-code platform: pages, blocks, and apps. This file is a router — load the reference files on demand for detail instead of reading everything up front.
Quick Reference
| Task |
Do |
| Generate page DSL |
Describe components, layout, interactions → §Workflow |
| Generate block DSL |
Describe reusable functionality + configurable props |
| Generate app DSL |
Describe multi-page structure + shared componentsMap |
| From screenshot |
Describe layout → map to components (§Design-to-DSL) |
| Lookup a component |
node scripts/query_components.mjs props <Name> |
| Validate output |
bash scripts/validate_all.sh <file> (required) |
Workflow
Understand the goal — Page (components / state / methods / lifeCycles), Block (reusable, exposes a props schema), or App (pages + componentsMap + meta).
Gather requirements — name / route / title; component hierarchy; state; event handlers; data sources. Blocks additionally: exposed props, emitted events. Apps additionally: all pages, shared componentsMap.
Load only the reference you need:
| Need |
File |
| Schema structure, TS interfaces, reserved names, prop types |
protocol.md |
| Component props/events, or a component not listed here |
components.md · query_components.mjs |
| List page / form page / layout / interaction templates |
patterns.md |
⚠️ Before generating any page with interactions, read the event-binding section of protocol.md. Event handlers are the #1 error source; the compact Critical Rules table below is a reminder, not a substitute for the full ❌/✅ example.
Generate — follow the Page skeleton + property types below. Full Page/Block/Component interfaces are in protocol.md.
Validate (required) — see §Validate.
Run the checklist before handing off — see §Pre-Generation Checklist.
Page skeleton (anchor)
{
"componentName": "Page",
"fileName": "PageName",
"meta": { "id": 1, "title": "...", "router": "...", "creator": "...", "isHome": false, "parentId": "0", "rootElement": "div", "group": "staticPages" },
"state": {},
"methods": {},
"lifeCycles": {},
"children": []
}
Property value types
- Literal:
"text", 123, true
- JSExpression:
{"type":"JSExpression","value":"this.state.count"} — bindings, conditions, event handlers
- JSFunction:
{"type":"JSFunction","value":"function(){}"} — only inside methods / lifeCycles
- i18n:
{"type":"i18n","key":"app.title"}
- JSResource:
{"type":"JSResource","value":"this.utils.format()"}
Referencing a block
{ "componentName": "BlockFileName", "componentType": "block", "id": "block-001", "props": { "title": "value" } }
Inside the block: read this.props.xxx, emit via this.emit('eventName', data).
Critical Rules (common pitfalls)
These cause silent failures. Full ❌/✅ JSON examples live in protocol.md; the checklist below enforces them.
| Rule |
❌ Wrong |
✅ Right |
| Event bindings |
"onClick":{"type":"JSFunction",...}; or JSExpression.value = "function…" |
"onClick":{"type":"JSExpression","value":"this.handleX"} — put the body in methods as JSFunction |
| Method params |
function(filter){...} |
function(event, filter){...}; binding "params":["'all'"] → call handleX(event,'all') |
| Lifecycle name |
"mounted":{...} |
"onMounted":{"type":"JSFunction","value":"function onMounted(){...}"} |
| Two-way binding |
modelValue with no model |
"model":true (v-model) or "model":{"prop":"x"} (v-model:x) |
| Page editable |
"occupier": {...} |
"occupier": null |
| CSS class |
props.class |
props.className |
Event bindings — the full pattern (highest-frequency error)
The function body lives in methods (JSFunction); the event only references it (JSExpression). event is always the first arg; params append after.
"methods": {
"handleDelete": {
"type": "JSFunction",
"value": "function(event, id) { this.state.list = this.state.list.filter(x => x.id !== id); }"
}
},
"children": [{
"componentName": "TinyButton",
"props": {
"text": "删除",
"onClick": { "type": "JSExpression", "value": "this.handleDelete", "params": ["123"] }
}
}]
The binding above calls handleDelete(event, 123). ❌ Never put a JSFunction on an event, and never put a function(){} body inside a JSExpression.value — both silently break the handler.
Memory aid: JSExpression = reference (this.fn) · JSFunction = definition (function(){}). Events use references; methods use definitions.
Validate (required)
bash .agents/skills/tinyengine-dsl-generator/scripts/validate_all.sh <output-file>
validate_all.sh chains three checks — do not rely on validate_dsl.mjs alone (it misses event-binding errors). Fix and re-run until all three pass; never hand off unvalidated output.
| Stage |
Script |
Catches |
| Structure |
validate_dsl.mjs |
Required fields, Page/Block componentName, meta, class vs className, app/page id types |
| Event bindings |
check_event_bindings.mjs |
JSFunction on an event, or a function body in JSExpression.value |
| CSS |
check_css.mjs |
Malformed css strings |
Pre-Generation Checklist
Component lookup
Don't load bundle.json (≈1 MB) by hand. Query it:
node scripts/query_components.mjs list # all components
node scripts/query_components.mjs props TinyButton # one component's props (fuzzy match)
node scripts/query_components.mjs cat 表单 # components in a category
node scripts/query_components.mjs search 表格 # full-text search
File output
- Apps →
mockServer/data/apps/<app-name>.json
- Pages →
mockServer/data/pages/<PageName>.json
- Blocks →
mockServer/data/blocks/<BlockName>.json
Pages and blocks are saved with an outer wrapper around the DSL: page files wrap the Page DSL in page_content (plus name, id, app, route, tenant, parentId, group, isPage, isHome); block files wrap the Block DSL in content (plus id, label, framework, path, public, is_published). The validators auto-unwrap both, so you can validate either the wrapper or the inner DSL directly.
Design-to-DSL
From a description or screenshot: identify layout regions → map visuals to components → extract interactions → define state + handlers → apply className / style.
Troubleshooting
| Problem |
Check |
| Input not working |
modelValue declares model (true for standard v-model) |
| Event not firing |
JSExpression (not JSFunction); method exists in methods |
| Page not editable |
occupier is null |
| Wrong params |
first param is always event; params append after |
Resources
- protocol.md — schema spec, TS interfaces, reserved names, property types, slots, full ❌/✅ examples
- components.md — component catalog (props/events); supplement with
query_components.mjs
- patterns.md — list/form page, layout, interaction templates
scripts/ — validate_all.sh (run this), validate_dsl.mjs, check_event_bindings.mjs, check_css.mjs, validate_page.mjs, query_components.mjs
1---2name: tinyengine-dsl-generator3description: Use when creating or modifying TinyEngine low-code applications - generating page, block, or app DSL (JSON schemas), converting designs/screenshots to DSL, or debugging generated TinyEngine JSON.4---56# TinyEngine DSL Generator78Generate conformant DSL (JSON) for the TinyEngine low-code platform: **pages**, **blocks**, and **apps**. This file is a router — load the reference files on demand for detail instead of reading everything up front.910## Quick Reference1112| Task | Do |13| ------------------ | ----------------------------------------------------- |14| Generate page DSL | Describe components, layout, interactions → §Workflow |15| Generate block DSL | Describe reusable functionality + configurable props |16| Generate app DSL | Describe multi-page structure + shared componentsMap |17| From screenshot | Describe layout → map to components (§Design-to-DSL) |18| Lookup a component | `node scripts/query_components.mjs props <Name>` |19| Validate output | `bash scripts/validate_all.sh <file>` (required) |2021## Workflow22231. **Understand the goal** — Page (components / state / methods / lifeCycles), Block (reusable, exposes a props `schema`), or App (pages + `componentsMap` + `meta`).242. **Gather requirements** — name / route / title; component hierarchy; state; event handlers; data sources. Blocks additionally: exposed props, emitted events. Apps additionally: all pages, shared `componentsMap`.253. **Load only the reference you need:**2627 | Need | File |28 | ----------------------------------------------------------- | ---------------------------------------------------------- |29 | Schema structure, TS interfaces, reserved names, prop types | [protocol.md](references/protocol.md) |30 | Component props/events, or a component not listed here | [components.md](references/components.md) · `query_components.mjs` |31 | List page / form page / layout / interaction templates | [patterns.md](references/patterns.md) |3233 ⚠️ **Before generating any page with interactions**, read the event-binding section of [protocol.md](references/protocol.md). Event handlers are the #1 error source; the compact Critical Rules table below is a reminder, not a substitute for the full ❌/✅ example.34354. **Generate** — follow the Page skeleton + property types below. Full Page/Block/Component interfaces are in protocol.md.365. **Validate** (required) — see §Validate.376. **Run the checklist** before handing off — see §Pre-Generation Checklist.3839### Page skeleton (anchor)4041```json42{43 "componentName": "Page",44 "fileName": "PageName",45 "meta": { "id": 1, "title": "...", "router": "...", "creator": "...", "isHome": false, "parentId": "0", "rootElement": "div", "group": "staticPages" },46 "state": {},47 "methods": {},48 "lifeCycles": {},49 "children": []50}51```5253### Property value types5455- **Literal**: `"text"`, `123`, `true`56- **JSExpression**: `{"type":"JSExpression","value":"this.state.count"}` — bindings, conditions, **event handlers**57- **JSFunction**: `{"type":"JSFunction","value":"function(){}"}` — **only** inside `methods` / `lifeCycles`58- **i18n**: `{"type":"i18n","key":"app.title"}`59- **JSResource**: `{"type":"JSResource","value":"this.utils.format()"}`6061### Referencing a block6263```json64{ "componentName": "BlockFileName", "componentType": "block", "id": "block-001", "props": { "title": "value" } }65```6667Inside the block: read `this.props.xxx`, emit via `this.emit('eventName', data)`.6869## Critical Rules (common pitfalls)7071These cause silent failures. Full ❌/✅ JSON examples live in [protocol.md](references/protocol.md); the checklist below enforces them.7273| Rule | ❌ Wrong | ✅ Right |74| ------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |75| **Event bindings** | `"onClick":{"type":"JSFunction",...}`; or `JSExpression.value` = `"function…"` | `"onClick":{"type":"JSExpression","value":"this.handleX"}` — put the body in `methods` as `JSFunction` |76| **Method params** | `function(filter){...}` | `function(event, filter){...}`; binding `"params":["'all'"]` → call `handleX(event,'all')` |77| **Lifecycle name** | `"mounted":{...}` | `"onMounted":{"type":"JSFunction","value":"function onMounted(){...}"}` |78| **Two-way binding**| `modelValue` with **no** `model` | `"model":true` (v-model) or `"model":{"prop":"x"}` (v-model:x) |79| **Page editable** | `"occupier": {...}` | `"occupier": null` |80| **CSS class** | `props.class` | `props.className` |8182### Event bindings — the full pattern (highest-frequency error)8384The function body lives in `methods` (`JSFunction`); the event only **references** it (`JSExpression`). `event` is always the first arg; `params` append after.8586```json87"methods": {88 "handleDelete": {89 "type": "JSFunction",90 "value": "function(event, id) { this.state.list = this.state.list.filter(x => x.id !== id); }"91 }92},93"children": [{94 "componentName": "TinyButton",95 "props": {96 "text": "删除",97 "onClick": { "type": "JSExpression", "value": "this.handleDelete", "params": ["123"] }98 }99}]100```101102The binding above calls `handleDelete(event, 123)`. ❌ Never put a `JSFunction` on an event, and never put a `function(){}` body inside a `JSExpression.value` — both silently break the handler.103104**Memory aid:** `JSExpression` = reference (`this.fn`) · `JSFunction` = definition (`function(){}`). Events use references; methods use definitions.105106## Validate (required)107108```bash109bash .agents/skills/tinyengine-dsl-generator/scripts/validate_all.sh <output-file>110```111112`validate_all.sh` chains three checks — do **not** rely on `validate_dsl.mjs` alone (it misses event-binding errors). Fix and re-run until all three pass; never hand off unvalidated output.113114| Stage | Script | Catches |115| -------------- | ------------------------ | ------------------------------------------------------------------------------- |116| Structure | validate_dsl.mjs | Required fields, Page/Block `componentName`, meta, `class` vs `className`, app/page id types |117| Event bindings | check_event_bindings.mjs | `JSFunction` on an event, or a function body in `JSExpression.value` |118| CSS | check_css.mjs | Malformed `css` strings |119120## Pre-Generation Checklist121122- [ ] Event bindings use `JSExpression`; no `value` starts with `"function"`; function bodies live in `methods` / `lifeCycles`123- [ ] Event methods take `event` as the first parameter; `params` append after it124- [ ] Lifecycle names start with `on` (`onMounted`, …); `setup` is the only exception125- [ ] `modelValue` declares `model` (`true` for standard v-model)126- [ ] `occupier` is `null`127- [ ] All `id`s are unique; CSS classes use `className`, not `class`128- [ ] **App schema** `id` and `meta.appId` are integers (`918`, not `"918"`) — apps.js persists `meta.appId` as string internally, keep the DSL integer129- [ ] **Page** `app` reference is a string (`"918"`, not `918`) — pages.js queries with `appId.toString()`; a numeric `app` won't be found by `list()`. Page's own `id` is a NanoID string assigned by the server130131## Component lookup132133Don't load `bundle.json` (≈1 MB) by hand. Query it:134135```bash136node scripts/query_components.mjs list # all components137node scripts/query_components.mjs props TinyButton # one component's props (fuzzy match)138node scripts/query_components.mjs cat 表单 # components in a category139node scripts/query_components.mjs search 表格 # full-text search140```141142## File output143144- **Apps** → `mockServer/data/apps/<app-name>.json`145- **Pages** → `mockServer/data/pages/<PageName>.json`146- **Blocks** → `mockServer/data/blocks/<BlockName>.json`147148Pages and blocks are saved with an **outer wrapper** around the DSL: page files wrap the Page DSL in `page_content` (plus `name`, `id`, `app`, `route`, `tenant`, `parentId`, `group`, `isPage`, `isHome`); block files wrap the Block DSL in `content` (plus `id`, `label`, `framework`, `path`, `public`, `is_published`). The validators auto-unwrap both, so you can validate either the wrapper or the inner DSL directly.149150## Design-to-DSL151152From a description or screenshot: identify layout regions → map visuals to components → extract interactions → define state + handlers → apply `className` / `style`.153154## Troubleshooting155156| Problem | Check |157| ----------------- | ---------------------------------------------------------------------- |158| Input not working | `modelValue` declares `model` (`true` for standard v-model) |159| Event not firing | `JSExpression` (not `JSFunction`); method exists in `methods` |160| Page not editable | `occupier` is `null` |161| Wrong params | first param is always `event`; `params` append after |162163## Resources164165- [protocol.md](references/protocol.md) — schema spec, TS interfaces, reserved names, property types, slots, full ❌/✅ examples166- [components.md](references/components.md) — component catalog (props/events); supplement with `query_components.mjs`167- [patterns.md](references/patterns.md) — list/form page, layout, interaction templates168- `scripts/` — `validate_all.sh` (run this), `validate_dsl.mjs`, `check_event_bindings.mjs`, `check_css.mjs`, `validate_page.mjs`, `query_components.mjs`