page.mdx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. export const metadata = { title: "Registry" }
  2. # Registry
  3. A registry maps your [catalog](/docs/catalog) definitions to platform-specific implementations. The catalog defines *what* AI can generate — the registry provides the *how*.
  4. What a registry contains depends on the schema you use. Each package defines its own schema, which determines the shape of both the catalog and the registry.
  5. - **`@json-render/react`** — Components (React elements) and action handlers
  6. - **`@json-render/react-native`** — Components (React Native elements) and action handlers
  7. - **`@json-render/remotion`** — Clip components, transitions, and effects
  8. ## @json-render/react
  9. ### defineRegistry
  10. Use `defineRegistry` to create a type-safe registry from your catalog. Pass your components, actions, or both:
  11. ```tsx
  12. import { defineRegistry } from '@json-render/react';
  13. import { myCatalog } from './catalog';
  14. export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
  15. components: {
  16. Card: ({ props, children }) => (
  17. <div className="card">
  18. <h2>{props.title}</h2>
  19. {props.description && <p>{props.description}</p>}
  20. {children}
  21. </div>
  22. ),
  23. Button: ({ props, onAction }) => (
  24. <button onClick={() => onAction?.({ name: props.action })}>
  25. {props.label}
  26. </button>
  27. ),
  28. },
  29. actions: {
  30. submit_form: async (params, setState) => {
  31. const res = await fetch('/api/submit', {
  32. method: 'POST',
  33. body: JSON.stringify(params),
  34. });
  35. const result = await res.json();
  36. setState((prev) => ({ ...prev, formResult: result }));
  37. },
  38. export_data: async (params) => {
  39. const blob = await generateExport(params.format);
  40. downloadBlob(blob, `export.${params.format}`);
  41. },
  42. },
  43. });
  44. ```
  45. The returned object contains:
  46. - `registry` — component registry for `<Renderer />`
  47. - `handlers` — factory for ActionProvider-compatible handlers
  48. - `executeAction` — imperative action dispatch (for use outside the React tree)
  49. ### Component Props
  50. Each component receives a `ComponentContext` object:
  51. ```typescript
  52. interface ComponentContext {
  53. props: T; // Type-safe props from your catalog
  54. children?: React.ReactNode; // Rendered children (for slot components)
  55. onAction?: (action: ActionTrigger) => void; // Dispatch an action
  56. loading?: boolean; // Whether the renderer is in a loading state
  57. }
  58. ```
  59. Props are automatically inferred from your catalog, so `props.title` is typed as `string` if your catalog defines it that way.
  60. ### Action Handlers
  61. Instead of AI generating arbitrary code, it declares *intent* by name. Your application provides the implementation. This is a core guardrail.
  62. Actions are declared in your [catalog](/docs/catalog). The `@json-render/react` schema supports an `actions` key where you define what operations AI can trigger:
  63. ```typescript
  64. import { defineCatalog } from '@json-render/core';
  65. import { schema } from '@json-render/react';
  66. import { z } from 'zod';
  67. const catalog = defineCatalog(schema, {
  68. components: { /* ... */ },
  69. actions: {
  70. submit_form: {
  71. params: z.object({
  72. formId: z.string(),
  73. }),
  74. description: 'Submit a form',
  75. },
  76. export_data: {
  77. params: z.object({
  78. format: z.enum(['csv', 'pdf', 'json']),
  79. }),
  80. },
  81. navigate: {
  82. params: z.object({
  83. url: z.string(),
  84. }),
  85. },
  86. },
  87. });
  88. ```
  89. Action handlers receive `(params, setState, data)` and are defined inside `defineRegistry`:
  90. ```tsx
  91. export const { handlers, executeAction } = defineRegistry(catalog, {
  92. actions: {
  93. submit_form: async (params, setState) => {
  94. const response = await fetch('/api/submit', {
  95. method: 'POST',
  96. body: JSON.stringify({ formId: params.formId }),
  97. });
  98. const result = await response.json();
  99. setState((prev) => ({ ...prev, formResult: result }));
  100. },
  101. export_data: async (params) => {
  102. const blob = await generateExport(params.format);
  103. downloadBlob(blob, `export.${params.format}`);
  104. },
  105. navigate: (params) => {
  106. window.location.href = params.url;
  107. },
  108. },
  109. });
  110. ```
  111. ### Data Binding
  112. Use hooks inside your registry components to read and write data:
  113. ```tsx
  114. import { useStateStore } from '@json-render/react';
  115. import { getByPath } from '@json-render/core';
  116. // Inside defineRegistry components:
  117. Metric: ({ props }) => {
  118. const { data } = useStateStore();
  119. const value = getByPath(data, props.valuePath);
  120. return (
  121. <div className="metric">
  122. <span className="label">{props.label}</span>
  123. <span className="value">{formatValue(value)}</span>
  124. </div>
  125. );
  126. },
  127. TextField: ({ props }) => {
  128. const { data, set } = useStateStore();
  129. const value = getByPath(data, props.valuePath) as string;
  130. return (
  131. <input
  132. value={value || ''}
  133. onChange={(e) => set(props.valuePath, e.target.value)}
  134. placeholder={props.placeholder}
  135. />
  136. );
  137. },
  138. ```
  139. ### Using the Renderer
  140. Wire everything together with providers and the `<Renderer />` component:
  141. ```tsx
  142. import { useMemo, useRef } from 'react';
  143. import {
  144. Renderer,
  145. StateProvider,
  146. VisibilityProvider,
  147. ActionProvider,
  148. } from '@json-render/react';
  149. import { registry, handlers } from './registry';
  150. function App({ spec, data, setState }) {
  151. const dataRef = useRef(data);
  152. const setStateRef = useRef(setState);
  153. dataRef.current = data;
  154. setStateRef.current = setState;
  155. const actionHandlers = useMemo(
  156. () => handlers(() => setStateRef.current, () => dataRef.current),
  157. [],
  158. );
  159. return (
  160. <StateProvider initialState={data}>
  161. <VisibilityProvider>
  162. <ActionProvider handlers={actionHandlers}>
  163. <Renderer spec={spec} registry={registry} />
  164. </ActionProvider>
  165. </VisibilityProvider>
  166. </StateProvider>
  167. );
  168. }
  169. ```
  170. ## @json-render/react-native
  171. `@json-render/react-native` uses the same `defineRegistry` API. The only difference is that components return React Native elements instead of HTML:
  172. ```tsx
  173. import { defineRegistry } from '@json-render/react-native';
  174. import { View, Text, Pressable } from 'react-native';
  175. export const { registry } = defineRegistry(catalog, {
  176. components: {
  177. Card: ({ props, children }) => (
  178. <View style={styles.card}>
  179. <Text style={styles.title}>{props.title}</Text>
  180. {children}
  181. </View>
  182. ),
  183. Button: ({ props, emit }) => (
  184. <Pressable onPress={() => emit?.("press")}>
  185. <Text>{props.label}</Text>
  186. </Pressable>
  187. ),
  188. },
  189. });
  190. ```
  191. See the [@json-render/react-native API reference](/docs/api/react-native) for the full API.
  192. ## @json-render/remotion
  193. `@json-render/remotion` takes a different approach. Instead of `defineRegistry`, it uses a plain component registry with built-in standard components for video production:
  194. ```tsx
  195. import { Renderer, standardComponents } from '@json-render/remotion';
  196. // Use the standard components directly
  197. <Renderer spec={timelineSpec} components={standardComponents} />
  198. // Or extend with your own
  199. const components = {
  200. ...standardComponents,
  201. CustomSlide: ({ clip }) => <AbsoluteFill>{/* ... */}</AbsoluteFill>,
  202. };
  203. ```
  204. The Remotion schema also supports `transitions` and `effects` in the catalog rather than actions.
  205. See the [@json-render/remotion API reference](/docs/api/remotion) for the full API.
  206. ## Next
  207. Learn about [data binding](/docs/data-binding) for dynamic values.