mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 12:28:17 +08:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f855b5a0f | ||
|
|
0d51a194bc | ||
|
|
1977fb3a11 | ||
|
|
4606c01085 | ||
|
|
c1a700d719 |
@@ -98,7 +98,7 @@ When asked to prepare a release (e.g. "prepare v0.12.0"):
|
||||
- An API reference page at `apps/web/app/(main)/docs/api/<name>/page.mdx`
|
||||
- An entry in `apps/web/lib/page-titles.ts` and `apps/web/lib/docs-navigation.ts`
|
||||
- An entry in the docs-chat system prompt (`apps/web/app/api/docs-chat/route.ts`)
|
||||
- A skill at `skills/json-render-<name>/SKILL.md`
|
||||
- A skill at `skills/<name>/SKILL.md`
|
||||
- A `packages/<name>/README.md`
|
||||
6. **Run `pnpm type-check`** after all changes to verify nothing is broken
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { pageMetadata } from "@/lib/page-metadata"
|
||||
|
||||
export const metadata = pageMetadata("docs/skills")
|
||||
|
||||
# Skills
|
||||
|
||||
json-render ships with skills that teach AI coding agents how to use each package. Install a skill and your agent in Cursor, Claude Code, or Codex can generate json-render UIs without manual guidance.
|
||||
|
||||
## Available Skills
|
||||
|
||||
- **core** — Core schemas, catalogs, and AI prompt generation.
|
||||
- **react** — React renderer that turns JSON specs into React component trees.
|
||||
- **react-pdf** — PDF renderer using `@react-pdf/renderer`.
|
||||
- **react-email** — Email renderer that produces HTML or plain-text emails.
|
||||
- **react-native** — React Native renderer for native mobile UIs.
|
||||
- **shadcn** — Pre-built shadcn/ui components (Radix UI + Tailwind).
|
||||
- **image** — Image renderer that turns JSON specs into SVG and PNG via Satori.
|
||||
- **remotion** — Remotion renderer for video generation from JSON timeline specs.
|
||||
- **vue** — Vue 3 renderer for Vue component trees.
|
||||
- **svelte** — Svelte 5 renderer for Svelte component trees.
|
||||
- **codegen** — Code generation utilities for building custom exporters.
|
||||
- **mcp** — MCP Apps integration for Claude, ChatGPT, Cursor, and VS Code.
|
||||
- **redux** — Redux adapter for json-render's `StateStore` interface.
|
||||
- **zustand** — Zustand adapter for json-render's `StateStore` interface.
|
||||
- **jotai** — Jotai adapter for json-render's `StateStore` interface.
|
||||
- **xstate** — XState Store adapter for json-render's `StateStore` interface.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx skills add vercel-labs/json-render --skill core
|
||||
npx skills add vercel-labs/json-render --skill react
|
||||
npx skills add vercel-labs/json-render --skill react-pdf
|
||||
npx skills add vercel-labs/json-render --skill react-email
|
||||
npx skills add vercel-labs/json-render --skill react-native
|
||||
npx skills add vercel-labs/json-render --skill shadcn
|
||||
npx skills add vercel-labs/json-render --skill image
|
||||
npx skills add vercel-labs/json-render --skill remotion
|
||||
npx skills add vercel-labs/json-render --skill vue
|
||||
npx skills add vercel-labs/json-render --skill svelte
|
||||
npx skills add vercel-labs/json-render --skill codegen
|
||||
npx skills add vercel-labs/json-render --skill mcp
|
||||
npx skills add vercel-labs/json-render --skill redux
|
||||
npx skills add vercel-labs/json-render --skill zustand
|
||||
npx skills add vercel-labs/json-render --skill jotai
|
||||
npx skills add vercel-labs/json-render --skill xstate
|
||||
```
|
||||
|
||||
After installing, your AI agent will automatically activate the right skill when it encounters a matching request.
|
||||
|
||||
## core
|
||||
|
||||
The foundational skill. Teaches agents how to define catalogs, create schemas, build specs, and generate AI prompts. This is the starting point for any json-render project and covers `defineCatalog`, `defineSchema`, `specSchema`, `toPrompt`, and the full spec format.
|
||||
|
||||
## react
|
||||
|
||||
Teaches agents how to render JSON specs as React component trees using `JsonRender`, `JsonRenderClient`, and `useJsonRender`. Covers custom component registries, client-side interactivity, state management, and streaming integration.
|
||||
|
||||
## react-pdf
|
||||
|
||||
Teaches agents how to generate PDFs from JSON specs using `@react-pdf/renderer`. Covers the PDF-specific component registry, page layout, and styling.
|
||||
|
||||
## react-email
|
||||
|
||||
Teaches agents how to render JSON specs as HTML or plain-text emails using React Email components. Covers the email-specific registry and rendering pipeline.
|
||||
|
||||
## react-native
|
||||
|
||||
Teaches agents how to render JSON specs as native mobile UIs with React Native. Covers the native component registry and platform-specific considerations.
|
||||
|
||||
## shadcn
|
||||
|
||||
Teaches agents how to use the pre-built shadcn/ui component registry with json-render. Includes Radix UI primitives, Tailwind styling, and the full set of available shadcn components.
|
||||
|
||||
## image
|
||||
|
||||
Teaches agents how to turn JSON specs into SVG and PNG images using Satori. Covers the image-specific registry, dimensions, fonts, and rendering options.
|
||||
|
||||
## remotion
|
||||
|
||||
Teaches agents how to generate videos from JSON timeline specs using Remotion. Covers compositions, sequences, timeline structure, and video rendering.
|
||||
|
||||
## vue
|
||||
|
||||
Teaches agents how to render JSON specs as Vue 3 component trees. Covers the Vue renderer API, custom component registries, and reactivity integration.
|
||||
|
||||
## svelte
|
||||
|
||||
Teaches agents how to render JSON specs as Svelte 5 component trees. Covers the Svelte renderer API and component registration.
|
||||
|
||||
## codegen
|
||||
|
||||
Teaches agents how to use code generation utilities to export UI specs as framework-specific source code. Covers the codegen pipeline and custom exporter creation.
|
||||
|
||||
## mcp
|
||||
|
||||
Teaches agents how to build MCP Apps that serve json-render UIs inside AI tools like Claude, ChatGPT, Cursor, and VS Code. Covers MCP server setup, tool definitions, and UI streaming.
|
||||
|
||||
## redux
|
||||
|
||||
Teaches agents how to connect a Redux store to json-render's `StateStore` interface for state-driven UIs.
|
||||
|
||||
## zustand
|
||||
|
||||
Teaches agents how to connect a Zustand store to json-render's `StateStore` interface for lightweight state management.
|
||||
|
||||
## jotai
|
||||
|
||||
Teaches agents how to connect Jotai atoms to json-render's `StateStore` interface for atomic state management.
|
||||
|
||||
## xstate
|
||||
|
||||
Teaches agents how to connect an XState Store to json-render's `StateStore` interface for state-machine-driven UIs.
|
||||
|
||||
## Source
|
||||
|
||||
All skill files are in the [`skills/`](https://github.com/vercel-labs/json-render/tree/main/skills) directory of the repository.
|
||||
@@ -17,6 +17,7 @@ const SYSTEM_PROMPT = `You are a helpful documentation assistant for json-render
|
||||
GitHub repository: https://github.com/vercel-labs/json-render
|
||||
Documentation: https://json-render.dev/docs
|
||||
npm packages: @json-render/core, @json-render/react, @json-render/vue, @json-render/svelte, @json-render/shadcn, @json-render/react-native, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen, @json-render/mcp, @json-render/redux, @json-render/zustand, @json-render/jotai, @json-render/xstate
|
||||
Skills: json-render ships AI agent skills that teach coding agents how to use each package. Install with "npx skills add vercel-labs/json-render --skill <name>". Available skills: core, react, react-pdf, react-email, react-native, shadcn, image, remotion, vue, svelte, codegen, mcp, redux, zustand, jotai, xstate. See /docs/skills for details.
|
||||
|
||||
You have access to the full json-render documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/docs/ directory.
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export const docsNavigation: NavSection[] = [
|
||||
{ title: "Introduction", href: "/docs" },
|
||||
{ title: "Installation", href: "/docs/installation" },
|
||||
{ title: "Quick Start", href: "/docs/quick-start" },
|
||||
{ title: "Skills", href: "/docs/skills" },
|
||||
{ title: "Migration Guide", href: "/docs/migration" },
|
||||
{ title: "Changelog", href: "/docs/changelog" },
|
||||
],
|
||||
|
||||
@@ -37,6 +37,7 @@ export const PAGE_TITLES: Record<string, string> = {
|
||||
"docs/ag-ui": "AG-UI Integration",
|
||||
"docs/migration": "Migration Guide",
|
||||
"docs/changelog": "Changelog",
|
||||
"docs/skills": "Skills",
|
||||
|
||||
// API references
|
||||
"docs/api/core": "@json-render/core API",
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"@json-render/shadcn": "workspace:*",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,6 +5,9 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss(), viteSingleFile()],
|
||||
resolve: {
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -160,6 +160,7 @@ export { defineSchema } from "./schema";
|
||||
// Catalog — defines the vocabulary (what components and actions are available)
|
||||
export type {
|
||||
Catalog,
|
||||
JsonSchemaOptions,
|
||||
PromptOptions,
|
||||
PromptContext,
|
||||
SpecValidationResult,
|
||||
|
||||
@@ -560,6 +560,194 @@ describe("catalog.jsonSchema", () => {
|
||||
expect(jsonSchema).not.toBeNull();
|
||||
expect(typeof jsonSchema).toBe("object");
|
||||
});
|
||||
|
||||
describe("strict mode (LLM structured output compatible)", () => {
|
||||
function hasNoPropertyNames(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
if ("propertyNames" in obj) return false;
|
||||
return Object.values(obj).every(hasNoPropertyNames);
|
||||
}
|
||||
|
||||
function allObjectsHaveAdditionalPropertiesFalse(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const record = obj as Record<string, unknown>;
|
||||
if (record.type === "object") {
|
||||
if (record.additionalProperties !== false) return false;
|
||||
}
|
||||
return Object.values(record).every(
|
||||
allObjectsHaveAdditionalPropertiesFalse,
|
||||
);
|
||||
}
|
||||
|
||||
function allObjectPropertiesRequired(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const record = obj as Record<string, unknown>;
|
||||
if (
|
||||
record.type === "object" &&
|
||||
record.properties &&
|
||||
typeof record.properties === "object"
|
||||
) {
|
||||
const propKeys = Object.keys(record.properties);
|
||||
const required = (record.required as string[]) ?? [];
|
||||
if (!propKeys.every((k) => required.includes(k))) return false;
|
||||
}
|
||||
return Object.values(record).every(allObjectPropertiesRequired);
|
||||
}
|
||||
|
||||
it("sets additionalProperties: false on all nested objects", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(allObjectsHaveAdditionalPropertiesFalse(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not emit propertyNames", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(hasNoPropertyNames(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("lists all properties in required (optional uses nullable)", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({ strict: true });
|
||||
expect(allObjectPropertiesRequired(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("converts record types without additionalProperties schema value", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const schema = catalog.jsonSchema({
|
||||
strict: true,
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
// Walk the schema and ensure no additionalProperties is set to a non-false value
|
||||
function noAdditionalPropertiesSchema(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) return true;
|
||||
const rec = obj as Record<string, unknown>;
|
||||
if (
|
||||
"additionalProperties" in rec &&
|
||||
rec.additionalProperties !== false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(rec).every(noAdditionalPropertiesSchema);
|
||||
}
|
||||
expect(noAdditionalPropertiesSchema(schema)).toBe(true);
|
||||
});
|
||||
|
||||
it("wraps optional properties with anyOf nullable", () => {
|
||||
// Use a schema where propsOf resolves to a single component's props
|
||||
// (no record wrapper around the props) so the optional anyOf handling
|
||||
// is directly visible in the JSON Schema output.
|
||||
const flatSchema = defineSchema((s) => ({
|
||||
spec: s.object({
|
||||
component: s.object({
|
||||
type: s.ref("catalog.components"),
|
||||
props: s.propsOf("catalog.components"),
|
||||
}),
|
||||
}),
|
||||
catalog: s.object({
|
||||
components: s.map({
|
||||
props: s.zod(),
|
||||
description: s.string(),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const catalog = defineCatalog(flatSchema, {
|
||||
components: {
|
||||
Card: {
|
||||
props: z.object({
|
||||
heading: z.string(),
|
||||
caption: z.string().optional(),
|
||||
}),
|
||||
description: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schema = catalog.jsonSchema({ strict: true }) as {
|
||||
properties: {
|
||||
component: {
|
||||
properties: { props: Record<string, unknown> };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const propsSchema = schema.properties.component.properties.props;
|
||||
|
||||
// caption is optional – in strict mode it must be in `required`
|
||||
// and wrapped in anyOf with null
|
||||
const captionSchema = (
|
||||
propsSchema as {
|
||||
properties: { caption: Record<string, unknown> };
|
||||
}
|
||||
).properties.caption;
|
||||
expect(captionSchema).toEqual({
|
||||
anyOf: [{ type: "string" }, { type: "null" }],
|
||||
});
|
||||
|
||||
const propsRequired = (propsSchema as { required: string[] }).required;
|
||||
expect(propsRequired).toContain("heading");
|
||||
expect(propsRequired).toContain("caption");
|
||||
});
|
||||
|
||||
it("does not affect default (non-strict) output", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({ content: z.string() }),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const defaultSchema = catalog.jsonSchema();
|
||||
const defaultSchema2 = catalog.jsonSchema({ strict: false });
|
||||
expect(defaultSchema).toEqual(defaultSchema2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
|
||||
+147
-34
@@ -92,7 +92,7 @@ export interface Catalog<
|
||||
/** Generate system prompt for AI */
|
||||
prompt(options?: PromptOptions): string;
|
||||
/** Export as JSON Schema for structured outputs */
|
||||
jsonSchema(): object;
|
||||
jsonSchema(options?: JsonSchemaOptions): object;
|
||||
/** Validate a spec against this catalog */
|
||||
validate(spec: unknown): SpecValidationResult<InferSpec<TDef, TCatalog>>;
|
||||
/** Get the Zod schema for the spec */
|
||||
@@ -101,6 +101,28 @@ export interface Catalog<
|
||||
readonly _specType: InferSpec<TDef, TCatalog>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for JSON Schema export
|
||||
*/
|
||||
export interface JsonSchemaOptions {
|
||||
/**
|
||||
* When true, produces a strict JSON Schema subset compatible with
|
||||
* LLM structured output APIs (OpenAI, Google Gemini, Anthropic, etc.).
|
||||
* This ensures:
|
||||
* - `additionalProperties: false` on every object
|
||||
* - All object properties listed in `required` (optionals use nullable types)
|
||||
* - Record/map types converted to fixed-key objects
|
||||
*
|
||||
* **Limitation:** Record types (dynamic-key maps) cannot be represented in
|
||||
* strict JSON Schema because `additionalProperties` must be `false`. They
|
||||
* are emitted as `{ type: "object", properties: {}, additionalProperties: false }`.
|
||||
* The LLM prompt (via `catalog.prompt()`) still describes the full structure,
|
||||
* so the model can produce valid output even though the JSON Schema for
|
||||
* record entries is opaque.
|
||||
*/
|
||||
strict?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt generation options
|
||||
*/
|
||||
@@ -411,8 +433,8 @@ function createCatalogFromSchema<TDef extends SchemaDefinition, TCatalog>(
|
||||
return generatePrompt(this, options);
|
||||
},
|
||||
|
||||
jsonSchema(): object {
|
||||
return zodToJsonSchema(zodSchema);
|
||||
jsonSchema(options: JsonSchemaOptions = {}): object {
|
||||
return zodToJsonSchema(zodSchema, options.strict ?? false);
|
||||
},
|
||||
|
||||
validate(spec: unknown): SpecValidationResult<InferSpec<TDef, TCatalog>> {
|
||||
@@ -1303,44 +1325,111 @@ function formatZodType(schema: z.ZodType): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Zod schema to JSON Schema
|
||||
* Resolve the Zod type name from a schema's internal definition.
|
||||
* Supports both Zod 3 (`_def.typeName`) and Zod 4 (`_def.type`).
|
||||
*/
|
||||
function zodToJsonSchema(schema: z.ZodType): object {
|
||||
// Simplified JSON Schema conversion
|
||||
const def = schema._def as unknown as Record<string, unknown>;
|
||||
const typeName = (def.typeName as string) ?? "";
|
||||
function zodTypeName(def: Record<string, unknown>): string {
|
||||
// Zod 4 uses _def.type as a plain string (e.g. "string", "object")
|
||||
if (typeof def.type === "string") return def.type;
|
||||
// Zod 3 uses _def.typeName (e.g. "ZodString", "ZodObject")
|
||||
if (typeof def.typeName === "string") return def.typeName;
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (typeName) {
|
||||
case "ZodString":
|
||||
/**
|
||||
* Normalise a Zod type name to a canonical lowercase form.
|
||||
* Handles both Zod 3 ("ZodString") and Zod 4 ("string") conventions.
|
||||
*/
|
||||
function normalizeTypeName(raw: string): string {
|
||||
// Zod 3 names start with "Zod", e.g. "ZodString" → "string"
|
||||
if (raw.startsWith("Zod")) {
|
||||
return raw.slice(3).toLowerCase();
|
||||
}
|
||||
return raw.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Zod schema to JSON Schema.
|
||||
*
|
||||
* When `strict` is true the output conforms to the JSON Schema subset required
|
||||
* by LLM structured output APIs (no `propertyNames`, `additionalProperties: false`
|
||||
* everywhere, all properties listed in `required`).
|
||||
*/
|
||||
function zodToJsonSchema(schema: z.ZodType, strict = false): object {
|
||||
const def = schema._def as unknown as Record<string, unknown>;
|
||||
const kind = normalizeTypeName(zodTypeName(def));
|
||||
|
||||
switch (kind) {
|
||||
case "string":
|
||||
return { type: "string" };
|
||||
case "ZodNumber":
|
||||
case "number":
|
||||
return { type: "number" };
|
||||
case "ZodBoolean":
|
||||
case "boolean":
|
||||
return { type: "boolean" };
|
||||
case "ZodLiteral":
|
||||
return { const: def.value };
|
||||
case "ZodEnum":
|
||||
return { enum: def.values };
|
||||
case "ZodArray": {
|
||||
const inner = def.type as z.ZodType | undefined;
|
||||
case "literal": {
|
||||
// Zod 4: _def.values (array), Zod 3: _def.value (single)
|
||||
const values = def.values as unknown[] | undefined;
|
||||
const value = values ? values[0] : def.value;
|
||||
return { const: value };
|
||||
}
|
||||
case "enum": {
|
||||
// Zod 4: _def.entries (object { a:"a", b:"b" }), Zod 3: _def.values (string[])
|
||||
const entries = def.entries as Record<string, string> | undefined;
|
||||
const values = entries
|
||||
? Object.values(entries)
|
||||
: (def.values as string[] | undefined);
|
||||
return { enum: values ?? [] };
|
||||
}
|
||||
case "array": {
|
||||
// Zod 4: _def.element, Zod 3: _def.type
|
||||
const inner = (def.element ?? def.type) as z.ZodType | undefined;
|
||||
return {
|
||||
type: "array",
|
||||
items: inner ? zodToJsonSchema(inner) : {},
|
||||
items: inner ? zodToJsonSchema(inner, strict) : {},
|
||||
};
|
||||
}
|
||||
case "ZodObject": {
|
||||
const shape = (def.shape as () => Record<string, z.ZodType>)?.();
|
||||
if (!shape) return { type: "object" };
|
||||
case "object": {
|
||||
// Zod 4: _def.shape is an object, Zod 3: _def.shape is a function
|
||||
const rawShape = def.shape;
|
||||
const shape: Record<string, z.ZodType> | undefined =
|
||||
typeof rawShape === "function"
|
||||
? (rawShape as () => Record<string, z.ZodType>)()
|
||||
: (rawShape as Record<string, z.ZodType> | undefined);
|
||||
|
||||
if (!shape) {
|
||||
if (strict) {
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
return { type: "object" };
|
||||
}
|
||||
|
||||
const properties: Record<string, object> = {};
|
||||
const required: string[] = [];
|
||||
for (const [key, value] of Object.entries(shape)) {
|
||||
properties[key] = zodToJsonSchema(value);
|
||||
const innerDef = value._def as unknown as Record<string, unknown>;
|
||||
if (
|
||||
innerDef.typeName !== "ZodOptional" &&
|
||||
innerDef.typeName !== "ZodNullable"
|
||||
) {
|
||||
const innerKind = normalizeTypeName(zodTypeName(innerDef));
|
||||
const isOptional = innerKind === "optional" || innerKind === "nullable";
|
||||
|
||||
if (strict) {
|
||||
// In strict mode, all properties must be in required.
|
||||
// Optional properties are represented as nullable types.
|
||||
required.push(key);
|
||||
if (isOptional) {
|
||||
const unwrapped = zodToJsonSchema(value, strict);
|
||||
properties[key] = { anyOf: [unwrapped, { type: "null" }] };
|
||||
} else {
|
||||
properties[key] = zodToJsonSchema(value, strict);
|
||||
}
|
||||
} else {
|
||||
properties[key] = zodToJsonSchema(value);
|
||||
if (!isOptional) {
|
||||
required.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -1350,23 +1439,47 @@ function zodToJsonSchema(schema: z.ZodType): object {
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
case "ZodRecord": {
|
||||
case "record": {
|
||||
const valueType = def.valueType as z.ZodType | undefined;
|
||||
if (strict) {
|
||||
// LLM strict schemas require `additionalProperties: false` and do not
|
||||
// permit a schema value for `additionalProperties`. Since record types
|
||||
// have dynamic keys that cannot be enumerated at schema-generation time,
|
||||
// we emit an opaque object. The LLM prompt still describes the expected
|
||||
// structure so the model can produce valid output.
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
additionalProperties: valueType ? zodToJsonSchema(valueType) : true,
|
||||
};
|
||||
}
|
||||
case "ZodOptional":
|
||||
case "ZodNullable": {
|
||||
case "optional":
|
||||
case "nullable": {
|
||||
const inner = def.innerType as z.ZodType | undefined;
|
||||
return inner ? zodToJsonSchema(inner) : {};
|
||||
return inner ? zodToJsonSchema(inner, strict) : {};
|
||||
}
|
||||
case "ZodUnion": {
|
||||
case "union": {
|
||||
const options = def.options as z.ZodType[] | undefined;
|
||||
return options ? { anyOf: options.map(zodToJsonSchema) } : {};
|
||||
return options
|
||||
? { anyOf: options.map((o) => zodToJsonSchema(o, strict)) }
|
||||
: {};
|
||||
}
|
||||
case "ZodAny":
|
||||
case "any":
|
||||
case "unknown":
|
||||
if (strict) {
|
||||
return {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
"types": "./dist/app.d.ts",
|
||||
"import": "./dist/app.mjs",
|
||||
"require": "./dist/app.js"
|
||||
},
|
||||
"./build-app-html": {
|
||||
"types": "./dist/build-app-html-entry.d.ts",
|
||||
"import": "./dist/build-app-html-entry.mjs",
|
||||
"require": "./dist/build-app-html-entry.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Server-side utility for building self-contained MCP App HTML.
|
||||
*
|
||||
* This entry point does NOT depend on React and is safe to import
|
||||
* in Node.js / server environments.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { buildAppHtml } from "./build-app-html.js";
|
||||
export type { BuildAppHtmlOptions } from "./build-app-html.js";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts", "src/app.ts"],
|
||||
entry: ["src/index.ts", "src/app.ts", "src/build-app-html-entry.ts"],
|
||||
format: ["cjs", "esm"],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
|
||||
Generated
+9
-48
@@ -536,16 +536,16 @@ importers:
|
||||
version: link:../../packages/shadcn
|
||||
'@modelcontextprotocol/ext-apps':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6)
|
||||
version: 1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.27.1
|
||||
version: 1.27.1(zod@4.3.6)
|
||||
react:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3(react@19.2.3)
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.4(react@19.2.4)
|
||||
zod:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
@@ -600,7 +600,7 @@ importers:
|
||||
version: 0.563.0(react@19.2.4)
|
||||
next:
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
radix-ui:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -947,7 +947,7 @@ importers:
|
||||
version: 6.0.103(zod@4.3.6)
|
||||
next:
|
||||
specifier: ^16.1.6
|
||||
version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react:
|
||||
specifier: ^19.1.0
|
||||
version: 19.2.4
|
||||
@@ -15019,14 +15019,6 @@ snapshots:
|
||||
|
||||
'@mixmark-io/domino@2.2.0': {}
|
||||
|
||||
'@modelcontextprotocol/ext-apps@1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
'@modelcontextprotocol/ext-apps@1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6)
|
||||
@@ -17941,7 +17933,7 @@ snapshots:
|
||||
'@stripe/ui-extension-tools@0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))':
|
||||
dependencies:
|
||||
'@types/jest': 28.1.8
|
||||
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
|
||||
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
|
||||
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
|
||||
eslint: 8.57.1
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||
@@ -18467,7 +18459,7 @@ snapshots:
|
||||
'@types/node': 22.19.6
|
||||
optional: true
|
||||
|
||||
'@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
|
||||
'@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
|
||||
@@ -23804,32 +23796,6 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@next/env': 16.1.6
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.9.14
|
||||
caniuse-lite: 1.0.30001764
|
||||
postcss: 8.4.31
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
styled-jsx: 5.1.6(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.1.6
|
||||
'@next/swc-darwin-x64': 16.1.6
|
||||
'@next/swc-linux-arm64-gnu': 16.1.6
|
||||
'@next/swc-linux-arm64-musl': 16.1.6
|
||||
'@next/swc-linux-x64-gnu': 16.1.6
|
||||
'@next/swc-linux-x64-musl': 16.1.6
|
||||
'@next/swc-win32-arm64-msvc': 16.1.6
|
||||
'@next/swc-win32-x64-msvc': 16.1.6
|
||||
'@opentelemetry/api': 1.9.0
|
||||
babel-plugin-react-compiler: 1.0.0
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
node-abi@3.87.0:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
@@ -25904,11 +25870,6 @@ snapshots:
|
||||
client-only: 0.0.1
|
||||
react: 19.2.3
|
||||
|
||||
styled-jsx@5.1.6(react@19.2.4):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.2.4
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-codegen
|
||||
name: codegen
|
||||
description: Code generation utilities for json-render. Use when generating code from UI specs, building custom code exporters, traversing specs, or serializing props for @json-render/codegen.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-core
|
||||
name: core
|
||||
description: Core package for defining schemas, catalogs, and AI prompt generation for json-render. Use when working with @json-render/core, defining schemas, creating catalogs, or building JSON specs for UI/video generation.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-image
|
||||
name: image
|
||||
description: Image renderer for json-render that turns JSON specs into SVG and PNG images via Satori. Use when working with @json-render/image, generating OG images from JSON, creating social cards, or rendering AI-generated image specs.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-jotai
|
||||
name: jotai
|
||||
description: Jotai adapter for json-render's StateStore interface. Use when integrating json-render with Jotai for state management via @json-render/jotai.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-mcp
|
||||
name: mcp
|
||||
description: MCP Apps integration for json-render. Use when building MCP servers that render interactive UIs in Claude, ChatGPT, Cursor, or VS Code, or when integrating json-render with the Model Context Protocol.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-react-email
|
||||
name: react-email
|
||||
description: React Email renderer for json-render that turns JSON specs into HTML or plain-text emails using @react-email/components and @react-email/render. Use when working with @json-render/react-email, building transactional or marketing emails from JSON, creating email catalogs, rendering AI-generated email specs, or when the user mentions react-email, HTML email, or transactional email.
|
||||
metadata:
|
||||
tags: react-email, email, json-render, html email, transactional email
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-react-native
|
||||
name: react-native
|
||||
description: React Native renderer for json-render that turns JSON specs into native mobile UIs. Use when working with @json-render/react-native, building React Native UIs from JSON, creating mobile component catalogs, or rendering AI-generated specs on mobile.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-react-pdf
|
||||
name: react-pdf
|
||||
description: React PDF renderer for json-render. Use when generating PDF documents from JSON specs, working with @json-render/react-pdf, or rendering specs to PDF buffers/streams/files.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-react
|
||||
name: react
|
||||
description: React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-redux
|
||||
name: redux
|
||||
description: Redux adapter for json-render's StateStore interface. Use when integrating json-render with Redux or Redux Toolkit for state management via @json-render/redux.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-remotion
|
||||
name: remotion
|
||||
description: Remotion renderer for json-render that turns JSON timeline specs into videos. Use when working with @json-render/remotion, building video compositions from JSON, creating video catalogs, or rendering AI-generated video timelines.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-shadcn
|
||||
name: shadcn
|
||||
description: Pre-built shadcn/ui components for json-render. Use when working with @json-render/shadcn, adding standard UI components to a catalog, or building web UIs with Radix UI + Tailwind CSS components.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-svelte
|
||||
name: svelte
|
||||
description: Svelte 5 renderer for json-render that turns JSON specs into Svelte component trees. Use when working with @json-render/svelte, building Svelte UIs from JSON, creating component catalogs, or rendering AI-generated specs.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-vue
|
||||
name: vue
|
||||
description: Vue 3 renderer for json-render. Use when building Vue UIs from JSON specs, working with @json-render/vue, defining Vue component registries, or rendering AI-generated specs in Vue.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-xstate
|
||||
name: xstate
|
||||
description: XState Store adapter for json-render's StateStore interface. Use when integrating json-render with @xstate/store for state management via @json-render/xstate.
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: json-render-zustand
|
||||
name: zustand
|
||||
description: Zustand adapter for json-render's StateStore interface. Use when integrating json-render with Zustand for state management via @json-render/zustand.
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user