The schema system provides a framework-agnostic, schema-driven UI renderer designed for modular web apps. It turns declarative JSON-like schemas into live, reactive UIs, supporting layouts, routing, slots, and dynamic data/actions.
@we/schema-shared(shared/) — Framework-agnostic types, validators, prop resolvers, and mutations@we/schema-solid(solid/) — SolidJS renderer implementation
Anything computed — a condition, a label, a count, a filtered list — is one expression in a
{ $: '…' } token, with a closed grammar and an open function library
(shared/src/expressions/):
| Written as | Answers |
|---|---|
{ $: "spaceStore.members.count() > 0" } |
a boolean |
{ $: " `/space/${item.uuid}` " } |
a string |
{ $: "item.author == me.did ? 'mine' : 'theirs'" } |
a value |
{ $: "filter(local.rows, { role: 'admin' }, 5)" } |
a list — the where-object $query takes |
{ $: "items.filter(x, x.done).map(x, x.title)" } |
a list, by comprehension |
{ $: "plural(count(local.rows), 'row', 'rows')" } |
a library call |
References start from a store, local, a name $each bound, me, surface, or event inside a
handler. Checked statically with a column; total and inert at paint. A plain string is always
text — a reference is always { $: '…' }.
| Token | Purpose | Example |
|---|---|---|
$action |
Store method call + async lifecycle | { $action: 'store.method', args: [{ $: 'arg.id' }], onSuccess: [...] } |
$setLocal … |
Write local state | { $setLocal: 'page', value: { $: 'local.page + 20' } } |
$if |
Choose between handlers | { $if: { condition: { $: 'formValid()' }, then: { $action: '…' } } } |
$query |
Data retrieval | { $query: { entity: 'Post', where: { … } } } |
Operators that appear as the type field and control rendering structure:
| Operator | Purpose |
|---|---|
$if (node) |
Conditional rendering with optional transitions |
$each |
List rendering with context injection |
$routes |
Route outlet placeholder |
| Fragment | Renders children without wrapper (no type) |
| HTML passthrough | Lowercase types render as native elements |
- Slots: Named slots for layout composition
- Component registry: PascalCase → registered component,
we-*→ web component, lowercase → HTML element - REACTIVE_ACCESSOR: Signal tagging for web component prop unwrapping
- Schema validation: Zod-based validation with
validateSchema() - Schema mutations:
findMutations()for diff-based updates,updateSchema()for applying changes - Schema versioning:
schemaVersionfield for future migrations - Transitions:
TransitionConfigfor animated$ifnode show/hide (fade,slide,scale) - Operator token types:
StoreToken,ConcatToken,ActionToken,OperatorToken, etc. for typed schema authoring
See OPERATORS.md for the full operator reference.
-
Install:
pnpm add @we/schema-shared @we/schema-solid solid-js
-
Create a component registry in your app:
import { MyComponent, AnotherComponent } from './components'; export const registry = { MyComponent, AnotherComponent, // ...other components };
-
Render a schema:
import { RenderSchema } from '@we/schema-solid'; import { registry } from './registry'; <RenderSchema node={mySchema} stores={myStores} registry={registry} />;
- Shared types and prop resolvers are in
shared/. - Framework-specific renderers are in
solid/(addreact/, etc. for other frameworks). - To add support for another framework, implement a renderer that uses the shared logic and passes the correct JSX type.
- Types:
SchemaNode,TemplateSchema,RouteSchema,SchemaProp,ComponentRegistry,RenderProps,RendererOutput,TransitionConfig - Operator token types:
ExpressionToken,StoreToken,ActionToken,IfToken,MapToken,PickToken,EqToken,NeToken,NotToken,AndToken,OrToken,OperatorToken - Expressions:
parseExpression(),printExpression(),evaluateExpression(),checkExpression(),defineFunction(),listFunctions(),operatorToExpr(),exprToOperator() - Functions:
resolveProp(),resolveProps(),splitProps(),validateSchema(),findMutations(),hasToken() - Constants:
REACTIVE_ACCESSOR
RenderSchema: Main renderer componentupdateSchema(): Apply mutations to a reactive Solid store
MIT