Skip to content

fix(core): align DynamicValueSchema with resolveActionParam's runtime contract - #330

Open
hata33 wants to merge 1 commit into
vercel-labs:mainfrom
hata33:fix/dynamic-value-schema-runtime-contract
Open

fix(core): align DynamicValueSchema with resolveActionParam's runtime contract#330
hata33 wants to merge 1 commit into
vercel-labs:mainfrom
hata33:fix/dynamic-value-schema-runtime-contract

Conversation

@hata33

@hata33 hata33 commented Aug 29, 2026

Copy link
Copy Markdown

Summary

DynamicValueSchema rejects shapes that resolveActionParam resolves correctly at runtime, producing false positives for tools that schema-validate emitted specs against ActionBindingSchema:

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

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

All three shapes are documented API (the schema rules shipped in packages/react/src/schema.ts tell the model to use { "$item": "field" }`` and { "$index": true }`` inside repeated children, and the runtime recursively resolves arrays and plain-object values via resolvePropValue).

Changes

Widen the union in packages/core/src/types.ts:

  • { $item: string } — resolves to an absolute state path in action params
  • { $index: true } — resolves to the current repeat index
  • arrays of dynamic values (recursive)
  • records whose values are dynamic values (recursive), mirroring the runtime's plain-object recursion

The expression object arms stay strict, so a malformed { $state: 123 } still fails its dedicated arm — it only passes via the record arm, exactly matching the runtime, which treats it as a literal passthrough object.

Tests

Added a parity block to packages/core/src/types.test.ts that runs each widened shape through both DynamicValueSchema.safeParse and resolveActionParam, pinning schema/runtime agreement:

pnpm vitest run packages/core/src
Test Files  12 passed (12)
     Tests  511 passed (511)

Fixes #296

… contract

resolveActionParam (via resolvePropValue) accepts $index expressions,
$item expressions, arrays, and plain objects with recursively dynamic
values — all documented shapes for action params inside repeats. But
DynamicValueSchema only allowed literals and { $state }, so
schema-validating an emitted spec against ActionBindingSchema produced
false rejections for exactly the shapes the runtime resolves fine.

Widen the union to cover the documented contract: { $item }, { $index },
arrays of dynamic values, and records whose values are dynamic values
(mirroring the runtime's recursive object/array resolution). Expression
object arms stay strict so typos in $state/$item/$index keep failing
fast when they are the only applicable arm.

Fixes vercel-labs#296
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

@hata33 is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DynamicValueSchema is narrower than resolveActionParam's actual contract

1 participant