Repository navigation
feat(web_core): settle Promise-valued function arguments before validation - #3578
josemontespg wants to merge 1 commit into
Conversation
7059a9f to
1597a1b
Compare
There was a problem hiding this comment.
Code Review
This pull request refactors the A2UI expression runtime to automatically and recursively await pending Promises in function arguments before validating and executing the functions. This centralizes the asynchronous argument resolution logic via a new settleDeep utility, allowing individual function implementations (such as jmespath, parseJson, split, and regex functions) to be simplified and to use stricter Zod schemas instead of z.unknown(). The changes are well-supported by extensive unit tests and updated documentation. I have no feedback to provide as there are no review comments to address.
1597a1b to
e86a97e
Compare
e86a97e to
fab6316
Compare
fab6316 to
f5a49d4
Compare
f5a49d4 to
b513612
Compare
b513612 to
a5e3834
Compare
a5e3834 to
08d3fbc
Compare
08d3fbc to
faf12c5
Compare
faf12c5 to
8930a21
Compare
8930a21 to
7ac69ed
Compare
b231dd3 to
f31be4d
Compare
19bc420 to
1233d9c
Compare
b40f1c4 to
e2ef243
Compare
bc19517 to
56acdc1
Compare
56acdc1 to
29ea6f6
Compare
29ea6f6 to
f505d6b
Compare
f505d6b to
0e4857d
Compare
…ation The runtime now awaits pending Promises nested anywhere in the resolved arguments of a function call before it validates them and runs the function. Calls whose arguments are all settled stay synchronous. At the top level of an expression a rejected call is reported as an EXPRESSION_ERROR and resolves to undefined; nested, the rejection fails the enclosing call. A reactive subscription to an asynchronous call holds undefined until the result settles and ignores completions that arrive after its arguments changed, and RpcHandler awaits pending arguments before validating a callRendererFunction.
0e4857d to
13166ac
Compare
ditman
left a comment
There was a problem hiding this comment.
LGTM! Moving this below the catalog PRs so it only touches typescript/core/web_core/ and keeping isThenable and settleDeep internal to web_core makes this super clean. This will also let sample-local functions (like in a2ui-over-mcp-filesystem) compose with callMcpTool without custom Promise-settling wrappers.
Summary
The runtime now awaits
PromiseLikevalues nested in a function call's resolved arguments before validating and running the function, so a synchronous catalog function can take the result of an asynchronous one (jmespath(callMcpTool(...))) without widening its schema or awaiting by hand. Synchronous expressions stay synchronous:settleDeepreturns its input unchanged when nothing is pending, andrequiresUserActivationis still checked in the user gesture's turn. A rejected inner call aborts the outer call and reports oneEXPRESSION_ERROR; in reactive bindings the signal keeps its value until the call settles.Changes
@a2ui/web_core:src/common/settle_deep.ts(isThenable,settleDeep, internal toweb_core);createCatalogInvoker,DataContext(resolveDynamicValue,resolveFunctionCallValue,resolveSignal) andRpcHandler.handleCallRendererFunctionsettle arguments; tests and CHANGELOG.Known limitation
In a reactive binding (not an action) whose outer function wraps an asynchronous call, the outer function runs once with
undefinedwhile the inner call is pending and reports a validationEXPRESSION_ERRORbefore re-running with the settled value. Actions, which the MCP examples and the filesystem sample use, do not go through that path.Verification
yarn workspace @a2ui/web_core run build | test | lint(1094 tests) passes locally.