Follow-up from the architecture review of the feature/js-server-extensions branch (PR #687).
Context. Actions (.action.ts) accept async handlers: the engine settles returned promises through JSPromise.settle (GraalJS drains the microtask queue at the polyglot API boundary; unit-tested in JSPromiseTest). The three bridges written before that mechanism existed are still sync-only: registerNodeValidator, registerChoiceListInitializer, registerNodeLegacyAction (and registerRenderFilter), which is an inconsistent developer experience — an async callback silently misbehaves there instead of working or failing loudly.
Proposal. In each Java bridge (ChoiceListInitializerRegistrar, NodeValidatorRegistrar.collectViolations, NodeLegacyActionRegistrar.ActionBridge, RenderFilterRegistrar.RenderFilterBridge), pass the JS result through JSPromise.settle before converting it: microtask-resolvable promises (any async/await over synchronous work) then behave exactly like sync returns; never-settling promises produce an explicit error instead of a wrong result. Non-breaking — sync returns are untouched. Update the "no promises" wording in the TSDoc/guides accordingly.
Acceptance. Each of the four bridges has a unit test with an async callback fixture; guides no longer claim sync-only where async now works; existing Cypress specs stay green.
Follow-up from the architecture review of the
feature/js-server-extensionsbranch (PR #687).Context. Actions (
.action.ts) acceptasynchandlers: the engine settles returned promises throughJSPromise.settle(GraalJS drains the microtask queue at the polyglot API boundary; unit-tested inJSPromiseTest). The three bridges written before that mechanism existed are still sync-only:registerNodeValidator,registerChoiceListInitializer,registerNodeLegacyAction(andregisterRenderFilter), which is an inconsistent developer experience — anasynccallback silently misbehaves there instead of working or failing loudly.Proposal. In each Java bridge (
ChoiceListInitializerRegistrar,NodeValidatorRegistrar.collectViolations,NodeLegacyActionRegistrar.ActionBridge,RenderFilterRegistrar.RenderFilterBridge), pass the JS result throughJSPromise.settlebefore converting it: microtask-resolvable promises (anyasync/awaitover synchronous work) then behave exactly like sync returns; never-settling promises produce an explicit error instead of a wrong result. Non-breaking — sync returns are untouched. Update the "no promises" wording in the TSDoc/guides accordingly.Acceptance. Each of the four bridges has a unit test with an
asynccallback fixture; guides no longer claim sync-only where async now works; existing Cypress specs stay green.