{"_id":"@wotnak/json-render-core","_rev":"2-0145d2cc63c8298290e9a1849cbbc42b","name":"@wotnak/json-render-core","dist-tags":{"latest":"0.0.0-pr.slots.9c5563f","slots":"0.0.0-pr.slots.9c5563f"},"versions":{"0.0.0-pr.slots.9c5563f":{"name":"@wotnak/json-render-core","version":"0.0.0-pr.slots.9c5563f","keywords":["json","ui","react","ai","generative-ui","llm","schema","zod","streaming"],"license":"Apache-2.0","_id":"@wotnak/json-render-core@0.0.0-pr.slots.9c5563f","maintainers":[{"name":"wotnak","email":"wotnak@pm.me"}],"homepage":"https://github.com/vercel-labs/json-render#readme","bugs":{"url":"https://github.com/vercel-labs/json-render/issues"},"dist":{"shasum":"718d66723c91368b895aa402b3ddff4e0ad250a4","tarball":"https://registry.npmjs.org/@wotnak/json-render-core/-/json-render-core-0.0.0-pr.slots.9c5563f.tgz","fileCount":9,"integrity":"sha512-w76EBhUWpcDAjoo1Llu2shNgGOfuvPM1RU7gtIikxAsplmUE3ElrYT6kxYh85ckwbiickou5f+OocwYgMSz+wQ==","signatures":[{"sig":"MEYCIQCfUCkOPhkgv73gq82DnQw9xMe/2zrY3cooSiDy5zE8oQIhAJHJAMMnL1/m/7knX/WHeEIGYuwW5jedcTEKHXK+ZPol","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":615291},"main":"./dist/index.js","_from":"file:wotnak-json-render-core-0.0.0-pr.slots.9c5563f.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"wotnak","email":"wotnak@pm.me"},"_resolved":"/private/var/folders/27/cc851yz539l_4__9gtjk1ggm0000gn/T/cb86ba9432b57e30f7672b02ec979507/wotnak-json-render-core-0.0.0-pr.slots.9c5563f.tgz","_integrity":"sha512-w76EBhUWpcDAjoo1Llu2shNgGOfuvPM1RU7gtIikxAsplmUE3ElrYT6kxYh85ckwbiickou5f+OocwYgMSz+wQ==","repository":{"url":"git+https://github.com/wotnak/json-render.git","type":"git"},"_npmVersion":"11.6.2","description":"JSON becomes real things. Define your catalog, register your components, let AI generate.","directories":{},"_nodeVersion":"24.13.0","dependencies":{"zod":"^4.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.2","typescript":"^5.4.5","@repo/typescript-config":"0.0.0-pr.slots.9c5563f"},"peerDependencies":{"zod":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/json-render-core_0.0.0-pr.slots.9c5563f_1771310448787_0.047305767167959756","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-02-17T06:40:48.618Z","modified":"2026-02-17T06:48:15.541Z","0.0.0-pr.slots.9c5563f":"2026-02-17T06:40:48.968Z"},"bugs":{"url":"https://github.com/vercel-labs/json-render/issues"},"license":"Apache-2.0","homepage":"https://github.com/vercel-labs/json-render#readme","keywords":["json","ui","react","ai","generative-ui","llm","schema","zod","streaming"],"repository":{"url":"git+https://github.com/wotnak/json-render.git","type":"git"},"description":"JSON becomes real things. Define your catalog, register your components, let AI generate.","maintainers":[{"name":"wotnak","email":"wotnak@pm.me"}],"readme":"# @json-render/core\n\nCore library for json-render. Define schemas, create catalogs, generate AI prompts, and stream specs.\n\n## Installation\n\n```bash\nnpm install @json-render/core zod\n```\n\n## Key Concepts\n\n- **Schema**: Defines the structure of specs and catalogs\n- **Catalog**: Maps component/action names to their definitions with Zod props\n- **Spec**: JSON output from AI that conforms to the schema\n- **SpecStream**: JSONL streaming format for progressive spec building\n\n## Quick Start\n\n### Define a Schema\n\n```typescript\nimport { defineSchema } from \"@json-render/core\";\n\nexport const schema = defineSchema((s) => ({\n  spec: s.object({\n    root: s.object({\n      type: s.ref(\"catalog.components\"),\n      props: s.propsOf(\"catalog.components\"),\n      children: s.array(s.string()), // Element keys (flat spec format)\n    }),\n  }),\n  catalog: s.object({\n    components: s.map({\n      props: s.zod(),\n      description: s.string(),\n    }),\n    actions: s.map({\n      description: s.string(),\n    }),\n  }),\n}), {\n  promptTemplate: myPromptTemplate, // Optional custom AI prompt generator\n});\n```\n\n### Create a Catalog\n\n```typescript\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema } from \"./schema\";\nimport { z } from \"zod\";\n\nexport const catalog = defineCatalog(schema, {\n  components: {\n    Card: {\n      props: z.object({\n        title: z.string(),\n        subtitle: z.string().nullable(),\n      }),\n      description: \"A card container with title\",\n    },\n    Button: {\n      props: z.object({\n        label: z.string(),\n        variant: z.enum([\"primary\", \"secondary\"]).nullable(),\n      }),\n      description: \"A clickable button\",\n    },\n  },\n  actions: {\n    submit: { description: \"Submit the form\" },\n    cancel: { description: \"Cancel and close\" },\n  },\n});\n```\n\n### Generate AI Prompts\n\n```typescript\n// Generate system prompt for AI\nconst systemPrompt = catalog.prompt();\n\n// With custom rules\nconst systemPrompt = catalog.prompt({\n  system: \"You are a dashboard builder.\",\n  customRules: [\n    \"Always include a header\",\n    \"Use Card components for grouping\",\n  ],\n});\n```\n\n### Stream AI Responses (SpecStream)\n\nThe SpecStream format uses JSONL patches to progressively build specs:\n\n```typescript\nimport { createSpecStreamCompiler } from \"@json-render/core\";\n\n// Create a compiler for your spec type\nconst compiler = createSpecStreamCompiler<MySpec>();\n\n// Process streaming chunks from AI\nwhile (streaming) {\n  const chunk = await reader.read();\n  const { result, newPatches } = compiler.push(chunk);\n  \n  if (newPatches.length > 0) {\n    // Update UI with partial result\n    setSpec(result);\n  }\n}\n\n// Get final compiled result\nconst finalSpec = compiler.getResult();\n```\n\nSpecStream format uses [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations (each line is a patch):\n\n```jsonl\n{\"op\":\"add\",\"path\":\"/root\",\"value\":\"card-1\"}\n{\"op\":\"add\",\"path\":\"/elements/card-1\",\"value\":{\"type\":\"Card\",\"props\":{\"title\":\"Hello\"},\"children\":[\"btn-1\"]}}\n{\"op\":\"add\",\"path\":\"/elements/btn-1\",\"value\":{\"type\":\"Button\",\"props\":{\"label\":\"Click\"},\"children\":[]}}\n```\n\nAll six RFC 6902 operations are supported: `add`, `remove`, `replace`, `move`, `copy`, `test`.\n\n### Low-Level Utilities\n\n```typescript\nimport {\n  parseSpecStreamLine,\n  applySpecStreamPatch,\n  compileSpecStream,\n} from \"@json-render/core\";\n\n// Parse a single line\nconst patch = parseSpecStreamLine('{\"op\":\"add\",\"path\":\"/root\",\"value\":{}}');\n// { op: \"add\", path: \"/root\", value: {} }\n\n// Apply a patch to an object\nconst obj = {};\napplySpecStreamPatch(obj, patch);\n// obj is now { root: {} }\n\n// Compile entire JSONL string at once\nconst spec = compileSpecStream<MySpec>(jsonlString);\n```\n\n## API Reference\n\n### Schema\n\n| Export | Purpose |\n|--------|---------|\n| `defineSchema(builder, options?)` | Create a schema with spec/catalog structure |\n| `SchemaBuilder` | Builder with `s.object()`, `s.array()`, `s.map()`, etc. |\n\n### Catalog\n\n| Export | Purpose |\n|--------|---------|\n| `defineCatalog(schema, data)` | Create a type-safe catalog from schema |\n| `catalog.prompt(options?)` | Generate AI system prompt |\n\n### SpecStream\n\n| Export | Purpose |\n|--------|---------|\n| `createSpecStreamCompiler<T>()` | Create streaming compiler |\n| `parseSpecStreamLine(line)` | Parse single JSONL line |\n| `applySpecStreamPatch(obj, patch)` | Apply patch to object |\n| `compileSpecStream<T>(jsonl)` | Compile entire JSONL string |\n\n### Dynamic Props\n\n| Export | Purpose |\n|--------|---------|\n| `resolvePropValue(value, ctx)` | Resolve a single prop expression |\n| `resolveElementProps(props, ctx)` | Resolve all prop expressions in an element |\n| `PropExpression<T>` | Type for prop values that may contain expressions |\n\n### User Prompt\n\n| Export | Purpose |\n|--------|---------|\n| `buildUserPrompt(options)` | Build a user prompt with optional spec refinement and state context |\n| `UserPromptOptions` | Options type for `buildUserPrompt` |\n\n### Spec Validation\n\n| Export | Purpose |\n|--------|---------|\n| `validateSpec(spec, options?)` | Validate spec structure and return issues |\n| `autoFixSpec(spec)` | Auto-fix common spec issues (returns corrected copy) |\n| `formatSpecIssues(issues)` | Format validation issues as readable strings |\n\n### Types\n\n| Export | Purpose |\n|--------|---------|\n| `Spec` | Base spec type |\n| `Catalog` | Catalog type |\n| `VisibilityCondition` | Visibility condition type (used by `$cond`) |\n| `VisibilityContext` | Context for evaluating visibility and prop expressions |\n| `SpecStreamLine` | Single patch operation |\n| `SpecStreamCompiler` | Streaming compiler interface |\n\n## Dynamic Prop Expressions\n\nAny prop value can be a dynamic expression that resolves based on data state at render time. Expressions are resolved by the renderer before props reach components.\n\n### Data Binding (`$state`)\n\nRead a value directly from the state model:\n\n```json\n{\n  \"color\": { \"$state\": \"/theme/primary\" },\n  \"label\": { \"$state\": \"/user/name\" }\n}\n```\n\n### Two-Way Binding (`$bindState` / `$bindItem`)\n\nUse `{ \"$bindState\": \"/path\" }` on the natural value prop for form components that need read/write access. The component reads from and writes to the state path:\n\n```json\n{\n  \"type\": \"Input\",\n  \"props\": {\n    \"value\": { \"$bindState\": \"/form/email\" },\n    \"placeholder\": \"Email\"\n  }\n}\n```\n\nInside a repeat scope, use `{ \"$bindItem\": \"completed\" }` to bind to a field on the current item:\n\n### Conditional (`$cond` / `$then` / `$else`)\n\nEvaluate a condition (same syntax as visibility conditions) and pick a value:\n\n```json\n{\n  \"color\": {\n    \"$cond\": { \"$state\": \"/activeTab\", \"eq\": \"home\" },\n    \"$then\": \"#007AFF\",\n    \"$else\": \"#8E8E93\"\n  },\n  \"name\": {\n    \"$cond\": { \"$state\": \"/activeTab\", \"eq\": \"home\" },\n    \"$then\": \"home\",\n    \"$else\": \"home-outline\"\n  }\n}\n```\n\n`$then` and `$else` can themselves be expressions (recursive):\n\n```json\n{\n  \"label\": {\n    \"$cond\": { \"$state\": \"/user/isAdmin\" },\n    \"$then\": { \"$state\": \"/admin/greeting\" },\n    \"$else\": \"Welcome\"\n  }\n}\n```\n\n### Repeat Item (`$item`)\n\nInside children of a repeated element, read a field from the current array item:\n\n```json\n{ \"$item\": \"title\" }\n```\n\nUse `\"\"` to get the entire item object. `$item` takes a path string because items are typically objects with nested fields to navigate.\n\n### Repeat Index (`$index`)\n\nGet the current array index inside a repeat:\n\n```json\n{ \"$index\": true }\n```\n\n`$index` uses `true` as a sentinel flag because the index is a scalar value with no sub-path to navigate (unlike `$item` which needs a path).\n\n### API\n\n```typescript\nimport { resolvePropValue, resolveElementProps } from \"@json-render/core\";\n\n// Resolve a single value\nconst color = resolvePropValue(\n  { $cond: { $state: \"/active\", eq: \"yes\" }, $then: \"blue\", $else: \"gray\" },\n  { stateModel: myState }\n);\n\n// Resolve all props on an element\nconst resolved = resolveElementProps(element.props, { stateModel: myState });\n```\n\n## Visibility Conditions\n\nVisibility conditions control when elements are shown. `VisibilityContext` is `{ stateModel: StateModel, repeatItem?: unknown, repeatIndex?: number }`.\n\n### Syntax\n\n```typescript\n{ \"$state\": \"/path\" }                          // truthiness\n{ \"$state\": \"/path\", \"not\": true }             // falsy\n{ \"$state\": \"/path\", \"eq\": value }             // equality\n{ \"$state\": \"/path\", \"neq\": value }            // inequality\n{ \"$state\": \"/path\", \"gt\": number }            // greater than\n{ \"$item\": \"field\" }                          // repeat item field\n{ \"$index\": true, \"gt\": 0 }                   // repeat index\n[ condition, condition ]                       // implicit AND\n{ \"$and\": [ condition, condition ] }           // explicit AND\n{ \"$or\": [ condition, condition ] }            // OR\ntrue / false                                   // always / never\n```\n\n### TypeScript Helpers\n\n```typescript\nimport { visibility } from \"@json-render/core\";\n\nvisibility.always              // true\nvisibility.never               // false\nvisibility.when(\"/path\")       // { $state: \"/path\" }\nvisibility.unless(\"/path\")     // { $state: \"/path\", not: true }\nvisibility.eq(\"/path\", val)    // { $state: \"/path\", eq: val }\nvisibility.neq(\"/path\", val)   // { $state: \"/path\", neq: val }\nvisibility.gt(\"/path\", n)      // { $state: \"/path\", gt: n }\nvisibility.gte(\"/path\", n)     // { $state: \"/path\", gte: n }\nvisibility.lt(\"/path\", n)      // { $state: \"/path\", lt: n }\nvisibility.lte(\"/path\", n)     // { $state: \"/path\", lte: n }\nvisibility.and(cond1, cond2)   // { $and: [cond1, cond2] }\nvisibility.or(cond1, cond2)    // { $or: [cond1, cond2] }\n```\n\n## User Prompt Builder\n\nBuild structured user prompts for AI generation, with support for refinement and state context:\n\n```typescript\nimport { buildUserPrompt } from \"@json-render/core\";\n\n// Fresh generation\nconst prompt = buildUserPrompt({ prompt: \"create a todo app\" });\n\n// Refinement with existing spec (triggers patch-only mode)\nconst refinementPrompt = buildUserPrompt({\n  prompt: \"add a dark mode toggle\",\n  currentSpec: existingSpec,\n});\n\n// With runtime state context\nconst contextPrompt = buildUserPrompt({\n  prompt: \"show my data\",\n  state: { todos: [{ text: \"Buy milk\" }] },\n});\n```\n\n## Spec Validation\n\nValidate spec structure and auto-fix common issues:\n\n```typescript\nimport { validateSpec, autoFixSpec, formatSpecIssues } from \"@json-render/core\";\n\n// Validate a spec\nconst { valid, issues } = validateSpec(spec);\n\n// Format issues for display\nconsole.log(formatSpecIssues(issues));\n\n// Auto-fix common issues (returns a corrected copy)\nconst fixed = autoFixSpec(spec);\n```\n\n## Custom Schemas\n\njson-render supports completely different spec formats for different renderers:\n\n```typescript\n// React: Flat element map\n{ root: \"card-1\", elements: { \"card-1\": { type: \"Card\", props: {...}, children: [...] } } }\n\n// Remotion: Timeline\n{ composition: {...}, tracks: [...], clips: [...] }\n\n// Your own: Whatever you need\n{ pages: [...], navigation: {...}, theme: {...} }\n```\n\nEach renderer defines its own schema with `defineSchema()` and its own prompt template.\n","readmeFilename":"README.md"}