@json-render/core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/{chunk-4ZGEEX7K.mjs → chunk-AFLK3Q4T.mjs} +1 -1
- package/dist/chunk-AFLK3Q4T.mjs.map +1 -0
- package/dist/index.d.mts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +210 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +211 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{store-utils-DHnkfKAT.d.mts → store-utils-D98Czbil.d.mts} +6 -0
- package/dist/{store-utils-DHnkfKAT.d.ts → store-utils-D98Czbil.d.ts} +6 -0
- package/dist/store-utils.d.mts +1 -1
- package/dist/store-utils.d.ts +1 -1
- package/dist/store-utils.js.map +1 -1
- package/dist/store-utils.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-4ZGEEX7K.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -188,6 +188,22 @@ Schema options:
|
|
|
188
188
|
| `resolvePropValue(value, ctx)` | Resolve a single prop expression |
|
|
189
189
|
| `resolveElementProps(props, ctx)` | Resolve all prop expressions in an element |
|
|
190
190
|
| `PropExpression<T>` | Type for prop values that may contain expressions |
|
|
191
|
+
| `ComputedFunction` | Function signature for `$computed` expressions |
|
|
192
|
+
| `PropResolutionContext` | Context for resolving props (includes `functions` for `$computed`) |
|
|
193
|
+
|
|
194
|
+
### Validation
|
|
195
|
+
|
|
196
|
+
| Export | Purpose |
|
|
197
|
+
|--------|---------|
|
|
198
|
+
| `check.required()` | Required validation helper |
|
|
199
|
+
| `check.email()` | Email validation helper |
|
|
200
|
+
| `check.matches(path)` | Cross-field match helper |
|
|
201
|
+
| `check.equalTo(path)` | Cross-field equality helper |
|
|
202
|
+
| `check.lessThan(path)` | Cross-field less-than helper |
|
|
203
|
+
| `check.greaterThan(path)` | Cross-field greater-than helper |
|
|
204
|
+
| `check.requiredIf(path)` | Conditional required helper |
|
|
205
|
+
| `builtInValidationFunctions` | All built-in validation functions |
|
|
206
|
+
| `runValidationCheck()` | Run a single validation check |
|
|
191
207
|
|
|
192
208
|
### User Prompt
|
|
193
209
|
|
|
@@ -285,6 +301,7 @@ The official adapter packages (`@json-render/redux`, `@json-render/zustand`, `@j
|
|
|
285
301
|
| `Spec` | Base spec type |
|
|
286
302
|
| `Catalog` | Catalog type |
|
|
287
303
|
| `BuiltInAction` | Built-in action type (`name` + `description`) |
|
|
304
|
+
| `ComputedFunction` | Function signature for `$computed` expressions |
|
|
288
305
|
| `VisibilityCondition` | Visibility condition type (used by `$cond`) |
|
|
289
306
|
| `VisibilityContext` | Context for evaluating visibility and prop expressions |
|
|
290
307
|
| `SpecStreamLine` | Single patch operation |
|
|
@@ -372,6 +389,44 @@ Get the current array index inside a repeat:
|
|
|
372
389
|
|
|
373
390
|
`$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).
|
|
374
391
|
|
|
392
|
+
### Template (`$template`)
|
|
393
|
+
|
|
394
|
+
Interpolate state values into strings using `${/path}` syntax:
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"label": { "$template": "Hello, ${/user/name}! You have ${/inbox/count} messages." }
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Missing paths resolve to an empty string.
|
|
403
|
+
|
|
404
|
+
### Computed (`$computed`)
|
|
405
|
+
|
|
406
|
+
Call a registered function with resolved arguments:
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"text": {
|
|
411
|
+
"$computed": "fullName",
|
|
412
|
+
"args": {
|
|
413
|
+
"first": { "$state": "/form/firstName" },
|
|
414
|
+
"last": { "$state": "/form/lastName" }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Functions are registered in the catalog and provided at runtime via the `functions` prop on the renderer.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import type { ComputedFunction } from "@json-render/core";
|
|
424
|
+
|
|
425
|
+
const functions: Record<string, ComputedFunction> = {
|
|
426
|
+
fullName: (args) => `${args.first} ${args.last}`,
|
|
427
|
+
};
|
|
428
|
+
```
|
|
429
|
+
|
|
375
430
|
### API
|
|
376
431
|
|
|
377
432
|
```typescript
|
|
@@ -466,6 +521,65 @@ console.log(formatSpecIssues(issues));
|
|
|
466
521
|
const fixed = autoFixSpec(spec);
|
|
467
522
|
```
|
|
468
523
|
|
|
524
|
+
## State Watchers
|
|
525
|
+
|
|
526
|
+
Elements can declare a `watch` field to trigger actions when state values change. `watch` is a top-level field on the element (sibling of `type`, `props`, `children`), not inside `props`.
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"type": "Select",
|
|
531
|
+
"props": {
|
|
532
|
+
"label": "Country",
|
|
533
|
+
"value": { "$bindState": "/form/country" },
|
|
534
|
+
"options": ["US", "Canada", "UK"]
|
|
535
|
+
},
|
|
536
|
+
"watch": {
|
|
537
|
+
"/form/country": {
|
|
538
|
+
"action": "loadCities",
|
|
539
|
+
"params": { "country": { "$state": "/form/country" } }
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
"children": []
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Watchers only fire on value changes, not on initial render. Multiple action bindings per path execute sequentially.
|
|
547
|
+
|
|
548
|
+
## Validation
|
|
549
|
+
|
|
550
|
+
### Built-in Validation Functions
|
|
551
|
+
|
|
552
|
+
| Function | Description | Args |
|
|
553
|
+
|----------|-------------|------|
|
|
554
|
+
| `required` | Value must not be empty | — |
|
|
555
|
+
| `email` | Must be a valid email | — |
|
|
556
|
+
| `url` | Must be a valid URL | — |
|
|
557
|
+
| `numeric` | Must be a number | — |
|
|
558
|
+
| `minLength` | Minimum string length | `{ min: number }` |
|
|
559
|
+
| `maxLength` | Maximum string length | `{ max: number }` |
|
|
560
|
+
| `min` | Minimum numeric value | `{ min: number }` |
|
|
561
|
+
| `max` | Maximum numeric value | `{ max: number }` |
|
|
562
|
+
| `pattern` | Must match regex | `{ pattern: string }` |
|
|
563
|
+
| `matches` | Must equal another field | `{ other: { $state: "/path" } }` |
|
|
564
|
+
| `equalTo` | Alias for matches | `{ other: { $state: "/path" } }` |
|
|
565
|
+
| `lessThan` | Must be less than another field | `{ other: { $state: "/path" } }` |
|
|
566
|
+
| `greaterThan` | Must be greater than another field | `{ other: { $state: "/path" } }` |
|
|
567
|
+
| `requiredIf` | Required when condition is truthy | `{ field: { $state: "/path" } }` |
|
|
568
|
+
|
|
569
|
+
### TypeScript Helpers
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { check } from "@json-render/core";
|
|
573
|
+
|
|
574
|
+
check.required("Field is required");
|
|
575
|
+
check.email("Invalid email");
|
|
576
|
+
check.matches("/form/password", "Passwords must match");
|
|
577
|
+
check.equalTo("/form/password", "Passwords must match");
|
|
578
|
+
check.lessThan("/form/endDate", "Must be before end date");
|
|
579
|
+
check.greaterThan("/form/startDate", "Must be after start date");
|
|
580
|
+
check.requiredIf("/form/enableNotifications", "Required when notifications enabled");
|
|
581
|
+
```
|
|
582
|
+
|
|
469
583
|
## Custom Schemas
|
|
470
584
|
|
|
471
585
|
json-render supports completely different spec formats for different renderers:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/state-store.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { ActionBinding } from \"./actions\";\n\n/**\n * Dynamic value - can be a literal or a `{ $state }` reference to the state model.\n *\n * Used in action params and validation args where values can either be\n * hardcoded or resolved from state at runtime.\n */\nexport type DynamicValue<T = unknown> = T | { $state: string };\n\n/**\n * Dynamic string value\n */\nexport type DynamicString = DynamicValue<string>;\n\n/**\n * Dynamic number value\n */\nexport type DynamicNumber = DynamicValue<number>;\n\n/**\n * Dynamic boolean value\n */\nexport type DynamicBoolean = DynamicValue<boolean>;\n\n/**\n * Zod schema for dynamic values\n */\nexport const DynamicValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.object({ $state: z.string() }),\n]);\n\nexport const DynamicStringSchema = z.union([\n z.string(),\n z.object({ $state: z.string() }),\n]);\n\nexport const DynamicNumberSchema = z.union([\n z.number(),\n z.object({ $state: z.string() }),\n]);\n\nexport const DynamicBooleanSchema = z.union([\n z.boolean(),\n z.object({ $state: z.string() }),\n]);\n\n/**\n * Base UI element structure for v2\n */\nexport interface UIElement<\n T extends string = string,\n P = Record<string, unknown>,\n> {\n /** Component type from the catalog */\n type: T;\n /** Component props */\n props: P;\n /** Child element keys (flat structure) */\n children?: string[];\n /** Visibility condition */\n visible?: VisibilityCondition;\n /** Event bindings — maps event names to action bindings */\n on?: Record<string, ActionBinding | ActionBinding[]>;\n /** Repeat children once per item in a state array */\n repeat?: { statePath: string; key?: string };\n /**\n * State watchers — maps JSON Pointer state paths to action bindings.\n * When the value at a watched path changes, the bound actions fire.\n * Useful for cascading dependencies (e.g. country → city option loading).\n */\n watch?: Record<string, ActionBinding | ActionBinding[]>;\n}\n\n/**\n * Element with key and parentKey for use with flatToTree.\n * When elements are in an array (not a keyed map), key and parentKey\n * are needed to establish identity and parent-child relationships.\n */\nexport interface FlatElement<\n T extends string = string,\n P = Record<string, unknown>,\n> extends UIElement<T, P> {\n /** Unique key identifying this element */\n key: string;\n /** Parent element key (null for root) */\n parentKey?: string | null;\n}\n\n/**\n * Shared comparison operators for visibility conditions.\n *\n * Use at most ONE comparison operator per condition. If multiple are\n * provided, only the first matching one is evaluated (precedence:\n * eq > neq > gt > gte > lt > lte). With no operator, truthiness is checked.\n *\n * `not` inverts the final result of whichever operator (or truthiness\n * check) is used.\n */\ntype ComparisonOperators = {\n eq?: unknown;\n neq?: unknown;\n gt?: number | { $state: string };\n gte?: number | { $state: string };\n lt?: number | { $state: string };\n lte?: number | { $state: string };\n not?: true;\n};\n\n/**\n * A single state-based condition.\n * Resolves `$state` to a value from the state model, then applies the operator.\n * Without an operator, checks truthiness.\n *\n * When `not` is `true`, the result of the entire condition is inverted.\n * For example `{ $state: \"/count\", gt: 5, not: true }` means \"NOT greater than 5\".\n */\nexport type StateCondition = { $state: string } & ComparisonOperators;\n\n/**\n * A condition that resolves `$item` to a field on the current repeat item.\n * Only meaningful inside a `repeat` scope.\n *\n * Use `\"\"` to reference the whole item, or `\"field\"` for a specific field.\n */\nexport type ItemCondition = { $item: string } & ComparisonOperators;\n\n/**\n * A condition that resolves `$index` to the current repeat array index.\n * Only meaningful inside a `repeat` scope.\n */\nexport type IndexCondition = { $index: true } & ComparisonOperators;\n\n/** A single visibility condition (state, item, or index). */\nexport type SingleCondition = StateCondition | ItemCondition | IndexCondition;\n\n/**\n * AND wrapper — all child conditions must be true.\n * This is the explicit form of the implicit array AND (`SingleCondition[]`).\n * Unlike the implicit form, `$and` supports nested `$or` and `$and` conditions.\n */\nexport type AndCondition = { $and: VisibilityCondition[] };\n\n/**\n * OR wrapper — at least one child condition must be true.\n */\nexport type OrCondition = { $or: VisibilityCondition[] };\n\n/**\n * Visibility condition types.\n * - `boolean` — always/never\n * - `SingleCondition` — single condition (`$state`, `$item`, or `$index`)\n * - `SingleCondition[]` — implicit AND (all must be true)\n * - `AndCondition` — `{ $and: [...] }`, explicit AND (all must be true)\n * - `OrCondition` — `{ $or: [...] }`, at least one must be true\n */\nexport type VisibilityCondition =\n | boolean\n | SingleCondition\n | SingleCondition[]\n | AndCondition\n | OrCondition;\n\n/**\n * Flat UI tree structure (optimized for LLM generation)\n */\nexport interface Spec {\n /** Root element key */\n root: string;\n /** Flat map of elements by key */\n elements: Record<string, UIElement>;\n /** Optional initial state to seed the state model.\n * Components using statePath will read from / write to this state. */\n state?: Record<string, unknown>;\n}\n\n/**\n * State model type\n */\nexport type StateModel = Record<string, unknown>;\n\n/**\n * An abstract store that owns state and notifies subscribers on change.\n *\n * Consumers can supply their own implementation (backed by Redux, Zustand,\n * XState, etc.) or use the built-in {@link createStateStore} for a simple\n * in-memory store.\n */\nexport interface StateStore {\n /** Read a value by JSON Pointer path. */\n get: (path: string) => unknown;\n /**\n * Write a value by JSON Pointer path and notify subscribers.\n * Equality is checked by reference (`===`), not deep comparison.\n * Callers must pass a new object/array reference for changes to be detected.\n */\n set: (path: string, value: unknown) => void;\n /**\n * Write multiple values at once and notify subscribers (single notification).\n * Each value is compared by reference (`===`); only paths whose value\n * actually changed are applied.\n */\n update: (updates: Record<string, unknown>) => void;\n /** Return the full state object (used by `useSyncExternalStore`). */\n getSnapshot: () => StateModel;\n /** Optional server snapshot for SSR (passed to `useSyncExternalStore`). Falls back to `getSnapshot` when omitted. */\n getServerSnapshot?: () => StateModel;\n /** Register a listener that is called on every state change. Returns an unsubscribe function. */\n subscribe: (listener: () => void) => () => void;\n}\n\n/**\n * Component schema definition using Zod\n */\nexport type ComponentSchema = z.ZodType<Record<string, unknown>>;\n\n/**\n * Validation mode for catalog validation\n */\nexport type ValidationMode = \"strict\" | \"warn\" | \"ignore\";\n\n/**\n * JSON patch operation types (RFC 6902)\n */\nexport type PatchOp = \"add\" | \"remove\" | \"replace\" | \"move\" | \"copy\" | \"test\";\n\n/**\n * JSON patch operation (RFC 6902)\n */\nexport interface JsonPatch {\n op: PatchOp;\n path: string;\n /** Required for add, replace, test */\n value?: unknown;\n /** Required for move, copy (source location) */\n from?: string;\n}\n\n/**\n * Resolve a dynamic value against a state model\n */\nexport function resolveDynamicValue<T>(\n value: DynamicValue<T>,\n stateModel: StateModel,\n): T | undefined {\n if (value === null || value === undefined) {\n return undefined;\n }\n\n if (typeof value === \"object\" && \"$state\" in value) {\n return getByPath(stateModel, (value as { $state: string }).$state) as\n | T\n | undefined;\n }\n\n return value as T;\n}\n\n/**\n * Unescape a JSON Pointer token per RFC 6901 Section 4.\n * ~1 is decoded to / and ~0 is decoded to ~ (order matters).\n */\nfunction unescapeJsonPointer(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Parse a JSON Pointer path into unescaped segments.\n */\nexport function parseJsonPointer(path: string): string[] {\n const raw = path.startsWith(\"/\") ? path.slice(1).split(\"/\") : path.split(\"/\");\n return raw.map(unescapeJsonPointer);\n}\n\n/**\n * Get a value from an object by JSON Pointer path (RFC 6901)\n */\nexport function getByPath(obj: unknown, path: string): unknown {\n if (!path || path === \"/\") {\n return obj;\n }\n\n const segments = parseJsonPointer(path);\n\n let current: unknown = obj;\n\n for (const segment of segments) {\n if (current === null || current === undefined) {\n return undefined;\n }\n\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n current = current[index];\n } else if (typeof current === \"object\") {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\n/**\n * Check if a string is a numeric index\n */\nfunction isNumericIndex(str: string): boolean {\n return /^\\d+$/.test(str);\n}\n\n/**\n * Set a value in an object by JSON Pointer path (RFC 6901).\n * Automatically creates arrays when the path segment is a numeric index.\n */\nexport function setByPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const segments = parseJsonPointer(path);\n\n if (segments.length === 0) return;\n\n let current: Record<string, unknown> | unknown[] = obj;\n\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]!;\n const nextSegment = segments[i + 1];\n const nextIsNumeric =\n nextSegment !== undefined &&\n (isNumericIndex(nextSegment) || nextSegment === \"-\");\n\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === undefined || typeof current[index] !== \"object\") {\n current[index] = nextIsNumeric ? [] : {};\n }\n current = current[index] as Record<string, unknown> | unknown[];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n current[segment] = nextIsNumeric ? [] : {};\n }\n current = current[segment] as Record<string, unknown> | unknown[];\n }\n }\n\n const lastSegment = segments[segments.length - 1]!;\n if (Array.isArray(current)) {\n if (lastSegment === \"-\") {\n current.push(value);\n } else {\n const index = parseInt(lastSegment, 10);\n current[index] = value;\n }\n } else {\n current[lastSegment] = value;\n }\n}\n\n/**\n * Add a value per RFC 6902 \"add\" semantics.\n * For objects: create-or-replace the member.\n * For arrays: insert before the given index, or append if \"-\".\n */\nexport function addByPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const segments = parseJsonPointer(path);\n\n if (segments.length === 0) return;\n\n let current: Record<string, unknown> | unknown[] = obj;\n\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]!;\n const nextSegment = segments[i + 1];\n const nextIsNumeric =\n nextSegment !== undefined &&\n (isNumericIndex(nextSegment) || nextSegment === \"-\");\n\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === undefined || typeof current[index] !== \"object\") {\n current[index] = nextIsNumeric ? [] : {};\n }\n current = current[index] as Record<string, unknown> | unknown[];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n current[segment] = nextIsNumeric ? [] : {};\n }\n current = current[segment] as Record<string, unknown> | unknown[];\n }\n }\n\n const lastSegment = segments[segments.length - 1]!;\n if (Array.isArray(current)) {\n if (lastSegment === \"-\") {\n current.push(value);\n } else {\n const index = parseInt(lastSegment, 10);\n current.splice(index, 0, value);\n }\n } else {\n current[lastSegment] = value;\n }\n}\n\n/**\n * Remove a value per RFC 6902 \"remove\" semantics.\n * For objects: delete the property.\n * For arrays: splice out the element at the given index.\n */\nexport function removeByPath(obj: Record<string, unknown>, path: string): void {\n const segments = parseJsonPointer(path);\n\n if (segments.length === 0) return;\n\n let current: Record<string, unknown> | unknown[] = obj;\n\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]!;\n\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === undefined || typeof current[index] !== \"object\") {\n return; // path does not exist\n }\n current = current[index] as Record<string, unknown> | unknown[];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n return; // path does not exist\n }\n current = current[segment] as Record<string, unknown> | unknown[];\n }\n }\n\n const lastSegment = segments[segments.length - 1]!;\n if (Array.isArray(current)) {\n const index = parseInt(lastSegment, 10);\n if (index >= 0 && index < current.length) {\n current.splice(index, 1);\n }\n } else {\n delete current[lastSegment];\n }\n}\n\n/**\n * Deep equality check for RFC 6902 \"test\" operation.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return false;\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));\n}\n\n/**\n * Find a form value from params and/or state.\n * Useful in action handlers to locate form input values regardless of path format.\n *\n * Checks in order:\n * 1. Direct param key (if not a path reference)\n * 2. Param keys ending with the field name\n * 3. State keys ending with the field name (dot notation)\n * 4. State path using getByPath (slash notation)\n *\n * @example\n * // Find \"name\" from params or state\n * const name = findFormValue(\"name\", params, state);\n *\n * // Will find from: params.name, params[\"form.name\"], state[\"form.name\"], or getByPath(state, \"name\")\n */\nexport function findFormValue(\n fieldName: string,\n params?: Record<string, unknown>,\n state?: Record<string, unknown>,\n): unknown {\n // Check params first (but not if it looks like a state path reference)\n if (params?.[fieldName] !== undefined) {\n const val = params[fieldName];\n // If the value looks like a path reference (contains dots), skip it\n if (typeof val !== \"string\" || !val.includes(\".\")) {\n return val;\n }\n }\n\n // Check param keys that end with the field name\n if (params) {\n for (const key of Object.keys(params)) {\n if (key.endsWith(`.${fieldName}`)) {\n const val = params[key];\n if (typeof val !== \"string\" || !val.includes(\".\")) {\n return val;\n }\n }\n }\n }\n\n // Check state keys that end with the field name (handles any form naming)\n if (state) {\n for (const key of Object.keys(state)) {\n if (key === fieldName || key.endsWith(`.${fieldName}`)) {\n return state[key];\n }\n }\n\n // Try getByPath with the raw field name\n const val = getByPath(state, fieldName);\n if (val !== undefined) {\n return val;\n }\n }\n\n return undefined;\n}\n\n// =============================================================================\n// SpecStream - Streaming format for progressively building specs\n// =============================================================================\n\n/**\n * A SpecStream line - a single patch operation in the stream.\n */\nexport type SpecStreamLine = JsonPatch;\n\n/**\n * Parse a single SpecStream line into a patch operation.\n * Returns null if the line is invalid or empty.\n *\n * SpecStream is json-render's streaming format where each line is a JSON patch\n * operation that progressively builds up the final spec.\n */\nexport function parseSpecStreamLine(line: string): SpecStreamLine | null {\n const trimmed = line.trim();\n if (!trimmed || !trimmed.startsWith(\"{\")) return null;\n\n try {\n const patch = JSON.parse(trimmed) as SpecStreamLine;\n if (patch.op && patch.path !== undefined) {\n return patch;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Apply a single RFC 6902 JSON Patch operation to an object.\n * Mutates the object in place.\n *\n * Supports all six RFC 6902 operations: add, remove, replace, move, copy, test.\n *\n * @throws {Error} If a \"test\" operation fails (value mismatch).\n */\nexport function applySpecStreamPatch<T extends Record<string, unknown>>(\n obj: T,\n patch: SpecStreamLine,\n): T {\n switch (patch.op) {\n case \"add\":\n addByPath(obj, patch.path, patch.value);\n break;\n case \"replace\":\n // RFC 6902: target must exist. For streaming tolerance we set regardless.\n setByPath(obj, patch.path, patch.value);\n break;\n case \"remove\":\n removeByPath(obj, patch.path);\n break;\n case \"move\": {\n if (!patch.from) break;\n const moveValue = getByPath(obj, patch.from);\n removeByPath(obj, patch.from);\n addByPath(obj, patch.path, moveValue);\n break;\n }\n case \"copy\": {\n if (!patch.from) break;\n const copyValue = getByPath(obj, patch.from);\n addByPath(obj, patch.path, copyValue);\n break;\n }\n case \"test\": {\n const actual = getByPath(obj, patch.path);\n if (!deepEqual(actual, patch.value)) {\n throw new Error(\n `Test operation failed: value at \"${patch.path}\" does not match`,\n );\n }\n break;\n }\n }\n return obj;\n}\n\n/**\n * Apply a single RFC 6902 JSON Patch operation to a Spec.\n * Mutates the spec in place and returns it.\n *\n * This is a typed convenience wrapper around `applySpecStreamPatch` that\n * accepts a `Spec` directly without requiring a cast to `Record<string, unknown>`.\n *\n * Note: This mutates the spec. For React state updates, spread the result\n * to create a new reference: `setSpec({ ...applySpecPatch(spec, patch) })`.\n *\n * @example\n * let spec: Spec = { root: \"\", elements: {} };\n * applySpecPatch(spec, { op: \"add\", path: \"/root\", value: \"main\" });\n */\nexport function applySpecPatch(spec: Spec, patch: SpecStreamLine): Spec {\n applySpecStreamPatch(spec as unknown as Record<string, unknown>, patch);\n return spec;\n}\n\n// =============================================================================\n// Nested-to-Flat Conversion\n// =============================================================================\n\n/**\n * A nested spec node. This is the tree format that humans naturally write —\n * each node has inline `children` as an array of child node objects rather\n * than string keys.\n */\ninterface NestedNode {\n type: string;\n props: Record<string, unknown>;\n children?: NestedNode[];\n /** Any other top-level fields (visible, on, repeat, etc.) */\n [key: string]: unknown;\n}\n\n/**\n * Convert a nested (tree-structured) spec into the flat `Spec` format used\n * by json-render renderers.\n *\n * In the nested format each node has inline `children` as an array of child\n * objects. This function walks the tree, assigns auto-generated keys\n * (`el-0`, `el-1`, ...), and produces a flat `{ root, elements, state }` spec.\n *\n * The top-level `state` field (if present on the root node) is hoisted to\n * `spec.state`.\n *\n * @example\n * ```ts\n * const nested = {\n * type: \"Card\",\n * props: { title: \"Hello\" },\n * children: [\n * { type: \"Text\", props: { content: \"World\" } },\n * ],\n * state: { count: 0 },\n * };\n * const spec = nestedToFlat(nested);\n * // {\n * // root: \"el-0\",\n * // elements: {\n * // \"el-0\": { type: \"Card\", props: { title: \"Hello\" }, children: [\"el-1\"] },\n * // \"el-1\": { type: \"Text\", props: { content: \"World\" }, children: [] },\n * // },\n * // state: { count: 0 },\n * // }\n * ```\n */\nexport function nestedToFlat(nested: Record<string, unknown>): Spec {\n const elements: Record<string, UIElement> = {};\n let counter = 0;\n\n function walk(node: Record<string, unknown>): string {\n const key = `el-${counter++}`;\n const { type, props, children: rawChildren, ...rest } = node as NestedNode;\n\n // Recursively flatten children\n const childKeys: string[] = [];\n if (Array.isArray(rawChildren)) {\n for (const child of rawChildren) {\n if (child && typeof child === \"object\" && \"type\" in child) {\n childKeys.push(walk(child as Record<string, unknown>));\n }\n }\n }\n\n // Build the flat element, preserving extra fields (visible, on, repeat, etc.)\n // but excluding `state` which is hoisted to spec-level.\n const element: UIElement = {\n type: type ?? \"unknown\",\n props: (props as Record<string, unknown>) ?? {},\n children: childKeys,\n };\n\n // Copy extra fields (visible, on, repeat) but not state\n for (const [k, v] of Object.entries(rest)) {\n if (k !== \"state\" && v !== undefined) {\n (element as unknown as Record<string, unknown>)[k] = v;\n }\n }\n\n elements[key] = element;\n return key;\n }\n\n const root = walk(nested);\n\n const spec: Spec = { root, elements };\n\n // Hoist state from root node if present\n if (\n nested.state &&\n typeof nested.state === \"object\" &&\n !Array.isArray(nested.state)\n ) {\n spec.state = nested.state as Record<string, unknown>;\n }\n\n return spec;\n}\n\n/**\n * Compile a SpecStream string into a JSON object.\n * Each line should be a patch operation.\n *\n * @example\n * const stream = `{\"op\":\"add\",\"path\":\"/name\",\"value\":\"Alice\"}\n * {\"op\":\"add\",\"path\":\"/age\",\"value\":30}`;\n * const result = compileSpecStream(stream);\n * // { name: \"Alice\", age: 30 }\n */\nexport function compileSpecStream<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(stream: string, initial: T = {} as T): T {\n const lines = stream.split(\"\\n\");\n const result = { ...initial };\n\n for (const line of lines) {\n const patch = parseSpecStreamLine(line);\n if (patch) {\n applySpecStreamPatch(result, patch);\n }\n }\n\n return result as T;\n}\n\n/**\n * Streaming SpecStream compiler.\n * Useful for processing SpecStream data as it streams in from AI.\n *\n * @example\n * const compiler = createSpecStreamCompiler<MySpec>();\n *\n * // As chunks arrive:\n * const { result, newPatches } = compiler.push(chunk);\n * if (newPatches.length > 0) {\n * updateUI(result);\n * }\n *\n * // When done:\n * const finalResult = compiler.getResult();\n */\nexport interface SpecStreamCompiler<T> {\n /** Push a chunk of text. Returns the current result and any new patches applied. */\n push(chunk: string): { result: T; newPatches: SpecStreamLine[] };\n /** Get the current compiled result */\n getResult(): T;\n /** Get all patches that have been applied */\n getPatches(): SpecStreamLine[];\n /** Reset the compiler to initial state */\n reset(initial?: Partial<T>): void;\n}\n\n/**\n * Create a streaming SpecStream compiler.\n *\n * SpecStream is json-render's streaming format. AI outputs patch operations\n * line by line, and this compiler progressively builds the final spec.\n *\n * @example\n * const compiler = createSpecStreamCompiler<TimelineSpec>();\n *\n * // Process streaming response\n * const reader = response.body.getReader();\n * while (true) {\n * const { done, value } = await reader.read();\n * if (done) break;\n *\n * const { result, newPatches } = compiler.push(decoder.decode(value));\n * if (newPatches.length > 0) {\n * setSpec(result); // Update UI with partial result\n * }\n * }\n */\nexport function createSpecStreamCompiler<T = Record<string, unknown>>(\n initial: Partial<T> = {},\n): SpecStreamCompiler<T> {\n let result = { ...initial } as T;\n let buffer = \"\";\n const appliedPatches: SpecStreamLine[] = [];\n const processedLines = new Set<string>();\n\n return {\n push(chunk: string): { result: T; newPatches: SpecStreamLine[] } {\n buffer += chunk;\n const newPatches: SpecStreamLine[] = [];\n\n // Process complete lines\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || processedLines.has(trimmed)) continue;\n processedLines.add(trimmed);\n\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n applySpecStreamPatch(result as Record<string, unknown>, patch);\n appliedPatches.push(patch);\n newPatches.push(patch);\n }\n }\n\n // Return a shallow copy to trigger re-renders\n if (newPatches.length > 0) {\n result = { ...result };\n }\n\n return { result, newPatches };\n },\n\n getResult(): T {\n // Process any remaining buffer\n if (buffer.trim()) {\n const patch = parseSpecStreamLine(buffer);\n if (patch && !processedLines.has(buffer.trim())) {\n processedLines.add(buffer.trim());\n applySpecStreamPatch(result as Record<string, unknown>, patch);\n appliedPatches.push(patch);\n result = { ...result };\n }\n buffer = \"\";\n }\n return result;\n },\n\n getPatches(): SpecStreamLine[] {\n return [...appliedPatches];\n },\n\n reset(newInitial: Partial<T> = {}): void {\n result = { ...newInitial } as T;\n buffer = \"\";\n appliedPatches.length = 0;\n processedLines.clear();\n },\n };\n}\n\n// =============================================================================\n// Mixed Stream Parser — for chat + GenUI (text interleaved with JSONL patches)\n// =============================================================================\n\n/**\n * Callbacks for the mixed stream parser.\n */\nexport interface MixedStreamCallbacks {\n /** Called when a JSONL patch line is parsed */\n onPatch: (patch: SpecStreamLine) => void;\n /** Called when a text (non-JSONL) line is received */\n onText: (text: string) => void;\n}\n\n/**\n * A stateful parser for mixed streams that contain both text and JSONL patches.\n * Used in chat + GenUI scenarios where an LLM responds with conversational text\n * interleaved with json-render JSONL patch operations.\n */\nexport interface MixedStreamParser {\n /** Push a chunk of streamed data. Calls onPatch/onText for each complete line. */\n push(chunk: string): void;\n /** Flush any remaining buffered content. Call when the stream ends. */\n flush(): void;\n}\n\n/**\n * Create a parser for mixed text + JSONL streams.\n *\n * In chat + GenUI scenarios, an LLM streams a response that contains both\n * conversational text and json-render JSONL patch lines. This parser buffers\n * incoming chunks, splits them into lines, and classifies each line as either\n * a JSONL patch (via `parseSpecStreamLine`) or plain text.\n *\n * @example\n * const parser = createMixedStreamParser({\n * onText: (text) => appendToMessage(text),\n * onPatch: (patch) => applySpecPatch(spec, patch),\n * });\n *\n * // As chunks arrive from the stream:\n * for await (const chunk of stream) {\n * parser.push(chunk);\n * }\n * parser.flush();\n */\nexport function createMixedStreamParser(\n callbacks: MixedStreamCallbacks,\n): MixedStreamParser {\n let buffer = \"\";\n let inSpecFence = false;\n\n function processLine(line: string): void {\n const trimmed = line.trim();\n\n // Fence detection\n if (!inSpecFence && trimmed.startsWith(\"```spec\")) {\n inSpecFence = true;\n return;\n }\n if (inSpecFence && trimmed === \"```\") {\n inSpecFence = false;\n return;\n }\n\n if (!trimmed) return;\n\n if (inSpecFence) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n callbacks.onPatch(patch);\n }\n return;\n }\n\n // Outside fence: heuristic mode\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n callbacks.onPatch(patch);\n } else {\n callbacks.onText(line);\n }\n }\n\n return {\n push(chunk: string): void {\n buffer += chunk;\n\n // Process complete lines\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n for (const line of lines) {\n processLine(line);\n }\n },\n\n flush(): void {\n if (buffer.trim()) {\n processLine(buffer);\n }\n buffer = \"\";\n },\n };\n}\n\n// =============================================================================\n// AI SDK Stream Transform\n// =============================================================================\n\n/**\n * Minimal chunk shape compatible with the AI SDK's `UIMessageChunk`.\n *\n * Defined here so that `@json-render/core` has no dependency on the `ai`\n * package. The discriminated union covers the three text-related chunk types\n * the transform inspects; all other chunk types pass through via the fallback.\n */\nexport type StreamChunk =\n | { type: \"text-start\"; id: string; [k: string]: unknown }\n | { type: \"text-delta\"; id: string; delta: string; [k: string]: unknown }\n | { type: \"text-end\"; id: string; [k: string]: unknown }\n | { type: string; [k: string]: unknown };\n\n/** The opening fence for a spec block (e.g. ` ```spec `). */\nconst SPEC_FENCE_OPEN = \"```spec\";\n/** The closing fence for a spec block. */\nconst SPEC_FENCE_CLOSE = \"```\";\n\n/**\n * Creates a `TransformStream` that intercepts AI SDK UI message stream chunks\n * and classifies text content as either prose or json-render JSONL patches.\n *\n * Two classification modes:\n *\n * 1. **Fence mode** (preferred): Lines between ` ```spec ` and ` ``` ` are\n * parsed as JSONL patches. Fence delimiters are swallowed (not emitted).\n * 2. **Heuristic mode** (backward compat): Outside of fences, lines starting\n * with `{` are buffered and tested with `parseSpecStreamLine`. Valid patches\n * are emitted as {@link SPEC_DATA_PART_TYPE} parts; everything else is\n * flushed as text.\n *\n * Non-text chunks (tool events, step markers, etc.) are passed through unchanged.\n *\n * @example\n * ```ts\n * import { createJsonRenderTransform } from \"@json-render/core\";\n * import { createUIMessageStream, createUIMessageStreamResponse } from \"ai\";\n *\n * const stream = createUIMessageStream({\n * execute: async ({ writer }) => {\n * writer.merge(\n * result.toUIMessageStream().pipeThrough(createJsonRenderTransform()),\n * );\n * },\n * });\n * return createUIMessageStreamResponse({ stream });\n * ```\n */\nexport function createJsonRenderTransform(): TransformStream<\n StreamChunk,\n StreamChunk\n> {\n let lineBuffer = \"\";\n let currentTextId = \"\";\n // Whether the current incomplete line might be JSONL (starts with '{')\n let buffering = false;\n // Whether we are inside a ```spec fence\n let inSpecFence = false;\n // Whether we are currently inside a text block (between text-start/text-end).\n // Used to split text blocks around spec data so the AI SDK creates separate\n // text parts, preserving interleaving of prose and UI in message.parts.\n let inTextBlock = false;\n let textIdCounter = 0;\n\n /** Close the current text block if one is open. */\n function closeTextBlock(\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n if (inTextBlock) {\n controller.enqueue({ type: \"text-end\", id: currentTextId });\n inTextBlock = false;\n }\n }\n\n /** Ensure a text block is open, starting a new one if needed. */\n function ensureTextBlock(\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n if (!inTextBlock) {\n textIdCounter++;\n currentTextId = String(textIdCounter);\n controller.enqueue({ type: \"text-start\", id: currentTextId });\n inTextBlock = true;\n }\n }\n\n /** Emit a text-delta, opening a text block first if necessary. */\n function emitTextDelta(\n delta: string,\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n ensureTextBlock(controller);\n controller.enqueue({ type: \"text-delta\", id: currentTextId, delta });\n }\n\n function emitPatch(\n patch: SpecStreamLine,\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n closeTextBlock(controller);\n controller.enqueue({\n type: SPEC_DATA_PART_TYPE,\n data: { type: \"patch\", patch },\n });\n }\n\n function flushBuffer(\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n if (!lineBuffer) return;\n\n const trimmed = lineBuffer.trim();\n\n // Inside a fence, everything is spec data\n if (inSpecFence) {\n if (trimmed) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) emitPatch(patch, controller);\n // Non-patch lines inside the fence are silently dropped\n }\n lineBuffer = \"\";\n buffering = false;\n return;\n }\n\n if (trimmed) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n emitPatch(patch, controller);\n } else {\n // Was buffered but isn't JSONL — flush as text\n emitTextDelta(lineBuffer, controller);\n }\n } else {\n // Whitespace-only buffer — forward as-is (preserves blank lines)\n emitTextDelta(lineBuffer, controller);\n }\n lineBuffer = \"\";\n buffering = false;\n }\n\n function processCompleteLine(\n line: string,\n controller: TransformStreamDefaultController<StreamChunk>,\n ) {\n const trimmed = line.trim();\n\n // --- Fence detection ---\n if (!inSpecFence && trimmed.startsWith(SPEC_FENCE_OPEN)) {\n inSpecFence = true;\n return; // Swallow the opening fence\n }\n if (inSpecFence && trimmed === SPEC_FENCE_CLOSE) {\n inSpecFence = false;\n return; // Swallow the closing fence\n }\n\n // Inside a fence: parse as spec data\n if (inSpecFence) {\n if (trimmed) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) emitPatch(patch, controller);\n }\n return;\n }\n\n // --- Outside fence: heuristic mode ---\n if (!trimmed) {\n // Empty line — forward for markdown paragraph breaks\n emitTextDelta(\"\\n\", controller);\n return;\n }\n\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n emitPatch(patch, controller);\n } else {\n emitTextDelta(line + \"\\n\", controller);\n }\n }\n\n return new TransformStream<StreamChunk, StreamChunk>({\n transform(chunk, controller) {\n switch (chunk.type) {\n case \"text-start\": {\n const id = (chunk as { id: string }).id;\n const idNum = parseInt(id, 10);\n if (!isNaN(idNum) && idNum >= textIdCounter) {\n textIdCounter = idNum;\n }\n currentTextId = id;\n inTextBlock = true;\n controller.enqueue(chunk);\n break;\n }\n\n case \"text-delta\": {\n const delta = chunk as { id: string; delta: string };\n const text = delta.delta;\n\n for (let i = 0; i < text.length; i++) {\n const ch = text.charAt(i);\n\n if (ch === \"\\n\") {\n // Line complete — classify and emit\n if (buffering) {\n processCompleteLine(lineBuffer, controller);\n lineBuffer = \"\";\n buffering = false;\n } else {\n // Outside fence, emit newline; inside fence, swallow it\n if (!inSpecFence) {\n emitTextDelta(\"\\n\", controller);\n }\n }\n } else if (lineBuffer.length === 0 && !buffering) {\n // Start of a new line — decide whether to buffer or stream\n if (inSpecFence || ch === \"{\" || ch === \"`\") {\n // Buffer: inside fence (everything), or heuristic mode ({), or potential fence (`)\n buffering = true;\n lineBuffer += ch;\n } else {\n emitTextDelta(ch, controller);\n }\n } else if (buffering) {\n lineBuffer += ch;\n } else {\n emitTextDelta(ch, controller);\n }\n }\n break;\n }\n\n case \"text-end\": {\n flushBuffer(controller);\n if (inTextBlock) {\n controller.enqueue({ type: \"text-end\", id: currentTextId });\n inTextBlock = false;\n }\n break;\n }\n\n default: {\n controller.enqueue(chunk);\n break;\n }\n }\n },\n\n flush(controller) {\n flushBuffer(controller);\n closeTextBlock(controller);\n },\n });\n}\n\n/**\n * The key registered in `AppDataParts` for json-render specs.\n * The AI SDK automatically prefixes this with `\"data-\"` on the wire,\n * so the actual stream chunk type is `\"data-spec\"` (see {@link SPEC_DATA_PART_TYPE}).\n *\n * @example\n * ```ts\n * import { SPEC_DATA_PART, type SpecDataPart } from \"@json-render/core\";\n * type AppDataParts = { [SPEC_DATA_PART]: SpecDataPart };\n * ```\n */\nexport const SPEC_DATA_PART = \"spec\" as const;\n\n/**\n * The wire-format type string as it appears in stream chunks and message parts.\n * This is `\"data-\"` + {@link SPEC_DATA_PART} — i.e. `\"data-spec\"`.\n *\n * Use this constant when filtering message parts or enqueuing stream chunks.\n */\nexport const SPEC_DATA_PART_TYPE = `data-${SPEC_DATA_PART}` as const;\n\n/**\n * Discriminated union for the payload of a {@link SPEC_DATA_PART_TYPE} SSE part.\n *\n * - `\"patch\"`: A single RFC 6902 JSON Patch operation (streaming, progressive UI).\n * - `\"flat\"`: A complete flat spec with `root`, `elements`, and optional `state`.\n * - `\"nested\"`: A complete nested spec (tree structure — schema depends on catalog).\n */\nexport type SpecDataPart =\n | { type: \"patch\"; patch: JsonPatch }\n | { type: \"flat\"; spec: Spec }\n | { type: \"nested\"; spec: Record<string, unknown> };\n\n/**\n * Convenience wrapper that pipes an AI SDK UI message stream through the\n * json-render transform, classifying text as prose or JSONL patches.\n *\n * Eliminates the need for manual `pipeThrough(createJsonRenderTransform())`\n * and the associated type cast.\n *\n * @example\n * ```ts\n * import { pipeJsonRender } from \"@json-render/core\";\n *\n * const stream = createUIMessageStream({\n * execute: async ({ writer }) => {\n * writer.merge(pipeJsonRender(result.toUIMessageStream()));\n * },\n * });\n * return createUIMessageStreamResponse({ stream });\n * ```\n */\nexport function pipeJsonRender<T = StreamChunk>(\n stream: ReadableStream<T>,\n): ReadableStream<T> {\n return stream.pipeThrough(\n createJsonRenderTransform() as unknown as TransformStream<T, T>,\n );\n}\n","import {\n getByPath,\n parseJsonPointer,\n type StateModel,\n type StateStore,\n} from \"./types\";\n\n/**\n * Immutably set a value at a JSON Pointer path using structural sharing.\n * Only objects along the path are shallow-cloned; untouched branches keep\n * their original references.\n */\nexport function immutableSetByPath(\n root: StateModel,\n path: string,\n value: unknown,\n): StateModel {\n const segments = parseJsonPointer(path);\n if (segments.length === 0) return root;\n\n const result = { ...root };\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!;\n const child = current[seg];\n if (Array.isArray(child)) {\n current[seg] = [...child];\n } else if (child !== null && typeof child === \"object\") {\n current[seg] = { ...(child as Record<string, unknown>) };\n } else {\n const nextSeg = segments[i + 1];\n current[seg] = nextSeg !== undefined && /^\\d+$/.test(nextSeg) ? [] : {};\n }\n current = current[seg] as Record<string, unknown>;\n }\n\n const lastSeg = segments[segments.length - 1]!;\n if (Array.isArray(current)) {\n if (lastSeg === \"-\") {\n (current as unknown[]).push(value);\n } else {\n (current as unknown[])[parseInt(lastSeg, 10)] = value;\n }\n } else {\n current[lastSeg] = value;\n }\n\n return result;\n}\n\n/**\n * Create a simple in-memory {@link StateStore}.\n *\n * This is the default store used by `StateProvider` when no external store is\n * provided. It mirrors the previous `useState`-based behaviour but is\n * framework-agnostic so it can also be used in tests or non-React contexts.\n */\nexport function createStateStore(initialState: StateModel = {}): StateStore {\n let state: StateModel = { ...initialState };\n const listeners = new Set<() => void>();\n\n function notify() {\n for (const listener of listeners) {\n listener();\n }\n }\n\n return {\n get(path: string): unknown {\n return getByPath(state, path);\n },\n\n set(path: string, value: unknown): void {\n if (getByPath(state, path) === value) return;\n state = immutableSetByPath(state, path, value);\n notify();\n },\n\n update(updates: Record<string, unknown>): void {\n let changed = false;\n let next = state;\n for (const [path, value] of Object.entries(updates)) {\n if (getByPath(next, path) !== value) {\n next = immutableSetByPath(next, path, value);\n changed = true;\n }\n }\n if (!changed) return;\n state = next;\n notify();\n },\n\n getSnapshot(): StateModel {\n return state;\n },\n\n getServerSnapshot(): StateModel {\n return state;\n },\n\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/**\n * Configuration for {@link createStoreAdapter}. Adapter authors supply these\n * three callbacks; everything else (get, set, update, no-op detection,\n * getServerSnapshot) is handled by the returned {@link StateStore}.\n */\nexport interface StoreAdapterConfig {\n /** Return the current state snapshot from the underlying store. */\n getSnapshot: () => StateModel;\n /** Write a new state snapshot to the underlying store. */\n setSnapshot: (next: StateModel) => void;\n /** Subscribe to changes in the underlying store. Return an unsubscribe fn. */\n subscribe: (listener: () => void) => () => void;\n}\n\n/**\n * Build a full {@link StateStore} from a minimal adapter config.\n *\n * Handles `get`, `set` (with no-op detection), `update` (batched, with no-op\n * detection), `getSnapshot`, `getServerSnapshot`, and `subscribe` -- so each\n * adapter only needs to wire its snapshot source, write API, and subscribe\n * mechanism.\n */\nexport function createStoreAdapter(config: StoreAdapterConfig): StateStore {\n return {\n get(path: string): unknown {\n return getByPath(config.getSnapshot(), path);\n },\n\n set(path: string, value: unknown): void {\n const current = config.getSnapshot();\n if (getByPath(current, path) === value) return;\n config.setSnapshot(immutableSetByPath(current, path, value));\n },\n\n update(updates: Record<string, unknown>): void {\n let next = config.getSnapshot();\n let changed = false;\n for (const [path, value] of Object.entries(updates)) {\n if (getByPath(next, path) !== value) {\n next = immutableSetByPath(next, path, value);\n changed = true;\n }\n }\n if (!changed) return;\n config.setSnapshot(next);\n },\n\n getSnapshot: config.getSnapshot,\n\n getServerSnapshot: config.getSnapshot,\n\n subscribe: config.subscribe,\n };\n}\n\nconst MAX_FLATTEN_DEPTH = 20;\n\n/**\n * Recursively flatten a plain object into a `Record<string, unknown>` keyed by\n * JSON Pointer paths. Only leaf values (non-plain-object) appear in the output.\n *\n * Includes circular reference protection and a depth cap to prevent stack\n * overflow on pathological inputs.\n *\n * ```ts\n * flattenToPointers({ user: { name: \"Alice\" }, count: 1 })\n * // => { \"/user/name\": \"Alice\", \"/count\": 1 }\n * ```\n */\nexport function flattenToPointers(\n obj: Record<string, unknown>,\n prefix = \"\",\n _depth = 0,\n _seen?: Set<object>,\n _warned?: { current: boolean },\n): Record<string, unknown> {\n const seen = _seen ?? new Set<object>();\n const warned = _warned ?? { current: false };\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const pointer = `${prefix}/${key}`;\n if (\n _depth < MAX_FLATTEN_DEPTH &&\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype &&\n !seen.has(value)\n ) {\n seen.add(value);\n Object.assign(\n result,\n flattenToPointers(\n value as Record<string, unknown>,\n pointer,\n _depth + 1,\n seen,\n warned,\n ),\n );\n } else {\n if (\n process.env.NODE_ENV !== \"production\" &&\n !warned.current &&\n _depth >= MAX_FLATTEN_DEPTH &&\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype &&\n !seen.has(value as object)\n ) {\n warned.current = true;\n console.warn(\n `flattenToPointers: depth limit (${MAX_FLATTEN_DEPTH}) reached. Nested state beyond this depth will be treated as a leaf value.`,\n );\n }\n result[pointer] = value;\n }\n }\n return result;\n}\n"],"mappings":"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","names":["patch"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as StateModel, V as VisibilityCondition, a as StateCondition, A as AndCondition, O as OrCondition, D as DynamicValue, b as Spec } from './store-utils-
|
|
2
|
-
export { Y as Action, X as ActionBinding, a4 as ActionBindingSchema, Z as ActionConfirm, a6 as ActionConfirmSchema, a1 as ActionDefinition, a3 as ActionExecutionContext, a0 as ActionHandler, $ as ActionOnError, a8 as ActionOnErrorSchema, _ as ActionOnSuccess, a7 as ActionOnSuccessSchema, a5 as ActionSchema, C as ComponentSchema, e as DynamicBoolean, r as DynamicBooleanSchema, d as DynamicNumber, q as DynamicNumberSchema, c as DynamicString, p as DynamicStringSchema, o as DynamicValueSchema, F as FlatElement, f as IndexCondition, I as ItemCondition, J as JsonPatch, M as MixedStreamCallbacks, l as MixedStreamParser, P as PatchOp, a2 as ResolvedAction, Q as SPEC_DATA_PART, R as SPEC_DATA_PART_TYPE, g as SingleCondition, n as SpecDataPart, k as SpecStreamCompiler, j as SpecStreamLine, h as StateStore, T as StoreAdapterConfig, m as StreamChunk, U as UIElement, i as ValidationMode, ad as action, ac as actionBinding, v as addByPath, B as applySpecPatch, z as applySpecStreamPatch, G as compileSpecStream, L as createJsonRenderTransform, K as createMixedStreamParser, H as createSpecStreamCompiler, W as createStateStore, aa as executeAction, x as findFormValue, t as getByPath, ab as interpolateString, E as nestedToFlat, y as parseSpecStreamLine, N as pipeJsonRender, w as removeByPath, a9 as resolveAction, s as resolveDynamicValue, u as setByPath } from './store-utils-
|
|
1
|
+
import { S as StateModel, V as VisibilityCondition, a as StateCondition, A as AndCondition, O as OrCondition, D as DynamicValue, b as Spec } from './store-utils-D98Czbil.mjs';
|
|
2
|
+
export { Y as Action, X as ActionBinding, a4 as ActionBindingSchema, Z as ActionConfirm, a6 as ActionConfirmSchema, a1 as ActionDefinition, a3 as ActionExecutionContext, a0 as ActionHandler, $ as ActionOnError, a8 as ActionOnErrorSchema, _ as ActionOnSuccess, a7 as ActionOnSuccessSchema, a5 as ActionSchema, C as ComponentSchema, e as DynamicBoolean, r as DynamicBooleanSchema, d as DynamicNumber, q as DynamicNumberSchema, c as DynamicString, p as DynamicStringSchema, o as DynamicValueSchema, F as FlatElement, f as IndexCondition, I as ItemCondition, J as JsonPatch, M as MixedStreamCallbacks, l as MixedStreamParser, P as PatchOp, a2 as ResolvedAction, Q as SPEC_DATA_PART, R as SPEC_DATA_PART_TYPE, g as SingleCondition, n as SpecDataPart, k as SpecStreamCompiler, j as SpecStreamLine, h as StateStore, T as StoreAdapterConfig, m as StreamChunk, U as UIElement, i as ValidationMode, ad as action, ac as actionBinding, v as addByPath, B as applySpecPatch, z as applySpecStreamPatch, G as compileSpecStream, L as createJsonRenderTransform, K as createMixedStreamParser, H as createSpecStreamCompiler, W as createStateStore, aa as executeAction, x as findFormValue, t as getByPath, ab as interpolateString, E as nestedToFlat, y as parseSpecStreamLine, N as pipeJsonRender, w as removeByPath, a9 as resolveAction, s as resolveDynamicValue, u as setByPath } from './store-utils-D98Czbil.mjs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -86,6 +86,10 @@ declare const visibility: {
|
|
|
86
86
|
* repeat item — resolves via `repeatBasePath + path` and exposes the
|
|
87
87
|
* absolute state path for write-back.
|
|
88
88
|
* - `{ $cond, $then, $else }` conditionally picks a value
|
|
89
|
+
* - `{ $computed: string, args?: Record<string, PropExpression> }` calls a
|
|
90
|
+
* registered function with resolved args and returns the result
|
|
91
|
+
* - `{ $template: string }` interpolates `${/path}` references in the
|
|
92
|
+
* string with values from the state model
|
|
89
93
|
* - Any other value is a literal (passthrough)
|
|
90
94
|
*/
|
|
91
95
|
type PropExpression<T = unknown> = T | {
|
|
@@ -102,7 +106,17 @@ type PropExpression<T = unknown> = T | {
|
|
|
102
106
|
$cond: VisibilityCondition;
|
|
103
107
|
$then: PropExpression<T>;
|
|
104
108
|
$else: PropExpression<T>;
|
|
109
|
+
} | {
|
|
110
|
+
$computed: string;
|
|
111
|
+
args?: Record<string, unknown>;
|
|
112
|
+
} | {
|
|
113
|
+
$template: string;
|
|
105
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Function signature for `$computed` expressions.
|
|
117
|
+
* Receives a record of resolved argument values and returns a computed result.
|
|
118
|
+
*/
|
|
119
|
+
type ComputedFunction = (args: Record<string, unknown>) => unknown;
|
|
106
120
|
/**
|
|
107
121
|
* Context for resolving prop expressions.
|
|
108
122
|
* Extends {@link VisibilityContext} with an optional `repeatBasePath` used
|
|
@@ -111,6 +125,8 @@ type PropExpression<T = unknown> = T | {
|
|
|
111
125
|
interface PropResolutionContext extends VisibilityContext {
|
|
112
126
|
/** Absolute state path to the current repeat item (e.g. "/todos/0"). Set inside repeat scopes. */
|
|
113
127
|
repeatBasePath?: string;
|
|
128
|
+
/** Named functions available for `$computed` expressions. */
|
|
129
|
+
functions?: Record<string, ComputedFunction>;
|
|
114
130
|
}
|
|
115
131
|
/**
|
|
116
132
|
* Resolve a single prop value that may contain expressions.
|
|
@@ -266,7 +282,12 @@ declare const check: {
|
|
|
266
282
|
min: (min: number, message?: string) => ValidationCheck;
|
|
267
283
|
max: (max: number, message?: string) => ValidationCheck;
|
|
268
284
|
url: (message?: string) => ValidationCheck;
|
|
285
|
+
numeric: (message?: string) => ValidationCheck;
|
|
269
286
|
matches: (otherPath: string, message?: string) => ValidationCheck;
|
|
287
|
+
equalTo: (otherPath: string, message?: string) => ValidationCheck;
|
|
288
|
+
lessThan: (otherPath: string, message?: string) => ValidationCheck;
|
|
289
|
+
greaterThan: (otherPath: string, message?: string) => ValidationCheck;
|
|
290
|
+
requiredIf: (fieldPath: string, message?: string) => ValidationCheck;
|
|
270
291
|
};
|
|
271
292
|
|
|
272
293
|
/**
|
|
@@ -284,7 +305,7 @@ interface SpecIssue {
|
|
|
284
305
|
/** The element key where the issue was found (if applicable) */
|
|
285
306
|
elementKey?: string;
|
|
286
307
|
/** Machine-readable issue code for programmatic handling */
|
|
287
|
-
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props";
|
|
308
|
+
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props" | "watch_in_props";
|
|
288
309
|
}
|
|
289
310
|
/**
|
|
290
311
|
* Result of spec structural validation.
|
|
@@ -605,4 +626,4 @@ interface UserPromptOptions {
|
|
|
605
626
|
*/
|
|
606
627
|
declare function buildUserPrompt(options: UserPromptOptions): string;
|
|
607
628
|
|
|
608
|
-
export { AndCondition, type BuiltInAction, type Catalog, DynamicValue, type InferActionParams, type InferCatalogActions, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, OrCondition, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type PropResolutionContext, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, Spec, type SpecIssue, type SpecIssueSeverity, type SpecValidationIssues, type SpecValidationResult, StateCondition, StateModel, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationResult, VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, defineCatalog, defineSchema, evaluateVisibility, formatSpecIssues, resolveActionParam, resolveBindings, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, validateSpec, visibility };
|
|
629
|
+
export { AndCondition, type BuiltInAction, type Catalog, type ComputedFunction, DynamicValue, type InferActionParams, type InferCatalogActions, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, OrCondition, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type PropResolutionContext, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, Spec, type SpecIssue, type SpecIssueSeverity, type SpecValidationIssues, type SpecValidationResult, StateCondition, StateModel, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationResult, VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, defineCatalog, defineSchema, evaluateVisibility, formatSpecIssues, resolveActionParam, resolveBindings, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, validateSpec, visibility };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as StateModel, V as VisibilityCondition, a as StateCondition, A as AndCondition, O as OrCondition, D as DynamicValue, b as Spec } from './store-utils-
|
|
2
|
-
export { Y as Action, X as ActionBinding, a4 as ActionBindingSchema, Z as ActionConfirm, a6 as ActionConfirmSchema, a1 as ActionDefinition, a3 as ActionExecutionContext, a0 as ActionHandler, $ as ActionOnError, a8 as ActionOnErrorSchema, _ as ActionOnSuccess, a7 as ActionOnSuccessSchema, a5 as ActionSchema, C as ComponentSchema, e as DynamicBoolean, r as DynamicBooleanSchema, d as DynamicNumber, q as DynamicNumberSchema, c as DynamicString, p as DynamicStringSchema, o as DynamicValueSchema, F as FlatElement, f as IndexCondition, I as ItemCondition, J as JsonPatch, M as MixedStreamCallbacks, l as MixedStreamParser, P as PatchOp, a2 as ResolvedAction, Q as SPEC_DATA_PART, R as SPEC_DATA_PART_TYPE, g as SingleCondition, n as SpecDataPart, k as SpecStreamCompiler, j as SpecStreamLine, h as StateStore, T as StoreAdapterConfig, m as StreamChunk, U as UIElement, i as ValidationMode, ad as action, ac as actionBinding, v as addByPath, B as applySpecPatch, z as applySpecStreamPatch, G as compileSpecStream, L as createJsonRenderTransform, K as createMixedStreamParser, H as createSpecStreamCompiler, W as createStateStore, aa as executeAction, x as findFormValue, t as getByPath, ab as interpolateString, E as nestedToFlat, y as parseSpecStreamLine, N as pipeJsonRender, w as removeByPath, a9 as resolveAction, s as resolveDynamicValue, u as setByPath } from './store-utils-
|
|
1
|
+
import { S as StateModel, V as VisibilityCondition, a as StateCondition, A as AndCondition, O as OrCondition, D as DynamicValue, b as Spec } from './store-utils-D98Czbil.js';
|
|
2
|
+
export { Y as Action, X as ActionBinding, a4 as ActionBindingSchema, Z as ActionConfirm, a6 as ActionConfirmSchema, a1 as ActionDefinition, a3 as ActionExecutionContext, a0 as ActionHandler, $ as ActionOnError, a8 as ActionOnErrorSchema, _ as ActionOnSuccess, a7 as ActionOnSuccessSchema, a5 as ActionSchema, C as ComponentSchema, e as DynamicBoolean, r as DynamicBooleanSchema, d as DynamicNumber, q as DynamicNumberSchema, c as DynamicString, p as DynamicStringSchema, o as DynamicValueSchema, F as FlatElement, f as IndexCondition, I as ItemCondition, J as JsonPatch, M as MixedStreamCallbacks, l as MixedStreamParser, P as PatchOp, a2 as ResolvedAction, Q as SPEC_DATA_PART, R as SPEC_DATA_PART_TYPE, g as SingleCondition, n as SpecDataPart, k as SpecStreamCompiler, j as SpecStreamLine, h as StateStore, T as StoreAdapterConfig, m as StreamChunk, U as UIElement, i as ValidationMode, ad as action, ac as actionBinding, v as addByPath, B as applySpecPatch, z as applySpecStreamPatch, G as compileSpecStream, L as createJsonRenderTransform, K as createMixedStreamParser, H as createSpecStreamCompiler, W as createStateStore, aa as executeAction, x as findFormValue, t as getByPath, ab as interpolateString, E as nestedToFlat, y as parseSpecStreamLine, N as pipeJsonRender, w as removeByPath, a9 as resolveAction, s as resolveDynamicValue, u as setByPath } from './store-utils-D98Czbil.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -86,6 +86,10 @@ declare const visibility: {
|
|
|
86
86
|
* repeat item — resolves via `repeatBasePath + path` and exposes the
|
|
87
87
|
* absolute state path for write-back.
|
|
88
88
|
* - `{ $cond, $then, $else }` conditionally picks a value
|
|
89
|
+
* - `{ $computed: string, args?: Record<string, PropExpression> }` calls a
|
|
90
|
+
* registered function with resolved args and returns the result
|
|
91
|
+
* - `{ $template: string }` interpolates `${/path}` references in the
|
|
92
|
+
* string with values from the state model
|
|
89
93
|
* - Any other value is a literal (passthrough)
|
|
90
94
|
*/
|
|
91
95
|
type PropExpression<T = unknown> = T | {
|
|
@@ -102,7 +106,17 @@ type PropExpression<T = unknown> = T | {
|
|
|
102
106
|
$cond: VisibilityCondition;
|
|
103
107
|
$then: PropExpression<T>;
|
|
104
108
|
$else: PropExpression<T>;
|
|
109
|
+
} | {
|
|
110
|
+
$computed: string;
|
|
111
|
+
args?: Record<string, unknown>;
|
|
112
|
+
} | {
|
|
113
|
+
$template: string;
|
|
105
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Function signature for `$computed` expressions.
|
|
117
|
+
* Receives a record of resolved argument values and returns a computed result.
|
|
118
|
+
*/
|
|
119
|
+
type ComputedFunction = (args: Record<string, unknown>) => unknown;
|
|
106
120
|
/**
|
|
107
121
|
* Context for resolving prop expressions.
|
|
108
122
|
* Extends {@link VisibilityContext} with an optional `repeatBasePath` used
|
|
@@ -111,6 +125,8 @@ type PropExpression<T = unknown> = T | {
|
|
|
111
125
|
interface PropResolutionContext extends VisibilityContext {
|
|
112
126
|
/** Absolute state path to the current repeat item (e.g. "/todos/0"). Set inside repeat scopes. */
|
|
113
127
|
repeatBasePath?: string;
|
|
128
|
+
/** Named functions available for `$computed` expressions. */
|
|
129
|
+
functions?: Record<string, ComputedFunction>;
|
|
114
130
|
}
|
|
115
131
|
/**
|
|
116
132
|
* Resolve a single prop value that may contain expressions.
|
|
@@ -266,7 +282,12 @@ declare const check: {
|
|
|
266
282
|
min: (min: number, message?: string) => ValidationCheck;
|
|
267
283
|
max: (max: number, message?: string) => ValidationCheck;
|
|
268
284
|
url: (message?: string) => ValidationCheck;
|
|
285
|
+
numeric: (message?: string) => ValidationCheck;
|
|
269
286
|
matches: (otherPath: string, message?: string) => ValidationCheck;
|
|
287
|
+
equalTo: (otherPath: string, message?: string) => ValidationCheck;
|
|
288
|
+
lessThan: (otherPath: string, message?: string) => ValidationCheck;
|
|
289
|
+
greaterThan: (otherPath: string, message?: string) => ValidationCheck;
|
|
290
|
+
requiredIf: (fieldPath: string, message?: string) => ValidationCheck;
|
|
270
291
|
};
|
|
271
292
|
|
|
272
293
|
/**
|
|
@@ -284,7 +305,7 @@ interface SpecIssue {
|
|
|
284
305
|
/** The element key where the issue was found (if applicable) */
|
|
285
306
|
elementKey?: string;
|
|
286
307
|
/** Machine-readable issue code for programmatic handling */
|
|
287
|
-
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props";
|
|
308
|
+
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props" | "watch_in_props";
|
|
288
309
|
}
|
|
289
310
|
/**
|
|
290
311
|
* Result of spec structural validation.
|
|
@@ -605,4 +626,4 @@ interface UserPromptOptions {
|
|
|
605
626
|
*/
|
|
606
627
|
declare function buildUserPrompt(options: UserPromptOptions): string;
|
|
607
628
|
|
|
608
|
-
export { AndCondition, type BuiltInAction, type Catalog, DynamicValue, type InferActionParams, type InferCatalogActions, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, OrCondition, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type PropResolutionContext, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, Spec, type SpecIssue, type SpecIssueSeverity, type SpecValidationIssues, type SpecValidationResult, StateCondition, StateModel, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationResult, VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, defineCatalog, defineSchema, evaluateVisibility, formatSpecIssues, resolveActionParam, resolveBindings, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, validateSpec, visibility };
|
|
629
|
+
export { AndCondition, type BuiltInAction, type Catalog, type ComputedFunction, DynamicValue, type InferActionParams, type InferCatalogActions, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, OrCondition, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type PropResolutionContext, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, Spec, type SpecIssue, type SpecIssueSeverity, type SpecValidationIssues, type SpecValidationResult, StateCondition, StateModel, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationResult, VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, defineCatalog, defineSchema, evaluateVisibility, formatSpecIssues, resolveActionParam, resolveBindings, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, validateSpec, visibility };
|