DynamicValueSchema is narrower than resolveActionParam's actual contract

Author: charbelramiCreated May 23, 2026Updated May 23, 2026

Summary

DynamicValueSchema rejects shapes that resolveActionParam resolves correctly at runtime. This causes false positives for tools that schema-validate emitted specs against ActionBindingSchema.

Repro

typescript
import {
  ActionBindingSchema,
  DynamicValueSchema,
  resolveActionParam,
} from "@json-render/core"; // 0.19.0

const ctx = {
  stateModel: { form: { text: "hi" } },
  repeatBasePath: "/todos/2",
  repeatIndex: 2,
};

// Runtime accepts each shape:
resolveActionParam({ $index: true }, ctx);                       // → 2
resolveActionParam(["a", "b"], ctx);                             // → ["a", "b"]
resolveActionParam({ id: { $state: "/form/text" } }, ctx);       // → { id: "hi" }

// Schema rejects each:
DynamicValueSchema.safeParse({ $index: true }).success;          // false
DynamicValueSchema.safeParse(["a", "b"]).success;                // false
DynamicValueSchema.safeParse({ id: { $state: "/x" } }).success;  // false

// Same rejection cascades into ActionBindingSchema:
ActionBindingSchema.safeParse({
  action: "setState",
  params: { value: ["a", "b"] },
}).success; // false

Why this matters

resolveActionParam is documented to:

  • Resolve { $item: "field" } to an absolute state path.
  • Resolve { $index: true } to a numeric index.
  • Delegate everything else to resolvePropValue, which recurses into objects/arrays and handles the full PropExpression grammar.

But DynamicValueSchema is union(string, number, boolean, null, { $state: string }) — anything else fails validation even though the runtime resolves it correctly.

Concrete impact: external tooling that validates an emitted Spec against ActionBindingSchema gets false positives on legitimate emissions like params: { value: ["a", "b"] } (literal array) or params: { value: { id: { \$state: "/x" } } } (object with state refs).

Context

Surfaced while building a parity test suite for a DSL → json-render compiler against @json-render/[email protected]. Happy to PR if alignment on the approach is reached.