@json-render/core 0.5.1 → 0.6.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/dist/index.d.ts CHANGED
@@ -33,7 +33,7 @@ type ActionOnError = {
33
33
  *
34
34
  * Used inside the `on` field of a UIElement:
35
35
  * ```json
36
- * { "on": { "press": { "action": "setState", "params": { "path": "/x", "value": 1 } } } }
36
+ * { "on": { "press": { "action": "setState", "params": { "statePath": "/x", "value": 1 } } } }
37
37
  * ```
38
38
  */
39
39
  interface ActionBinding {
@@ -89,7 +89,7 @@ declare const ActionOnErrorSchema: z.ZodUnion<readonly [z.ZodObject<{
89
89
  declare const ActionBindingSchema: z.ZodObject<{
90
90
  action: z.ZodString;
91
91
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
92
- path: z.ZodString;
92
+ $state: z.ZodString;
93
93
  }, z.core.$strip>]>>>;
94
94
  confirm: z.ZodOptional<z.ZodObject<{
95
95
  title: z.ZodString;
@@ -120,7 +120,7 @@ declare const ActionBindingSchema: z.ZodObject<{
120
120
  declare const ActionSchema: z.ZodObject<{
121
121
  action: z.ZodString;
122
122
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
123
- path: z.ZodString;
123
+ $state: z.ZodString;
124
124
  }, z.core.$strip>]>>>;
125
125
  confirm: z.ZodOptional<z.ZodObject<{
126
126
  title: z.ZodString;
@@ -219,10 +219,13 @@ declare const action: {
219
219
  };
220
220
 
221
221
  /**
222
- * Dynamic value - can be a literal or a path reference to state model
222
+ * Dynamic value - can be a literal or a `{ $state }` reference to the state model.
223
+ *
224
+ * Used in action params and validation args where values can either be
225
+ * hardcoded or resolved from state at runtime.
223
226
  */
224
227
  type DynamicValue<T = unknown> = T | {
225
- path: string;
228
+ $state: string;
226
229
  };
227
230
  /**
228
231
  * Dynamic string value
@@ -240,16 +243,16 @@ type DynamicBoolean = DynamicValue<boolean>;
240
243
  * Zod schema for dynamic values
241
244
  */
242
245
  declare const DynamicValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
243
- path: z.ZodString;
246
+ $state: z.ZodString;
244
247
  }, z.core.$strip>]>;
245
248
  declare const DynamicStringSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
246
- path: z.ZodString;
249
+ $state: z.ZodString;
247
250
  }, z.core.$strip>]>;
248
251
  declare const DynamicNumberSchema: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
249
- path: z.ZodString;
252
+ $state: z.ZodString;
250
253
  }, z.core.$strip>]>;
251
254
  declare const DynamicBooleanSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
252
- path: z.ZodString;
255
+ $state: z.ZodString;
253
256
  }, z.core.$strip>]>;
254
257
  /**
255
258
  * Base UI element structure for v2
@@ -267,7 +270,7 @@ interface UIElement<T extends string = string, P = Record<string, unknown>> {
267
270
  on?: Record<string, ActionBinding | ActionBinding[]>;
268
271
  /** Repeat children once per item in a state array */
269
272
  repeat?: {
270
- path: string;
273
+ statePath: string;
271
274
  key?: string;
272
275
  };
273
276
  }
@@ -283,37 +286,84 @@ interface FlatElement<T extends string = string, P = Record<string, unknown>> ex
283
286
  parentKey?: string | null;
284
287
  }
285
288
  /**
286
- * Visibility condition types
289
+ * Shared comparison operators for visibility conditions.
290
+ *
291
+ * Use at most ONE comparison operator per condition. If multiple are
292
+ * provided, only the first matching one is evaluated (precedence:
293
+ * eq > neq > gt > gte > lt > lte). With no operator, truthiness is checked.
294
+ *
295
+ * `not` inverts the final result of whichever operator (or truthiness
296
+ * check) is used.
297
+ */
298
+ type ComparisonOperators = {
299
+ eq?: unknown;
300
+ neq?: unknown;
301
+ gt?: number | {
302
+ $state: string;
303
+ };
304
+ gte?: number | {
305
+ $state: string;
306
+ };
307
+ lt?: number | {
308
+ $state: string;
309
+ };
310
+ lte?: number | {
311
+ $state: string;
312
+ };
313
+ not?: true;
314
+ };
315
+ /**
316
+ * A single state-based condition.
317
+ * Resolves `$state` to a value from the state model, then applies the operator.
318
+ * Without an operator, checks truthiness.
319
+ *
320
+ * When `not` is `true`, the result of the entire condition is inverted.
321
+ * For example `{ $state: "/count", gt: 5, not: true }` means "NOT greater than 5".
287
322
  */
288
- type VisibilityCondition = boolean | {
289
- path: string;
290
- } | {
291
- auth: "signedIn" | "signedOut";
292
- } | LogicExpression;
323
+ type StateCondition = {
324
+ $state: string;
325
+ } & ComparisonOperators;
293
326
  /**
294
- * Logic expression for complex conditions
327
+ * A condition that resolves `$item` to a field on the current repeat item.
328
+ * Only meaningful inside a `repeat` scope.
329
+ *
330
+ * Use `""` to reference the whole item, or `"field"` for a specific field.
295
331
  */
296
- type LogicExpression = {
297
- and: LogicExpression[];
298
- } | {
299
- or: LogicExpression[];
300
- } | {
301
- not: LogicExpression;
302
- } | {
303
- path: string;
304
- } | {
305
- eq: [DynamicValue, DynamicValue];
306
- } | {
307
- neq: [DynamicValue, DynamicValue];
308
- } | {
309
- gt: [DynamicValue<number>, DynamicValue<number>];
310
- } | {
311
- gte: [DynamicValue<number>, DynamicValue<number>];
312
- } | {
313
- lt: [DynamicValue<number>, DynamicValue<number>];
314
- } | {
315
- lte: [DynamicValue<number>, DynamicValue<number>];
332
+ type ItemCondition = {
333
+ $item: string;
334
+ } & ComparisonOperators;
335
+ /**
336
+ * A condition that resolves `$index` to the current repeat array index.
337
+ * Only meaningful inside a `repeat` scope.
338
+ */
339
+ type IndexCondition = {
340
+ $index: true;
341
+ } & ComparisonOperators;
342
+ /** A single visibility condition (state, item, or index). */
343
+ type SingleCondition = StateCondition | ItemCondition | IndexCondition;
344
+ /**
345
+ * AND wrapper — all child conditions must be true.
346
+ * This is the explicit form of the implicit array AND (`SingleCondition[]`).
347
+ * Unlike the implicit form, `$and` supports nested `$or` and `$and` conditions.
348
+ */
349
+ type AndCondition = {
350
+ $and: VisibilityCondition[];
351
+ };
352
+ /**
353
+ * OR wrapper — at least one child condition must be true.
354
+ */
355
+ type OrCondition = {
356
+ $or: VisibilityCondition[];
316
357
  };
358
+ /**
359
+ * Visibility condition types.
360
+ * - `boolean` — always/never
361
+ * - `SingleCondition` — single condition (`$state`, `$item`, or `$index`)
362
+ * - `SingleCondition[]` — implicit AND (all must be true)
363
+ * - `AndCondition` — `{ $and: [...] }`, explicit AND (all must be true)
364
+ * - `OrCondition` — `{ $or: [...] }`, at least one must be true
365
+ */
366
+ type VisibilityCondition = boolean | SingleCondition | SingleCondition[] | AndCondition | OrCondition;
317
367
  /**
318
368
  * Flat UI tree structure (optimized for LLM generation)
319
369
  */
@@ -326,13 +376,6 @@ interface Spec {
326
376
  * Components using statePath will read from / write to this state. */
327
377
  state?: Record<string, unknown>;
328
378
  }
329
- /**
330
- * Auth state for visibility evaluation
331
- */
332
- interface AuthState {
333
- isSignedIn: boolean;
334
- user?: Record<string, unknown>;
335
- }
336
379
  /**
337
380
  * State model type
338
381
  */
@@ -386,22 +429,22 @@ declare function addByPath(obj: Record<string, unknown>, path: string, value: un
386
429
  */
387
430
  declare function removeByPath(obj: Record<string, unknown>, path: string): void;
388
431
  /**
389
- * Find a form value from params and/or data.
432
+ * Find a form value from params and/or state.
390
433
  * Useful in action handlers to locate form input values regardless of path format.
391
434
  *
392
435
  * Checks in order:
393
436
  * 1. Direct param key (if not a path reference)
394
437
  * 2. Param keys ending with the field name
395
- * 3. Data keys ending with the field name (dot notation)
396
- * 4. Data paths using getByPath (slash notation)
438
+ * 3. State keys ending with the field name (dot notation)
439
+ * 4. State path using getByPath (slash notation)
397
440
  *
398
441
  * @example
399
- * // Find "name" from params or data
400
- * const name = findFormValue("name", params, data);
442
+ * // Find "name" from params or state
443
+ * const name = findFormValue("name", params, state);
401
444
  *
402
- * // Will find from: params.name, params["form.name"], data["customerForm.name"], data.customerForm.name
445
+ * // Will find from: params.name, params["form.name"], state["form.name"], or getByPath(state, "name")
403
446
  */
404
- declare function findFormValue(fieldName: string, params?: Record<string, unknown>, data?: Record<string, unknown>): unknown;
447
+ declare function findFormValue(fieldName: string, params?: Record<string, unknown>, state?: Record<string, unknown>): unknown;
405
448
  /**
406
449
  * A SpecStream line - a single patch operation in the stream.
407
450
  */
@@ -423,6 +466,54 @@ declare function parseSpecStreamLine(line: string): SpecStreamLine | null;
423
466
  * @throws {Error} If a "test" operation fails (value mismatch).
424
467
  */
425
468
  declare function applySpecStreamPatch<T extends Record<string, unknown>>(obj: T, patch: SpecStreamLine): T;
469
+ /**
470
+ * Apply a single RFC 6902 JSON Patch operation to a Spec.
471
+ * Mutates the spec in place and returns it.
472
+ *
473
+ * This is a typed convenience wrapper around `applySpecStreamPatch` that
474
+ * accepts a `Spec` directly without requiring a cast to `Record<string, unknown>`.
475
+ *
476
+ * Note: This mutates the spec. For React state updates, spread the result
477
+ * to create a new reference: `setSpec({ ...applySpecPatch(spec, patch) })`.
478
+ *
479
+ * @example
480
+ * let spec: Spec = { root: "", elements: {} };
481
+ * applySpecPatch(spec, { op: "add", path: "/root", value: "main" });
482
+ */
483
+ declare function applySpecPatch(spec: Spec, patch: SpecStreamLine): Spec;
484
+ /**
485
+ * Convert a nested (tree-structured) spec into the flat `Spec` format used
486
+ * by json-render renderers.
487
+ *
488
+ * In the nested format each node has inline `children` as an array of child
489
+ * objects. This function walks the tree, assigns auto-generated keys
490
+ * (`el-0`, `el-1`, ...), and produces a flat `{ root, elements, state }` spec.
491
+ *
492
+ * The top-level `state` field (if present on the root node) is hoisted to
493
+ * `spec.state`.
494
+ *
495
+ * @example
496
+ * ```ts
497
+ * const nested = {
498
+ * type: "Card",
499
+ * props: { title: "Hello" },
500
+ * children: [
501
+ * { type: "Text", props: { content: "World" } },
502
+ * ],
503
+ * state: { count: 0 },
504
+ * };
505
+ * const spec = nestedToFlat(nested);
506
+ * // {
507
+ * // root: "el-0",
508
+ * // elements: {
509
+ * // "el-0": { type: "Card", props: { title: "Hello" }, children: ["el-1"] },
510
+ * // "el-1": { type: "Text", props: { content: "World" }, children: [] },
511
+ * // },
512
+ * // state: { count: 0 },
513
+ * // }
514
+ * ```
515
+ */
516
+ declare function nestedToFlat(nested: Record<string, unknown>): Spec;
426
517
  /**
427
518
  * Compile a SpecStream string into a JSON object.
428
519
  * Each line should be a patch operation.
@@ -485,101 +576,316 @@ interface SpecStreamCompiler<T> {
485
576
  * }
486
577
  */
487
578
  declare function createSpecStreamCompiler<T = Record<string, unknown>>(initial?: Partial<T>): SpecStreamCompiler<T>;
488
-
489
579
  /**
490
- * Logic expression schema (recursive)
491
- * Using a more permissive schema that aligns with runtime behavior
580
+ * Callbacks for the mixed stream parser.
581
+ */
582
+ interface MixedStreamCallbacks {
583
+ /** Called when a JSONL patch line is parsed */
584
+ onPatch: (patch: SpecStreamLine) => void;
585
+ /** Called when a text (non-JSONL) line is received */
586
+ onText: (text: string) => void;
587
+ }
588
+ /**
589
+ * A stateful parser for mixed streams that contain both text and JSONL patches.
590
+ * Used in chat + GenUI scenarios where an LLM responds with conversational text
591
+ * interleaved with json-render JSONL patch operations.
592
+ */
593
+ interface MixedStreamParser {
594
+ /** Push a chunk of streamed data. Calls onPatch/onText for each complete line. */
595
+ push(chunk: string): void;
596
+ /** Flush any remaining buffered content. Call when the stream ends. */
597
+ flush(): void;
598
+ }
599
+ /**
600
+ * Create a parser for mixed text + JSONL streams.
601
+ *
602
+ * In chat + GenUI scenarios, an LLM streams a response that contains both
603
+ * conversational text and json-render JSONL patch lines. This parser buffers
604
+ * incoming chunks, splits them into lines, and classifies each line as either
605
+ * a JSONL patch (via `parseSpecStreamLine`) or plain text.
606
+ *
607
+ * @example
608
+ * const parser = createMixedStreamParser({
609
+ * onText: (text) => appendToMessage(text),
610
+ * onPatch: (patch) => applySpecPatch(spec, patch),
611
+ * });
612
+ *
613
+ * // As chunks arrive from the stream:
614
+ * for await (const chunk of stream) {
615
+ * parser.push(chunk);
616
+ * }
617
+ * parser.flush();
492
618
  */
493
- declare const LogicExpressionSchema: z.ZodType<LogicExpression>;
619
+ declare function createMixedStreamParser(callbacks: MixedStreamCallbacks): MixedStreamParser;
494
620
  /**
495
- * Visibility condition schema
621
+ * Minimal chunk shape compatible with the AI SDK's `UIMessageChunk`.
622
+ *
623
+ * Defined here so that `@json-render/core` has no dependency on the `ai`
624
+ * package. The discriminated union covers the three text-related chunk types
625
+ * the transform inspects; all other chunk types pass through via the fallback.
626
+ */
627
+ type StreamChunk = {
628
+ type: "text-start";
629
+ id: string;
630
+ [k: string]: unknown;
631
+ } | {
632
+ type: "text-delta";
633
+ id: string;
634
+ delta: string;
635
+ [k: string]: unknown;
636
+ } | {
637
+ type: "text-end";
638
+ id: string;
639
+ [k: string]: unknown;
640
+ } | {
641
+ type: string;
642
+ [k: string]: unknown;
643
+ };
644
+ /**
645
+ * Creates a `TransformStream` that intercepts AI SDK UI message stream chunks
646
+ * and classifies text content as either prose or json-render JSONL patches.
647
+ *
648
+ * Two classification modes:
649
+ *
650
+ * 1. **Fence mode** (preferred): Lines between ` ```spec ` and ` ``` ` are
651
+ * parsed as JSONL patches. Fence delimiters are swallowed (not emitted).
652
+ * 2. **Heuristic mode** (backward compat): Outside of fences, lines starting
653
+ * with `{` are buffered and tested with `parseSpecStreamLine`. Valid patches
654
+ * are emitted as {@link SPEC_DATA_PART_TYPE} parts; everything else is
655
+ * flushed as text.
656
+ *
657
+ * Non-text chunks (tool events, step markers, etc.) are passed through unchanged.
658
+ *
659
+ * @example
660
+ * ```ts
661
+ * import { createJsonRenderTransform } from "@json-render/core";
662
+ * import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
663
+ *
664
+ * const stream = createUIMessageStream({
665
+ * execute: async ({ writer }) => {
666
+ * writer.merge(
667
+ * result.toUIMessageStream().pipeThrough(createJsonRenderTransform()),
668
+ * );
669
+ * },
670
+ * });
671
+ * return createUIMessageStreamResponse({ stream });
672
+ * ```
673
+ */
674
+ declare function createJsonRenderTransform(): TransformStream<StreamChunk, StreamChunk>;
675
+ /**
676
+ * The key registered in `AppDataParts` for json-render specs.
677
+ * The AI SDK automatically prefixes this with `"data-"` on the wire,
678
+ * so the actual stream chunk type is `"data-spec"` (see {@link SPEC_DATA_PART_TYPE}).
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * import { SPEC_DATA_PART, type SpecDataPart } from "@json-render/core";
683
+ * type AppDataParts = { [SPEC_DATA_PART]: SpecDataPart };
684
+ * ```
685
+ */
686
+ declare const SPEC_DATA_PART: "spec";
687
+ /**
688
+ * The wire-format type string as it appears in stream chunks and message parts.
689
+ * This is `"data-"` + {@link SPEC_DATA_PART} — i.e. `"data-spec"`.
690
+ *
691
+ * Use this constant when filtering message parts or enqueuing stream chunks.
692
+ */
693
+ declare const SPEC_DATA_PART_TYPE: "data-spec";
694
+ /**
695
+ * Discriminated union for the payload of a {@link SPEC_DATA_PART_TYPE} SSE part.
696
+ *
697
+ * - `"patch"`: A single RFC 6902 JSON Patch operation (streaming, progressive UI).
698
+ * - `"flat"`: A complete flat spec with `root`, `elements`, and optional `state`.
699
+ * - `"nested"`: A complete nested spec (tree structure — schema depends on catalog).
700
+ */
701
+ type SpecDataPart = {
702
+ type: "patch";
703
+ patch: JsonPatch;
704
+ } | {
705
+ type: "flat";
706
+ spec: Spec;
707
+ } | {
708
+ type: "nested";
709
+ spec: Record<string, unknown>;
710
+ };
711
+ /**
712
+ * Convenience wrapper that pipes an AI SDK UI message stream through the
713
+ * json-render transform, classifying text as prose or JSONL patches.
714
+ *
715
+ * Eliminates the need for manual `pipeThrough(createJsonRenderTransform())`
716
+ * and the associated type cast.
717
+ *
718
+ * @example
719
+ * ```ts
720
+ * import { pipeJsonRender } from "@json-render/core";
721
+ *
722
+ * const stream = createUIMessageStream({
723
+ * execute: async ({ writer }) => {
724
+ * writer.merge(pipeJsonRender(result.toUIMessageStream()));
725
+ * },
726
+ * });
727
+ * return createUIMessageStreamResponse({ stream });
728
+ * ```
729
+ */
730
+ declare function pipeJsonRender<T = StreamChunk>(stream: ReadableStream<T>): ReadableStream<T>;
731
+
732
+ /**
733
+ * Visibility condition schema.
734
+ *
735
+ * Lazy because `OrCondition` can recursively contain `VisibilityCondition`.
496
736
  */
497
737
  declare const VisibilityConditionSchema: z.ZodType<VisibilityCondition>;
498
738
  /**
499
- * Context for evaluating visibility
739
+ * Context for evaluating visibility conditions.
740
+ *
741
+ * `repeatItem` and `repeatIndex` are only present inside a `repeat` scope
742
+ * and enable `$item` / `$index` conditions.
500
743
  */
501
744
  interface VisibilityContext {
502
745
  stateModel: StateModel;
503
- authState?: AuthState;
746
+ /** The current repeat item (set inside a repeat scope). */
747
+ repeatItem?: unknown;
748
+ /** The current repeat array index (set inside a repeat scope). */
749
+ repeatIndex?: number;
504
750
  }
505
751
  /**
506
- * Evaluate a logic expression against data and auth state
507
- */
508
- declare function evaluateLogicExpression(expr: LogicExpression, ctx: VisibilityContext): boolean;
509
- /**
510
- * Evaluate a visibility condition
752
+ * Evaluate a visibility condition.
753
+ *
754
+ * - `undefined` visible
755
+ * - `boolean` → that value
756
+ * - `SingleCondition` evaluate single condition
757
+ * - `SingleCondition[]` → implicit AND (all must be true)
758
+ * - `AndCondition` → `{ $and: [...] }`, explicit AND
759
+ * - `OrCondition` → `{ $or: [...] }`, at least one must be true
511
760
  */
512
761
  declare function evaluateVisibility(condition: VisibilityCondition | undefined, ctx: VisibilityContext): boolean;
513
762
  /**
514
- * Helper to create visibility conditions
763
+ * Helper to create visibility conditions.
515
764
  */
516
765
  declare const visibility: {
517
766
  /** Always visible */
518
767
  always: true;
519
768
  /** Never visible */
520
769
  never: false;
521
- /** Visible when path is truthy */
522
- when: (path: string) => VisibilityCondition;
523
- /** Visible when signed in */
524
- signedIn: {
525
- readonly auth: "signedIn";
526
- };
527
- /** Visible when signed out */
528
- signedOut: {
529
- readonly auth: "signedOut";
530
- };
531
- /** AND multiple conditions */
532
- and: (...conditions: LogicExpression[]) => LogicExpression;
533
- /** OR multiple conditions */
534
- or: (...conditions: LogicExpression[]) => LogicExpression;
535
- /** NOT a condition */
536
- not: (condition: LogicExpression) => LogicExpression;
770
+ /** Visible when state path is truthy */
771
+ when: (path: string) => StateCondition;
772
+ /** Visible when state path is falsy */
773
+ unless: (path: string) => StateCondition;
537
774
  /** Equality check */
538
- eq: (left: DynamicValue, right: DynamicValue) => LogicExpression;
775
+ eq: (path: string, value: unknown) => StateCondition;
539
776
  /** Not equal check */
540
- neq: (left: DynamicValue, right: DynamicValue) => LogicExpression;
777
+ neq: (path: string, value: unknown) => StateCondition;
541
778
  /** Greater than */
542
- gt: (left: DynamicValue<number>, right: DynamicValue<number>) => LogicExpression;
779
+ gt: (path: string, value: number | {
780
+ $state: string;
781
+ }) => StateCondition;
543
782
  /** Greater than or equal */
544
- gte: (left: DynamicValue<number>, right: DynamicValue<number>) => LogicExpression;
783
+ gte: (path: string, value: number | {
784
+ $state: string;
785
+ }) => StateCondition;
545
786
  /** Less than */
546
- lt: (left: DynamicValue<number>, right: DynamicValue<number>) => LogicExpression;
787
+ lt: (path: string, value: number | {
788
+ $state: string;
789
+ }) => StateCondition;
547
790
  /** Less than or equal */
548
- lte: (left: DynamicValue<number>, right: DynamicValue<number>) => LogicExpression;
791
+ lte: (path: string, value: number | {
792
+ $state: string;
793
+ }) => StateCondition;
794
+ /** AND multiple conditions */
795
+ and: (...conditions: VisibilityCondition[]) => AndCondition;
796
+ /** OR multiple conditions */
797
+ or: (...conditions: VisibilityCondition[]) => OrCondition;
549
798
  };
550
799
 
551
800
  /**
552
801
  * A prop expression that resolves to a value based on state.
553
802
  *
554
- * - `{ $path: string }` reads a value from the state model
803
+ * - `{ $state: string }` reads a value from the global state model
804
+ * - `{ $item: string }` reads a field from the current repeat item
805
+ * (relative path into the item object; use `""` for the whole item)
806
+ * - `{ $index: true }` returns the current repeat array index. Uses `true`
807
+ * as a sentinel flag because the index is a scalar with no sub-path to
808
+ * navigate — unlike `$item` which needs a path into the item object.
809
+ * - `{ $bindState: string }` two-way binding to a global state path —
810
+ * resolves to the value at the path (like `$state`) AND exposes the
811
+ * resolved path so the component can write back.
812
+ * - `{ $bindItem: string }` two-way binding to a field on the current
813
+ * repeat item — resolves via `repeatBasePath + path` and exposes the
814
+ * absolute state path for write-back.
555
815
  * - `{ $cond, $then, $else }` conditionally picks a value
556
816
  * - Any other value is a literal (passthrough)
557
817
  */
558
818
  type PropExpression<T = unknown> = T | {
559
- $path: string;
819
+ $state: string;
820
+ } | {
821
+ $item: string;
822
+ } | {
823
+ $index: true;
824
+ } | {
825
+ $bindState: string;
826
+ } | {
827
+ $bindItem: string;
560
828
  } | {
561
829
  $cond: VisibilityCondition;
562
830
  $then: PropExpression<T>;
563
831
  $else: PropExpression<T>;
564
832
  };
833
+ /**
834
+ * Context for resolving prop expressions.
835
+ * Extends {@link VisibilityContext} with an optional `repeatBasePath` used
836
+ * to resolve `$bindItem` paths to absolute state paths.
837
+ */
838
+ interface PropResolutionContext extends VisibilityContext {
839
+ /** Absolute state path to the current repeat item (e.g. "/todos/0"). Set inside repeat scopes. */
840
+ repeatBasePath?: string;
841
+ }
565
842
  /**
566
843
  * Resolve a single prop value that may contain expressions.
567
- * Recursively resolves $path and $cond/$then/$else expressions.
844
+ * Handles $state, $item, $index, $bindState, $bindItem, and $cond/$then/$else in a single pass.
568
845
  */
569
- declare function resolvePropValue(value: unknown, ctx: VisibilityContext): unknown;
846
+ declare function resolvePropValue(value: unknown, ctx: PropResolutionContext): unknown;
570
847
  /**
571
848
  * Resolve all prop values in an element's props object.
572
849
  * Returns a new props object with all expressions resolved.
573
850
  */
574
- declare function resolveElementProps(props: Record<string, unknown>, ctx: VisibilityContext): Record<string, unknown>;
851
+ declare function resolveElementProps(props: Record<string, unknown>, ctx: PropResolutionContext): Record<string, unknown>;
852
+ /**
853
+ * Scan an element's raw props for `$bindState` / `$bindItem` expressions
854
+ * and return a map of prop name → resolved absolute state path.
855
+ *
856
+ * This is called **before** `resolveElementProps` so the component can
857
+ * receive both the resolved value (in `props`) and the write-back path
858
+ * (in `bindings`).
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * const rawProps = { value: { $bindState: "/form/email" }, label: "Email" };
863
+ * const bindings = resolveBindings(rawProps, ctx);
864
+ * // bindings = { value: "/form/email" }
865
+ * ```
866
+ */
867
+ declare function resolveBindings(props: Record<string, unknown>, ctx: PropResolutionContext): Record<string, string> | undefined;
868
+ /**
869
+ * Resolve a single action parameter value.
870
+ *
871
+ * Like {@link resolvePropValue} but with special handling for path-valued
872
+ * params: `{ $item: "field" }` resolves to an **absolute state path**
873
+ * (e.g. `/todos/0/field`) instead of the field's value, so the path can
874
+ * be passed to `setState` / `pushState` / `removeState`.
875
+ *
876
+ * - `{ $item: "field" }` → absolute state path via `repeatBasePath`
877
+ * - `{ $index: true }` → current repeat index (number)
878
+ * - Everything else delegates to `resolvePropValue` ($state, $cond, literals).
879
+ */
880
+ declare function resolveActionParam(value: unknown, ctx: PropResolutionContext): unknown;
575
881
 
576
882
  /**
577
883
  * Validation check definition
578
884
  */
579
885
  interface ValidationCheck {
580
- /** Function name (built-in or from catalog) */
581
- fn: string;
582
- /** Additional arguments for the function */
886
+ /** Validation type (built-in or from catalog) */
887
+ type: string;
888
+ /** Additional arguments for the validation */
583
889
  args?: Record<string, DynamicValue>;
584
890
  /** Error message to display if check fails */
585
891
  message: string;
@@ -593,15 +899,15 @@ interface ValidationConfig {
593
899
  /** When to run validation */
594
900
  validateOn?: "change" | "blur" | "submit";
595
901
  /** Condition for when validation is enabled */
596
- enabled?: LogicExpression;
902
+ enabled?: VisibilityCondition;
597
903
  }
598
904
  /**
599
905
  * Schema for validation check
600
906
  */
601
907
  declare const ValidationCheckSchema: z.ZodObject<{
602
- fn: z.ZodString;
908
+ type: z.ZodString;
603
909
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
604
- path: z.ZodString;
910
+ $state: z.ZodString;
605
911
  }, z.core.$strip>]>>>;
606
912
  message: z.ZodString;
607
913
  }, z.core.$strip>;
@@ -610,9 +916,9 @@ declare const ValidationCheckSchema: z.ZodObject<{
610
916
  */
611
917
  declare const ValidationConfigSchema: z.ZodObject<{
612
918
  checks: z.ZodOptional<z.ZodArray<z.ZodObject<{
613
- fn: z.ZodString;
919
+ type: z.ZodString;
614
920
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
615
- path: z.ZodString;
921
+ $state: z.ZodString;
616
922
  }, z.core.$strip>]>>>;
617
923
  message: z.ZodString;
618
924
  }, z.core.$strip>>>;
@@ -621,7 +927,7 @@ declare const ValidationConfigSchema: z.ZodObject<{
621
927
  blur: "blur";
622
928
  submit: "submit";
623
929
  }>>;
624
- enabled: z.ZodOptional<z.ZodType<LogicExpression, unknown, z.core.$ZodTypeInternals<LogicExpression, unknown>>>;
930
+ enabled: z.ZodOptional<z.ZodType<VisibilityCondition, unknown, z.core.$ZodTypeInternals<VisibilityCondition, unknown>>>;
625
931
  }, z.core.$strip>;
626
932
  /**
627
933
  * Validation function signature
@@ -644,7 +950,7 @@ declare const builtInValidationFunctions: Record<string, ValidationFunction>;
644
950
  * Validation result for a single check
645
951
  */
646
952
  interface ValidationCheckResult {
647
- fn: string;
953
+ type: string;
648
954
  valid: boolean;
649
955
  message: string;
650
956
  }
@@ -674,11 +980,7 @@ declare function runValidationCheck(check: ValidationCheck, ctx: ValidationConte
674
980
  /**
675
981
  * Run all validation checks for a field
676
982
  */
677
- declare function runValidation(config: ValidationConfig, ctx: ValidationContext & {
678
- authState?: {
679
- isSignedIn: boolean;
680
- };
681
- }): ValidationResult;
983
+ declare function runValidation(config: ValidationConfig, ctx: ValidationContext): ValidationResult;
682
984
  /**
683
985
  * Helper to create validation checks
684
986
  */
@@ -828,12 +1130,12 @@ interface Schema<TDef extends SchemaDefinition = SchemaDefinition> {
828
1130
  /** Default rules baked into the schema (injected before customRules) */
829
1131
  readonly defaultRules?: string[];
830
1132
  /** Create a catalog from this schema */
831
- createCatalog<TCatalog extends InferCatalogInput<TDef["catalog"]>>(catalog: TCatalog): Catalog$1<TDef, TCatalog>;
1133
+ createCatalog<TCatalog extends InferCatalogInput<TDef["catalog"]>>(catalog: TCatalog): Catalog<TDef, TCatalog>;
832
1134
  }
833
1135
  /**
834
1136
  * Catalog instance with methods
835
1137
  */
836
- interface Catalog$1<TDef extends SchemaDefinition = SchemaDefinition, TCatalog = unknown> {
1138
+ interface Catalog<TDef extends SchemaDefinition = SchemaDefinition, TCatalog = unknown> {
837
1139
  /** The schema this catalog is based on */
838
1140
  readonly schema: Schema<TDef>;
839
1141
  /** The catalog data */
@@ -861,6 +1163,14 @@ interface PromptOptions {
861
1163
  system?: string;
862
1164
  /** Additional rules to append */
863
1165
  customRules?: string[];
1166
+ /**
1167
+ * Output mode for the generated prompt.
1168
+ *
1169
+ * - `"generate"` (default): The LLM should output only JSONL patches (no prose).
1170
+ * - `"chat"`: The LLM should respond conversationally first, then output JSONL patches.
1171
+ * Includes rules about interleaving text with JSONL and not wrapping in code fences.
1172
+ */
1173
+ mode?: "generate" | "chat";
864
1174
  }
865
1175
  /**
866
1176
  * Context provided to prompt templates
@@ -902,28 +1212,28 @@ interface SpecValidationResult<T> {
902
1212
  * Extract the components map type from a catalog
903
1213
  * @example type Components = InferCatalogComponents<typeof myCatalog>;
904
1214
  */
905
- type InferCatalogComponents<C extends Catalog$1> = C extends Catalog$1<SchemaDefinition, infer TCatalog> ? TCatalog extends {
1215
+ type InferCatalogComponents<C extends Catalog> = C extends Catalog<SchemaDefinition, infer TCatalog> ? TCatalog extends {
906
1216
  components: infer Comps;
907
1217
  } ? Comps : never : never;
908
1218
  /**
909
1219
  * Extract the actions map type from a catalog
910
1220
  * @example type Actions = InferCatalogActions<typeof myCatalog>;
911
1221
  */
912
- type InferCatalogActions<C extends Catalog$1> = C extends Catalog$1<SchemaDefinition, infer TCatalog> ? TCatalog extends {
1222
+ type InferCatalogActions<C extends Catalog> = C extends Catalog<SchemaDefinition, infer TCatalog> ? TCatalog extends {
913
1223
  actions: infer Acts;
914
1224
  } ? Acts : never : never;
915
1225
  /**
916
1226
  * Infer component props from a catalog by component name
917
1227
  * @example type ButtonProps = InferComponentProps<typeof myCatalog, 'Button'>;
918
1228
  */
919
- type InferComponentProps<C extends Catalog$1, K extends keyof InferCatalogComponents<C>> = InferCatalogComponents<C>[K] extends {
1229
+ type InferComponentProps<C extends Catalog, K extends keyof InferCatalogComponents<C>> = InferCatalogComponents<C>[K] extends {
920
1230
  props: z.ZodType<infer P>;
921
1231
  } ? P : never;
922
1232
  /**
923
1233
  * Infer action params from a catalog by action name
924
1234
  * @example type ViewCustomersParams = InferActionParams<typeof myCatalog, 'viewCustomers'>;
925
1235
  */
926
- type InferActionParams<C extends Catalog$1, K extends keyof InferCatalogActions<C>> = InferCatalogActions<C>[K] extends {
1236
+ type InferActionParams<C extends Catalog, K extends keyof InferCatalogActions<C>> = InferCatalogActions<C>[K] extends {
927
1237
  params: z.ZodType<infer P>;
928
1238
  } ? P : never;
929
1239
  type InferCatalogInput<T> = T extends SchemaType<"object", infer Shape> ? {
@@ -965,7 +1275,7 @@ declare function defineSchema<TDef extends SchemaDefinition>(builder: (s: Schema
965
1275
  /**
966
1276
  * Shorthand: Define a catalog directly from a schema
967
1277
  */
968
- declare function defineCatalog<TDef extends SchemaDefinition, TCatalog extends InferCatalogInput<TDef["catalog"]>>(schema: Schema<TDef>, catalog: TCatalog): Catalog$1<TDef, TCatalog>;
1278
+ declare function defineCatalog<TDef extends SchemaDefinition, TCatalog extends InferCatalogInput<TDef["catalog"]>>(schema: Schema<TDef>, catalog: TCatalog): Catalog<TDef, TCatalog>;
969
1279
 
970
1280
  /**
971
1281
  * Options for building a user prompt.
@@ -1002,102 +1312,4 @@ interface UserPromptOptions {
1002
1312
  */
1003
1313
  declare function buildUserPrompt(options: UserPromptOptions): string;
1004
1314
 
1005
- /**
1006
- * Component definition with visibility and validation support
1007
- */
1008
- interface ComponentDefinition<TProps extends ComponentSchema = ComponentSchema> {
1009
- /** Zod schema for component props */
1010
- props: TProps;
1011
- /** Whether this component can have children */
1012
- hasChildren?: boolean;
1013
- /** Description for AI generation */
1014
- description?: string;
1015
- }
1016
- /**
1017
- * Catalog configuration
1018
- */
1019
- interface CatalogConfig<TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>> {
1020
- /** Catalog name */
1021
- name?: string;
1022
- /** Component definitions */
1023
- components: TComponents;
1024
- /** Action definitions with param schemas */
1025
- actions?: TActions;
1026
- /** Custom validation functions */
1027
- functions?: TFunctions;
1028
- /** Validation mode */
1029
- validation?: ValidationMode;
1030
- }
1031
- /**
1032
- * Catalog instance
1033
- */
1034
- interface Catalog<TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>> {
1035
- /** Catalog name */
1036
- readonly name: string;
1037
- /** Component names */
1038
- readonly componentNames: (keyof TComponents)[];
1039
- /** Action names */
1040
- readonly actionNames: (keyof TActions)[];
1041
- /** Function names */
1042
- readonly functionNames: (keyof TFunctions)[];
1043
- /** Validation mode */
1044
- readonly validation: ValidationMode;
1045
- /** Component definitions */
1046
- readonly components: TComponents;
1047
- /** Action definitions */
1048
- readonly actions: TActions;
1049
- /** Custom validation functions */
1050
- readonly functions: TFunctions;
1051
- /** Full element schema for AI generation */
1052
- readonly elementSchema: z.ZodType<UIElement>;
1053
- /** Full UI spec schema */
1054
- readonly specSchema: z.ZodType<Spec>;
1055
- /** Check if component exists */
1056
- hasComponent(type: string): boolean;
1057
- /** Check if action exists */
1058
- hasAction(name: string): boolean;
1059
- /** Check if function exists */
1060
- hasFunction(name: string): boolean;
1061
- /** Validate an element */
1062
- validateElement(element: unknown): {
1063
- success: boolean;
1064
- data?: UIElement;
1065
- error?: z.ZodError;
1066
- };
1067
- /** Validate a UI spec */
1068
- validateSpec(spec: unknown): {
1069
- success: boolean;
1070
- data?: Spec;
1071
- error?: z.ZodError;
1072
- };
1073
- }
1074
- /**
1075
- * Create a v2 catalog with visibility, actions, and validation support
1076
- */
1077
- declare function createCatalog<TComponents extends Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>>(config: CatalogConfig<TComponents, TActions, TFunctions>): Catalog<TComponents, TActions, TFunctions>;
1078
- /**
1079
- * Generate a prompt for AI that describes the catalog
1080
- */
1081
- declare function generateCatalogPrompt<TComponents extends Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction>>(catalog: Catalog<TComponents, TActions, TFunctions>): string;
1082
- /**
1083
- * Type helper to infer component props from catalog
1084
- */
1085
- type InferCatalogComponentProps<C extends Catalog<Record<string, ComponentDefinition>>> = {
1086
- [K in keyof C["components"]]: z.infer<C["components"][K]["props"]>;
1087
- };
1088
- /**
1089
- * Options for generating system prompts
1090
- */
1091
- interface SystemPromptOptions {
1092
- /** System message intro (replaces default) */
1093
- system?: string;
1094
- /** Additional rules to append to the rules section */
1095
- customRules?: string[];
1096
- }
1097
- /**
1098
- * Generate a complete system prompt for AI that can generate UI from a catalog.
1099
- * This produces a ready-to-use prompt that stays in sync with the catalog definition.
1100
- */
1101
- declare function generateSystemPrompt<TComponents extends Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction>>(catalog: Catalog<TComponents, TActions, TFunctions>, options?: SystemPromptOptions): string;
1102
-
1103
- export { type Action, type ActionBinding, ActionBindingSchema, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecIssue, type SpecIssueSeverity, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationIssues, type SpecValidationResult, type StateModel, type SystemPromptOptions, type UIElement, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, actionBinding, addByPath, applySpecStreamPatch, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, formatSpecIssues, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, setByPath, validateSpec, visibility };
1315
+ export { type Action, type ActionBinding, ActionBindingSchema, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AndCondition, type Catalog, type ComponentSchema, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type IndexCondition, type InferActionParams, type InferCatalogActions, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type ItemCondition, type JsonPatch, type MixedStreamCallbacks, type MixedStreamParser, type OrCondition, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type PropResolutionContext, type ResolvedAction, SPEC_DATA_PART, SPEC_DATA_PART_TYPE, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type SingleCondition, type Spec, type SpecDataPart, type SpecIssue, type SpecIssueSeverity, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationIssues, type SpecValidationResult, type StateCondition, type StateModel, type StreamChunk, type UIElement, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, actionBinding, addByPath, applySpecPatch, applySpecStreamPatch, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, compileSpecStream, createJsonRenderTransform, createMixedStreamParser, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateVisibility, executeAction, findFormValue, formatSpecIssues, getByPath, interpolateString, nestedToFlat, parseSpecStreamLine, pipeJsonRender, removeByPath, resolveAction, resolveActionParam, resolveBindings, resolveDynamicValue, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, setByPath, validateSpec, visibility };