core について
@json-render/core は、json-render アプリの構築に使う TypeScript の基盤パッケージです。カタログや AI 生成 UI の仕様を扱う開発者向けに、仕様ストリーミングや検証、状態式、実験的な意思決定モデル合成 API を提供します。
主な機能
- 01スキーマとコンポーネントカタログの定義
- 02プロンプト生成と仕様のストリーミング
- 03状態バインディングと表示条件の解決
- 04仕様構造の検証と修正
- 05意思決定モデル仕様の合成と編集
活用シーン
- 01生成 UI 用カタログの定義
- 02JSONL 仕様ストリームの逐次処理
- 03生成された UI 仕様の検証
- 04候補からの UI 合成や改訂
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ json-render --skill coreインストール後、Claude Code で/coreを入力して読み込まれたか確認します。
詳細
@json-render/core
スキーマ定義、カタログ作成、仕様のストリーミングのためのコアパッケージです。
主な概念
- スキーマ:仕様とカタログの構造を定義します(
defineSchemaを使用) - カタログ:コンポーネント名/アクション名とその定義を対応付けます(
defineCatalogを使用) - 仕様:スキーマに準拠したAIからのJSON出力です
- SpecStream:仕様を段階的に構築するためのJSONLストリーミング形式です
実験的な意思決定モデルの合成
意思決定モデルを合成するには、@json-render/coreからexperimental_composeSpecとexperimental_createEvaluatorをインポートしてください。これらのAPIはv0.21.0以降で利用できます。バージョンを厳密に固定し、json-renderパッケージのバージョンを統一してください。実験的なエクスポートとExperimental_型は、どのリリースでも変更される可能性があります。
- Gateway エバリュエーターをサーバー側で
{ model: "typesafe-ai/jev", apiKey: process.env.AI_GATEWAY_API_KEY! }を指定して実行します。プレーンなモデル識別子が必要です。Jev は現在の例です。プロバイダーのコンストラクターをインポートしないでください。 experimental_composeSpec({ catalog, candidates, prompt, evaluate, initialState, signal })を呼び出します。これは非同期ジェネレーターです。step.specのスナップショットを既存のレンダラーにストリーミングし、complete.stopReason(finish、limit、unavailable)を確認してください。エラーとキャンセルは例外をスローします。最後のスナップショットを部分的な UI として保持してください。- 新しいツリーはデフォルトで
strategy: "batch"になります。1 回目の評価でルート/メンバーシップを選択し、必要に応じて 2 回目で選択された要素を配置します。最初のスナップショットには、ルートのデフォルト/最初のスロットの下に、カタログ順で選択されたコンテンツが含まれます。リソースバリアントは 1 つの排他的な質問を共有します。繰り返し回数にはルートが含まれます。ルートの選択は、そのレシピ/リソースに対する競合する投機的メンバーシップよりも優先されます。等しい兄弟位置はカタログ順を維持します。結合されたレイアウトは公開前に検証されます。循環または過剰な深さは例外をスローします。maxElementsはバッチ作成を上限設定します(デフォルト 32)。制限により切り捨てられた選択、または必須のレイアウト呼び出しが欠落している場合はlimitを返します。レガシーなnext/parentアダプターまたは順次作成にはstrategy: "sequential"を使用してください。編集は順次のままです。 - バッチ処理されたトレースステップは
choice: "select" | "layout"とanswersレコードを使用します。各トレースを 1 回の評価としてカウントし、そのトークンとレイテンシーも一度だけ含めます。カスタムエバリュエーターは、提示されたすべての質問に回答する必要があります。名前/選択肢は不透明であり、root/select_*、次にバッチの場合はparent_*/order_*が含まれます。 - フォローアップ編集では、選択されたバージョンを
initialSpecとして渡します。これは複製および検証されます。エバリュエーターは、非ルートサブツリーの追加、置換、削除、または移動/並べ替えを行うことができます。変更されていない ID、バインディング、状態は保持されます。オプションのelementDescriptionsは、生の props/state を公開せずに識別用の説明を共有します。initialStateはシード状態を上書きします。シードは、カタログ、式サブセット、および深さ制限内の有効なツリーである必要があります。一致するレシピは使用量/リソース制限を消費します。削除/置換はそれらを解放します。置換/移動は 2 回の評価(ターゲットの選択、次にレシピ/宛先)を使用し、それぞれが予算に対してカウントされます。操作キーと位置キーは不透明として扱ってください。 - アトミックな候補を
{ id, description, element: { type, props, on?, visible? }, root?, maxUses?, resource? }で提供します。カタログだけでは不十分です。アプリは値とバインディングレシピを提供する必要があります。Jev は要素と親スロットを選択し、自由形式のテキストやコードは決して選択しません。アクションを実行することもありません。 - 候補はページテンプレートではなく、設定済みのコンポーネントインスタンスです。それらを現在のアプリのレコード/操作から構築するか、props を
initialStateにバインドしてください。チャートタイプ、フィールド設定、レイアウトバリアントに対して明示的な代替案を提供してください。モデルはそれらのオプション内でグループ化と順序を選択します。プロンプトで必須セクションを指定してください。構造的な有効性は意味的な完全性を意味しません。 - V1 はフラットな Spec カタログ、名前付きスロット、リテラル、
$state、$bindState、および状態の可視性をサポートします。事前構築された子、repeat/watch、computed/template/conditional props、カスタムディレクティブはサポートしません。成功/エラーコールバックは許可されたアクションを参照する必要があります。イベントはコンポーネントカタログで宣言する必要があります。 - props とアクションパラメーターは、スキーマ変換/デフォルトを適用せずに初期状態に対して検証されます。有効な初期値を提供し、実行時にアクション呼び出しを検証/認可してください。パラメータースキーマを持たないビルトインは名前検証のみを受けます。
rootはデフォルトで true、maxUsesはデフォルトで 1、共有resource値は代替案を相互に排他的にします。デフォルト: 32 回の評価(終端呼び出しを含む。バッチに対する追加の finish 呼び出しはなし)、深さ 8、呼び出しごとの Gateway タイムアウト 10 秒。全体的な中止シグナルを提供してください。- 候補の説明、プロンプト、指示、トポロジー、および明示的な
contextはエバリュエーターに送信されます。初期状態と生の props/バインディング値は自動的に送信されません。 - カスタムプロバイダーの場合は
Experimental_CompositionEvaluatorを実装してください。{ state, questions, signal }を受け取り、{ answers: { [question]: { choice, confidence? } }, usage?: { inputTokens? } }を返します。提示された基準キーのみを返してください。 アプリの統合方法とインストール手順については、packages/core/README.mdと/docs/jevを参照してください。ウェブプレイグラウンドは利用例であり、API の依存関係ではありません。
スキーマの定義
import { defineSchema } from "@json-render/core";
export const schema = defineSchema((s) => ({
spec: s.object({
// Define spec structure
}),
catalog: s.object({
components: s.map({
props: s.zod(),
description: s.string(),
}),
}),
}), {
promptTemplate: myPromptTemplate, // Optional custom AI prompt
});
カタログの作成
import { defineCatalog } from "@json-render/core";
import { schema } from "./schema";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "Clickable button component",
},
},
});
AI プロンプトの生成
const systemPrompt = catalog.prompt(); // Uses schema's promptTemplate
const systemPrompt = catalog.prompt({ customRules: ["Rule 1", "Rule 2"] });
SpecStream ユーティリティ
AI のレスポンス(JSONL パッチ)をストリーミングする場合:
import { createSpecStreamCompiler } from "@json-render/core";
const compiler = createSpecStreamCompiler<MySpec>();
// Process streaming chunks
const { result, newPatches } = compiler.push(chunk);
// Get final result
const finalSpec = compiler.getResult();
アクションハンドラーでのフォーム値
findFormValue("email", params, state) を使用すると、直接のパラメーター、ドット区切りのパラメーターキー("form.email" など)、一致するフラットな状態キー、またはネストされた状態内のスラッシュ区切りパス("/form/email" など)から値を読み取れます。パラメーター値は文字どおりに扱われるため、ドットを含むメールアドレスや URL はそのまま保持されます。ネストされた状態を読み取るアクションバインディングには、{ $state: "/form/email" } を使用してください。リゾルバーはこの値をハンドラーに渡します。単独の "email" フィールド名では、ネストされた state.form.email は検索されません。
動的プロパティ式
任意のプロパティ値に動的式を指定すると、レンダリング時に評価できます。
{ "$state": "/state/key" }- 状態モデルから値を読み取ります(読み取り専用){ "$bindState": "/path" }- 双方向バインディング:状態から値を読み取り、書き戻しを有効にします。フォームコンポーネントの自然な値プロパティ(value、checked、pressed など)に使用します。{ "$bindItem": "field" }- 繰り返し項目のフィールドへの双方向バインディングです。繰り返しスコープ内で使用します。{ "$cond": <condition>, "$then": <value>, "$else": <value> }- 表示条件を評価し、該当する分岐を選択します{ "$template": "Hello, ${/user/name}!" }-${/path}参照を状態値で補間します{ "$computed": "fnName", "args": { "key": <expression> } }- 登録済み関数を、解決済みの引数で呼び出します
$cond では表示条件と同じ構文($state、eq、neq、AND を表す配列)を使用します。$then と $else 自体にも式を指定できます(再帰的)。
コンポーネントでは、双方向バインディングに statePath プロパティを使用しません。代わりに、自然な値プロパティ(例:value、checked、pressed)に { "$bindState": "/path" } を指定します。
{
"color": {
"$cond": { "$state": "/activeTab", "eq": "home" },
"$then": "#007AFF",
"$else": "#8E8E93"
},
"label": { "$template": "Welcome, ${/user/name}!" },
"fullName": {
"$computed": "fullName",
"args": {
"first": { "$state": "/form/firstName" },
"last": { "$state": "/form/lastName" }
}
}
}
import { resolvePropValue, resolveElementProps } from "@json-render/core";
const resolved = resolveElementProps(element.props, { stateModel: myState });
状態ウォッチャー
要素に watch フィールド(type/props/children と同じ階層)を宣言すると、状態値の変更時にアクションを実行できます。
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": {
"/form/country": { "action": "loadCities", "params": { "country": { "$state": "/form/country" } } }
},
"children": []
}
ウォッチャーは値が変更されたときのみ実行され、初回のレンダリングでは実行されません。
バリデーション
組み込みのバリデーション関数:required、email、url、numeric、minLength、maxLength、min、max、pattern、matches、equalTo、lessThan、greaterThan、requiredIf。
フィールド間のバリデーションでは、引数に $state 式を使用します。
import { check } from "@json-render/core";
check.required("Field is required");
check.matches("/form/password", "Passwords must match");
check.lessThan("/form/endDate", "Must be before end date");
check.greaterThan("/form/startDate", "Must be after start date");
check.requiredIf("/form/enableNotifications", "Required when enabled");
ユーザープロンプトビルダー
仕様の任意の改良と状態コンテキストを含めて、構造化されたユーザープロンプトを作成します。
import { buildUserPrompt } from "@json-render/core";
// Fresh generation
buildUserPrompt({ prompt: "create a todo app" });
// Refinement with edit modes (default: patch-only)
buildUserPrompt({ prompt: "add a toggle", currentSpec: spec, editModes: ["patch", "merge"] });
// With runtime state
buildUserPrompt({ prompt: "show data", state: { todos: [] } });
利用可能な編集モード:"patch"(RFC 6902 JSON Patch)、"merge"(RFC 7396 Merge Patch)、"diff"(統一 diff)。
仕様のバリデーション
仕様の構造を検証し、よくある問題を自動修正します。
import { validateSpec, autoFixSpec } from "@json-render/core";
const { valid, issues } = validateSpec(spec);
// issues include: missing_child, invalid_visible (malformed conditions),
// repeat_without_children, repeat_item_outside_scope, repeat_state_mismatch
const { spec: fixed, fixDetails } = autoFixSpec(spec);
// fixDetails entries are { message, lossy }. Lossless fixes relocate
// misplaced fields; lossy fixes prune dangling children references.
// In a repair loop, withhold lossy fixes until retries are exhausted:
const attempt = autoFixSpec(spec, { lossy: retriesExhausted });
表示条件
状態に基づく条件で要素の表示を制御します。VisibilityContext は { stateModel: StateModel } です。
import { visibility } from "@json-render/core";
// Syntax
{ "$state": "/path" } // truthiness
{ "$state": "/path", "not": true } // falsy
{ "$state": "/path", "eq": value } // equality
[ cond1, cond2 ] // implicit AND
// Helpers
visibility.when("/path") // { $state: "/path" }
visibility.unless("/path") // { $state: "/path", not: true }
visibility.eq("/path", val) // { $state: "/path", eq: val }
visibility.and(cond1, cond2) // { $and: [cond1, cond2] }
visibility.or(cond1, cond2) // { $or: [cond1, cond2] }
visibility.always // true
visibility.never // false
スキーマ内の組み込みアクション
スキーマでは builtInActions を宣言できます。これは実行時に常に利用でき、プロンプトに自動で挿入されるアクションです。
const schema = defineSchema(builder, {
builtInActions: [
{ name: "setState", description: "Update a value in the state model" },
],
});
これらはプロンプト内に [built-in] と表示され、defineRegistry でハンドラーを定義する必要はありません。
StateStore
StateStore インターフェースを使用すると、外部の状態管理ライブラリ(Redux、Zustand、XState など)を json-render レンダラーに組み込めます。createStateStore ファクトリーは、シンプルなインメモリ実装を作成します。
import { createStateStore, type StateStore } from "@json-render/core";
const store = createStateStore({ count: 0 });
store.get("/count"); // 0
store.set("/count", 1); // updates and notifies subscribers
store.update({ "/a": 1, "/b": 2 }); // batch update
store.subscribe(() => {
console.log(store.getSnapshot()); // { count: 1 }
});
StateStore インターフェース:get(path)、set(path, value)、update(updates)、getSnapshot()、subscribe(listener)。
主なエクスポート
| エクスポート | 用途 |
|---|---|
defineSchema | 新しいスキーマを作成します |
defineCatalog | スキーマからカタログを作成します |
createStateStore | フレームワークに依存しないインメモリの StateStore を作成します |
resolvePropValue | データに基づいて単一のプロパティ式を解決します |
resolveElementProps | 要素内のすべてのプロパティ式を解決します |
buildUserPrompt | 詳細化と状態のコンテキストを含むユーザープロンプトを作成します |
buildEditUserPrompt | 既存の仕様を編集するためのユーザープロンプトを作成します |
buildEditInstructions | 利用可能な編集モードのプロンプトセクションを生成します |
isNonEmptySpec | 仕様にルートと少なくとも1つの要素があるか確認します |
deepMergeSpec | RFC 7396 のディープマージを行います(null は削除、配列は置換、オブジェクトは再帰的に処理) |
diffToPatches | オブジェクトの差分から RFC 6902 の JSON Patch 操作を生成します |
EditMode | 型:"patch" | "merge" | "diff" |
validateSpec | 仕様の構造を検証します |
autoFixSpec | 仕様の一般的な問題を自動修正します。修正を破壊的/非破壊的に分類し、{ lossy: false } を指定すると削除を行いません |
createSpecStreamCompiler | JSONL パッチを仕様にストリーミング適用します |
createJsonRenderTransform | 混在ストリーム内のテキストと JSONL を分離する TransformStream です |
parseSpecStreamLine | 単一の JSONL 行を解析します |
applySpecStreamPatch | オブジェクトにパッチを適用します |
StateStore | 外部の状態管理を接続するためのインターフェースです |
ComputedFunction | $computed 式の関数シグネチャです |
check | 検証チェックを作成するための TypeScript ヘルパーです |
BuiltInAction | 組み込みアクション定義の型です(name + description) |
ActionBinding | アクションバインディングの型です(preventDefault フィールドを含みます) |