| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- import { pageMetadata } from "@/lib/page-metadata"
- export const metadata = pageMetadata("docs/migration")
- # Migration Guide
- This guide covers breaking changes introduced in v0.6.0 and how to update your code.
- ## State Provider
- `DataProvider` has been renamed to `StateProvider`, and its props have changed.
- **Before:**
- ```tsx
- import { DataProvider } from "@json-render/react";
- <DataProvider data={myData} getValue={getter} setValue={setter}>
- {children}
- </DataProvider>
- ```
- **After:**
- ```tsx
- import { StateProvider } from "@json-render/react";
- <StateProvider initialState={myData} onStateChange={(path, value) => console.log(path, value)}>
- {children}
- </StateProvider>
- ```
- `StateProvider` now manages state internally. Use `useStateStore()` to access `get`, `set`, and `update`.
- <table>
- <thead>
- <tr>
- <th>Before</th>
- <th>After</th>
- </tr>
- </thead>
- <tbody>
- <tr><td><code>DataProvider</code></td><td><code>StateProvider</code></td></tr>
- <tr><td><code>data</code> prop</td><td><code>initialState</code> prop</td></tr>
- <tr><td><code>getValue</code> / <code>setValue</code> props</td><td>Removed (use <code>useStateStore()</code> hook for <code>get</code> / <code>set</code>)</td></tr>
- <tr><td><code>useData</code></td><td><code>useStateStore</code></td></tr>
- <tr><td><code>useDataValue</code></td><td><code>useStateValue</code></td></tr>
- <tr><td><code>useDataBinding</code></td><td><code>useStateBinding</code> (deprecated, use <code>useBoundProp</code> instead)</td></tr>
- <tr><td><code>DataModel</code> type</td><td><code>StateModel</code> type</td></tr>
- </tbody>
- </table>
- ## Dynamic Expressions
- All dynamic value expressions have been renamed to use `$state`, `$item`, and `$index`.
- **Before:**
- ```json
- {
- "type": "Text",
- "props": {
- "label": { "$path": "/user/name" },
- "count": { "$data": "/items/length" }
- }
- }
- ```
- **After:**
- ```json
- {
- "type": "Text",
- "props": {
- "label": { "$state": "/user/name" },
- "count": { "$state": "/items/length" }
- }
- }
- ```
- Inside repeat scopes, use `$item` and `$index`:
- ```json
- {
- "type": "Card",
- "props": {
- "title": { "$item": "name" },
- "subtitle": { "$index": true }
- }
- }
- ```
- <table>
- <thead>
- <tr>
- <th>Before</th>
- <th>After</th>
- </tr>
- </thead>
- <tbody>
- <tr><td><code>{'{ "$path": "/..." }'}</code></td><td><code>{'{ "$state": "/..." }'}</code></td></tr>
- <tr><td><code>{'{ "$data": "/..." }'}</code></td><td><code>{'{ "$state": "/..." }'}</code></td></tr>
- </tbody>
- </table>
- ## Two-Way Binding
- Form components no longer use `valuePath` / `statePath` props. Instead, use `$bindState` expressions on the value prop, and `useBoundProp` in your registry.
- **Before (catalog):**
- ```typescript
- Input: {
- props: z.object({
- label: z.string(),
- valuePath: z.string(),
- placeholder: z.string().optional(),
- }),
- }
- ```
- **Before (spec):**
- ```json
- {
- "type": "Input",
- "props": { "label": "Email", "valuePath": "/form/email" }
- }
- ```
- **Before (registry):**
- ```tsx
- Input: ({ props }) => {
- const [value, setValue] = useStateBinding(props.valuePath);
- return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
- }
- ```
- **After (catalog):**
- ```typescript
- Input: {
- props: z.object({
- label: z.string(),
- value: z.string().optional(),
- placeholder: z.string().optional(),
- }),
- }
- ```
- **After (spec):**
- ```json
- {
- "type": "Input",
- "props": { "label": "Email", "value": { "$bindState": "/form/email" } }
- }
- ```
- **After (registry):**
- ```tsx
- Input: ({ props, bindings }) => {
- const [value, setValue] = useBoundProp<string>(props.value, bindings?.value);
- return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
- }
- ```
- `$bindState` reads from and writes to the given state path. Inside repeat scopes, use `$bindItem` to bind to a field on the current item:
- ```json
- {
- "type": "Checkbox",
- "props": { "checked": { "$bindItem": "completed" } }
- }
- ```
- ## Visibility Conditions
- Visibility conditions have been renamed to use `$state`, `$and`, and `$or`.
- **Before:**
- ```json
- { "path": "/isAdmin" }
- { "eq": [{ "path": "/role" }, "admin"] }
- { "and": [{ "path": "/isAdmin" }, { "path": "/feature" }] }
- { "or": [{ "path": "/roleA" }, { "path": "/roleB" }] }
- ```
- **After:**
- ```json
- { "$state": "/isAdmin" }
- { "$state": "/role", "eq": "admin" }
- { "$and": [{ "$state": "/isAdmin" }, { "$state": "/feature" }] }
- { "$or": [{ "$state": "/roleA" }, { "$state": "/roleB" }] }
- ```
- You can also use an array as shorthand for `$and`:
- ```json
- [{ "$state": "/isAdmin" }, { "$state": "/feature" }]
- ```
- Inside repeat scopes, use `$item` and `$index`:
- ```json
- { "$item": "isActive" }
- { "$index": true, "eq": 0 }
- ```
- ## Event System
- Components now use `emit` to fire named events. `onAction` has been removed.
- **Before:**
- ```tsx
- Button: ({ props, onAction }) => (
- <button onClick={() => onAction?.("press")}>{props.label}</button>
- )
- ```
- **After:**
- ```tsx
- Button: ({ props, emit }) => (
- <button onClick={() => emit("press")}>{props.label}</button>
- )
- ```
- `emit` is always defined (never `undefined`), so optional chaining is not needed.
- ## Actions Context
- `dispatch` has been renamed to `execute`, and the provider prop has been renamed from `actionHandlers` to `handlers`.
- **Before:**
- ```tsx
- const { dispatch } = useActions();
- dispatch({ action: "submit", params: {} });
- <ActionProvider actionHandlers={myHandlers}>
- ```
- **After:**
- ```tsx
- const { execute } = useActions();
- execute({ action: "submit", params: {} });
- <ActionProvider handlers={myHandlers}>
- ```
- ## Repeat / List Rendering
- The `repeat` field now uses `statePath` instead of `path`.
- **Before:**
- ```json
- {
- "type": "Column",
- "repeat": { "path": "/todos", "key": "id" },
- "children": ["todo-item"]
- }
- ```
- **After:**
- ```json
- {
- "type": "Column",
- "repeat": { "statePath": "/todos", "key": "id" },
- "children": ["todo-item"]
- }
- ```
- ## Catalog Creation
- `createCatalog` and `generateSystemPrompt` have been replaced by `defineSchema` + `defineCatalog`.
- **Before:**
- ```typescript
- import { createCatalog, generateSystemPrompt } from "@json-render/core";
- const catalog = createCatalog({
- name: "my-app",
- components: { /* ... */ },
- actions: { /* ... */ },
- });
- const prompt = generateSystemPrompt(catalog);
- ```
- **After:**
- ```typescript
- import { defineCatalog } from "@json-render/core";
- import { schema } from "@json-render/react/schema";
- const catalog = defineCatalog(schema, {
- components: { /* ... */ },
- actions: { /* ... */ },
- });
- const prompt = catalog.prompt();
- // Inline mode prompt (formerly "chat")
- const inlinePrompt = catalog.prompt({ mode: "inline" });
- ```
- ## Generation Modes
- The generation mode values passed to `catalog.prompt()` have been renamed for clarity:
- - `"generate"` is now `"standalone"`
- - `"chat"` is now `"inline"`
- The old names are accepted as deprecated aliases, so existing code will continue to work. Update when convenient.
- **Before:**
- ```typescript
- const prompt = catalog.prompt({ mode: "generate" });
- const chatPrompt = catalog.prompt({ mode: "chat" });
- ```
- **After:**
- ```typescript
- const prompt = catalog.prompt({ mode: "standalone" });
- const inlinePrompt = catalog.prompt({ mode: "inline" });
- ```
- The default mode (when no `mode` option is provided) is `"standalone"`, which behaves identically to the previous `"generate"` default.
- ## Validation
- `ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
- **Before:**
- ```json
- { "fn": "required", "message": "Required" }
- { "fn": "minLength", "args": { "length": 8 }, "message": "Too short" }
- ```
- **After:**
- ```json
- { "type": "required", "message": "Required" }
- { "type": "minLength", "args": { "min": 8 }, "message": "Too short" }
- ```
- <table>
- <thead>
- <tr>
- <th>Before</th>
- <th>After</th>
- </tr>
- </thead>
- <tbody>
- <tr><td><code>{'{ fn: "required" }'}</code></td><td><code>{'{ type: "required" }'}</code></td></tr>
- <tr><td><code>{'ValidationProvider functions={...}'}</code></td><td><code>{'ValidationProvider customFunctions={...}'}</code></td></tr>
- <tr><td><code>useFieldValidation(path, checks)</code></td><td><code>useFieldValidation(path, config)</code> where config is <code>{'{ checks, validateOn? }'}</code></td></tr>
- </tbody>
- </table>
- ## Visibility Provider
- The `auth` prop has been removed from `VisibilityProvider`. Auth state should be modeled as regular state.
- **Before:**
- ```tsx
- <VisibilityProvider auth={{ isSignedIn: true, role: "admin" }}>
- ```
- ```json
- { "auth": "signedIn" }
- ```
- **After:**
- ```tsx
- <StateProvider initialState={{ auth: { isSignedIn: true, role: "admin" } }}>
- <VisibilityProvider>
- ```
- ```json
- { "$state": "/auth/isSignedIn" }
- ```
- ## Codegen
- `traverseTree` has been renamed to `traverseSpec`, `SpecVisitor` to `TreeVisitor`, and the visitor callback now receives a `key` parameter.
- **Before:**
- ```typescript
- import { traverseTree } from "@json-render/codegen";
- traverseTree(tree, (element) => {
- // ...
- });
- ```
- **After:**
- ```typescript
- import { traverseSpec } from "@json-render/codegen";
- traverseSpec(spec, (element, key) => {
- // ...
- });
- ```
- ## Action Params
- Action params in specs now use `statePath` instead of `path`.
- **Before:**
- ```json
- {
- "on": {
- "press": { "action": "setState", "params": { "path": "/count", "value": 0 } }
- }
- }
- ```
- **After:**
- ```json
- {
- "on": {
- "press": { "action": "setState", "params": { "statePath": "/count", "value": 0 } }
- }
- }
- ```
- ## Removed Exports
- The following exports have been removed from `@json-render/core`:
- <table>
- <thead>
- <tr>
- <th>Removed</th>
- <th>Replacement</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td><code>createCatalog</code></td>
- <td><code>defineCatalog(schema, config)</code></td>
- </tr>
- <tr>
- <td><code>generateCatalogPrompt</code></td>
- <td><code>catalog.prompt()</code></td>
- </tr>
- <tr>
- <td><code>generateSystemPrompt</code></td>
- <td><code>catalog.prompt()</code></td>
- </tr>
- <tr>
- <td><code>ComponentDefinition</code></td>
- <td>Use catalog component config directly</td>
- </tr>
- <tr>
- <td><code>CatalogConfig</code></td>
- <td>Use <code>defineCatalog</code> parameters</td>
- </tr>
- <tr>
- <td><code>SystemPromptOptions</code></td>
- <td>Use <code>PromptOptions</code></td>
- </tr>
- <tr>
- <td><code>LogicExpression</code></td>
- <td>Use <code>VisibilityCondition</code></td>
- </tr>
- <tr>
- <td><code>AuthState</code></td>
- <td>Model auth as regular state (e.g. <code>/auth/isSignedIn</code>)</td>
- </tr>
- <tr>
- <td><code>evaluateLogicExpression</code></td>
- <td>Use <code>evaluateVisibility</code></td>
- </tr>
- <tr>
- <td><code>createRendererFromCatalog</code></td>
- <td>Use <code>defineRegistry</code></td>
- </tr>
- <tr>
- <td><code>traverseTree</code> (codegen)</td>
- <td>Use <code>traverseSpec</code></td>
- </tr>
- </tbody>
- </table>
|