Skip to content

feat: JS server extension points, TypeScript content patches, and the JSServerExtensionInvoker SDK - #687

Open
romain-pm wants to merge 21 commits into
mainfrom
feature/js-server-extensions
Open

feat: JS server extension points, TypeScript content patches, and the JSServerExtensionInvoker SDK#687
romain-pm wants to merge 21 commits into
mainfrom
feature/js-server-extensions

Conversation

@romain-pm

@romain-pm romain-pm commented Jul 22, 2026

Copy link
Copy Markdown

Scope

Four features, delivered on this branch as stacked work (recommended merge: squash, see "Review notes"):

  1. Server extension points — JS modules can declare what previously required Java: choicelist initializers (registerChoiceListInitializer), server-side node validators (registerNodeValidator), legacy node actions (registerNodeLegacyAction, the org.jahia.bin.Action bridge) and typed render filters (registerRenderFilter), over a shared AbstractServiceRegistrar mechanism.
  2. Content patchesregisterContentPatch: versioned, run-once content transformations backed by core's patch status store.
  3. JSServerExtensionInvoker SDK — a polyglot-free exported package so third-party bundles can consume JS-declared extensions.
  4. Actions (Actions #588) — server functions callable from client islands: .action.ts files compiled twice (server implementation + client fetch stubs), devalue wire format, optional Standard Schema validation (action(schema, fn)), single jsAction dispatch endpoint.

How to review (suggested order)

  1. Start with the ADRs (docs/adr/00010008): every structural decision, alternative and trade-off is recorded there — the code is meant to read as their implementation.
  2. The mechanism: registrars/AbstractServiceRegistrar.java + RenderFilterRegistrar.java (the refactored template). Key invariant: bridges re-resolve JS functions from the pooled-context registry on every call.
  3. One vertical end-to-end (suggestion: choicelists — smallest): Java registrar → TS wrapper → test-module fixture → Cypress spec → guide. The other verticals follow the same shape.
  4. Validators (registrars/validation/): the one deliberate deviation — single Bean Validation bean on sentinel nt:base (ADR-0005 explains why per-nodeType registration is incorrect).
  5. Actions (vite-plugin/src/actions.ts, framework/actions/, actions/GenericActionEndpoint.java + JSPromise.java): the dual compilation and the microtask-settling contract (ADR-0008).

Verification

  • 65 engine-java unit tests (incl. full Bean Validation chain against the platform's Hibernate Validator 6.2.0.Final, and GraalJS promise-settling tests)
  • vite-plugin snapshot fixtures incl. an .action.ts fixture
  • 6 new Cypress specs (extension points, content patches, actions wire + through-island)
  • Live-verified on Jahia 8.2: choicelists (Content Editor forms API), legacy actions (GET/POST/CSRF), validators (field/node-level, phases, verbatim messages)
  • Engine bundle manifest imports javax.validation/org.json from the platform (not embedded)

Review notes / known caveats

  • Commit 3b6c507 is mislabeled: it carries an early snapshot of the content-patches work swept in from a shared working tree, under a one-line-fix title. Squash-merging makes this moot; reviewing file-by-file (not commit-by-commit) is recommended for the content-patches part.
  • Behavior change (changelog-noted): render-filter registry entries' priority was previously ignored (forced to 0) and is now honored.
  • Known v1 limitations are listed at the end of ADR-0008 (actions) and in the guides (sync-only legacy handlers, microtask-only asynchronicity, lexical export discovery in .action.ts).

How to review

Content patches have been split out into #697, so this PR is now the server extension points + actions + SDK (~2,300 LOC smaller).

Review by the Files-changed tab (squash-merge is intended — the intermediate commits are throwaway). Suggested attention budget:

  • Scrutinize (the load-bearing surface, ~1.5k LOC): the Java bridges in javascript-modules-engine-java/.../registrars/** and .../actions/**; the TS wrappers in javascript-modules-library/src/framework/**; vite-plugin/src/actions.ts. Read the ADRs (docs/adr/) first — the code is their implementation.
  • Skim: jahia-test-module/** fixtures, samples/hydrogen/**, the Cypress specs in tests/**, the guides in docs/2-guides/**.
  • Skip: generated types under javascript-modules-engine-java/target/**, the vite-plugin/fixtures/expected/** snapshots, and the pure-formatting commit 5008239 (reviewable with GitHub's Hide whitespace).

Once you've reviewed the shared mechanism + one vertical end-to-end, the other verticals are variations on the same bridge pattern.

Tracking

Part of EPIC #554.

Closes #588, closes #691, closes #692, closes #693, closes #694, closes #696, closes #688, closes #689.

Content patches were split into their own PR #697 (closes #695).

Follow-up (stays open): #690 — actions endpoint v2 (dedicated servlet transport).

romain-pm added 10 commits July 21, 2026 13:57
ADR-0001: per-type registrars over a shared base class (whiteboard-aligned)
ADR-0002: first-class registry types (action, choicelist-initializer, node-validator)
ADR-0003: typed TS registration wrappers with raw Java escape hatch
ADR-0004: CSRF whitelisting stays the module author's responsibility
ADR-0005: node validators bridged via a single Bean Validation bean on nt:base
…sion bridges

- New AbstractServiceRegistrar<S>: per-bundle registry-entry discovery, bridge
  creation, OSGi service publication and tracked unregistration with per-entry
  error isolation (ADR-0001).
- RenderFilterRegistrar refactored onto the base class. Two fixes folded in:
  the registry entry's priority is no longer overwritten to 0 after
  construction (parsed as float), and execute/prepare now null-guard both the
  registry entry (module stopped mid-flight) and null/undefined JS results.
- mockito-core + graalvm js added at test scope.
- ChoiceListInitializerRegistrar bridges registry entries of type
  'choicelist-initializer' to ModuleChoiceListInitializer OSGi services
  (consumed by core, usable as choicelist[key] in CND definitions).
- New registerChoiceListInitializer() library API: typed callback receiving
  {param, locale (BCP-47), values, node?, java escape hatch}; returns
  {label, value, properties?} choices (properties variant supported).
- java-ts-bind: generate ExtendedPropertyDefinition (narrow whitelist,
  flattened parent methods) and Locale.toLanguageTag.
- Test-module fixture + CND type, Cypress spec (forms GraphQL API), docs guide.
- ActionRegistrar bridges registry entries of type 'action' to
  org.jahia.bin.Action OSGi services, invoked via <nodeUrl>.<name>.do.
- New registerAction() library API: typed declaration (requiredMethods,
  requireAuthenticatedUser [Jahia default: true], requiredPermission,
  requiredWorkspace) and handler receiving {parameters, renderContext,
  resource, session, request, urlResolver}; result {statusCode, json,
  redirect, absoluteRedirect}. The adapter pre-stringifies json to avoid
  polyglot deep-conversion issues with nested structures.
- CSRF stays the module's responsibility (ADR-0004): documented recipe +
  test-module .cfg exercising it end-to-end.
- java-ts-bind: generate URLResolver (narrow whitelist).
- Test-module fixtures (GET/POST/auth/redirect), Cypress spec incl. redeploy
  resilience, docs guide.
- New registrars/validation package: a single JSNodeValidator Bean Validation
  bean registered under the sentinel nt:base node type (exactly-once execution
  per changed node per save; no clobbering of Java validators on real node
  types), carrying four repeatable class-level @JSValidation constraints that
  mirror Jahia's default/advanced/skip-on-import validation phases (ADR-0005).
- JSValidationConstraintValidator dispatches to JS validators via the
  ref-counted NodeValidatorRegistrar (volatile snapshot gate before any
  GraalVM entry, ownership-checked platform (un)registration, fail-closed on
  throwing validators).
- Messages follow JahiaMessageInterpolator semantics: {resource.bundle.key}
  messages are localized through module resource bundles, anything else is
  verbatim; sub-2-character messages get a generic fallback (interpolator
  crash guard).
- New registerNodeValidator() library API with locale (BCP-47) + raw Java
  escape hatch.
- Bean Validation deps (api provided, hibernate-validator 6.2.0.Final at test
  scope); full-chain unit tests against real HV; registrar state-machine tests.
- Test-module fixtures + CND type, Cypress spec (field/node-level, phases,
  verbatim messages, redeploy resilience), reference documentation.
…nsion points

- registerRenderFilter(): typed wrapper over the existing 'render-filter'
  registry shape (backward compatible), with applyOn* options accepting
  arrays and fractional priorities.
- Hydrogen gains a self-contained ContactForm component demonstrating all
  three new extension points: a CSRF-whitelisted POST action receiving the
  form, a choicelist initializer for the form style, and a node validator on
  the notification email.
getName() is not part of the generated JahiaUser typing; the CI test-module
build runs tsc --noEmit and failed on it.
- Choicelist initializers receive the CONTENT locale (language being edited),
  not the editor UI locale — docs, TSDoc and Cypress spec corrected (verified
  against a live instance via the Content Editor forms API).
- Actions guide: the render servlet writes the JSON body only when the request
  sends Accept: application/json.
… ADR 0006

Completes the TS migration scripts feature (runtime + typed API landed in
earlier commits on this branch):

- persistent failure barrier: a recorded .failed migration holds back
  later-named pending migrations across restarts/redeploys
- changeNodeType: rebind via getRealNode() (the Jahia wrapper throws
  UnsupportedRepositoryOperationException) and carry mapped properties
  around the retype (Jackrabbit validates existing properties against
  the new type)
- JcrHelper.doExecuteAsSystem: include the exception class in the
  message (JCR exceptions often carry a null message across polyglot)
- jahia-test-module: 9 migration fixtures covering the five operations
  plus skip/failure/halt semantics; Cypress spec asserting statuses in
  j:bundlesScripts and the transformed content
- guide (docs/2-guides/6-migrations) and ADR 0006

All semantics verified live on jahia-discovery 8.2.3.
Copilot AI review requested due to automatic review settings July 22, 2026 08:33
@romain-pm
romain-pm requested a review from GauBen as a code owner July 22, 2026 08:33
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦜 Chachalog

javascript-modules minor
  • New: actions — server functions callable from client components as plain async calls. Export a function from a .action.ts file, import it in an island, and call it: arguments and results are serialized automatically (devalue), with optional input validation via any Standard Schema compatible library (action(schema, fn)).

    The previous registerAction API (Jahia's node-bound .do endpoints) is renamed registerNodeLegacyAction.

  • JavaScript modules can now declare choicelist initializers, server-side node validators and actions — extension points that previously required a Java module. Use the new registerChoiceListInitializer, registerNodeValidator, registerNodeLegacyAction and registerRenderFilter functions from @jahia/javascript-modules-library.

    Note for existing modules using server.registry.add("render-filter", …): a declared priority is now honored (it was previously ignored and forced to 0), which may reorder such filters in the render chain.

  • Java modules can now consume JavaScript-declared server extensions through the new JSServerExtensionInvoker OSGi service. A module can define its own extension type, let JavaScript modules contribute entries via server.registry.add, and invoke their callbacks from Java without depending on GraalVM APIs. This enables, for example, form-field validators written in JavaScript to run during server-side form submission processing.

Create a new entry online or run npx chachalog@0.5.2 prompt to create a new entry locally.

@github-actions

Copy link
Copy Markdown

📝 Documentation Guidelines

Thank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:

This comment is posted automatically when changes are detected in the docs/ folder.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the JavaScript Modules engine so JS modules can declare additional server-side extension points (choicelist initializers, node validators, actions, render filters) and introduces first-class JS migrations with typed APIs, engine-side registrars, documentation (ADRs/guides), and automated test coverage.

Changes:

  • Added typed registration APIs in @jahia/javascript-modules-library for actions, choicelist initializers, node validators, render filters, and migrations.
  • Implemented/expanded engine-side registrars and bridges (including a shared AbstractServiceRegistrar) plus migration execution and status recording.
  • Added Cypress e2e specs, Java unit tests, sample module updates, docs/ADRs, and updated Vite plugin defaults for server entry globs.

Reviewed changes

Copilot reviewed 61 out of 61 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
vite-plugin/src/index.ts Expands default server entry glob to include .js/.ts variants.
vite-plugin/README.md Documents the updated default inputGlob.
tests/cypress/e2e/ui/nodeValidatorTest.cy.ts Cypress coverage for JS node validator behavior and redeploy resilience.
tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts Cypress coverage for JS choicelist initializer behavior (params, locale, properties).
tests/cypress/e2e/ui/actionTest.cy.ts Cypress coverage for JS actions (methods/auth/redirect/CSRF whitelist, redeploy resilience).
tests/cypress/e2e/engine/migrationTest.cy.ts Cypress coverage for JS migration ordering, statuses, and effects.
samples/hydrogen/src/components/ContactForm/extensions.server.tsx Example JS module declaring action/choicelist initializer/validator.
samples/hydrogen/src/components/ContactForm/definition.cnd Sample CND using a JS-declared choicelist initializer key.
samples/hydrogen/src/components/ContactForm/default.server.tsx Sample component using the JS-declared action endpoint.
samples/hydrogen/settings/configurations/org.jahia.modules.jahiacsrfguard-hydrogen.cfg Sample CSRF whitelist config for a POST action.
MIGRATIONS-DEMO.md Developer-facing migration contract/demo document.
javascript-modules-library/src/index.ts Exposes new registration APIs and migration types from the library entrypoint.
javascript-modules-library/src/framework/registerRenderFilter.ts Typed render-filter registration wrapper writing to the server registry.
javascript-modules-library/src/framework/registerNodeValidator.ts Typed node-validator registration wrapper and context adaptation.
javascript-modules-library/src/framework/registerChoiceListInitializer.ts Typed choicelist initializer wrapper and Java→JS value adaptation.
javascript-modules-library/src/framework/registerAction.ts Typed action wrapper with parameter conversion + JSON result shaping.
javascript-modules-library/src/framework/migrations/types.ts Public types for migration declaration/context/operations.
javascript-modules-library/src/framework/migrations/registerMigration.ts Migration registration wrapper with sync-only enforcement and dedupe guard.
javascript-modules-library/src/framework/migrations/operations.ts Implementation of guard-railed migration helpers (remove/set/convert/retype).
javascript-modules-library/src/framework/migrations/jcr.ts Migration JCR batching engine + system-session helper.
javascript-modules-engine-java/src/test/java/.../validation/NodeValidatorRegistrarTest.java Unit tests for validator registrar lifecycle and violation shape handling.
javascript-modules-engine-java/src/test/java/.../validation/JSNodeValidatorBeanValidationTest.java Full Bean Validation chain tests for the JS validator bridge.
javascript-modules-engine-java/src/test/java/.../migrations/MigrationRegistrarTest.java Unit tests for migration ordering, terminal status semantics, halting behavior.
javascript-modules-engine-java/src/test/java/.../ChoiceListInitializerRegistrarTest.java Unit tests for JS choicelist value conversion.
javascript-modules-engine-java/src/test/java/.../ActionRegistrarTest.java Unit tests for action mapping + result conversion.
javascript-modules-engine-java/src/test/java/.../AbstractServiceRegistrarTest.java Unit tests for the shared registrar base class behavior.
javascript-modules-engine-java/src/main/java/.../validation/NodeValidatorRegistrar.java Engine registrar bridging JS validators to Jahia’s validation pipeline.
javascript-modules-engine-java/src/main/java/.../validation/JSViolation.java Value object for JS validator violations.
javascript-modules-engine-java/src/main/java/.../validation/JSValidationConstraintValidator.java Bean Validation constraint validator dispatching to JS validators.
javascript-modules-engine-java/src/main/java/.../validation/JSValidation.java Repeatable constraint annotation representing validation phases.
javascript-modules-engine-java/src/main/java/.../validation/JSNodeValidator.java Sentinel Bean Validation bean bridging all JS node validators.
javascript-modules-engine-java/src/main/java/.../RenderFilterRegistrar.java Refactors render-filter registrar onto shared base + honors priority.
javascript-modules-engine-java/src/main/java/.../migrations/MigrationSupport.java Java support object passed into migrations (logger/dry-run/definition ops).
javascript-modules-engine-java/src/main/java/.../migrations/MigrationRegistrar.java Runs pending JS migrations and records terminal statuses in the shared store.
javascript-modules-engine-java/src/main/java/.../ChoiceListInitializerRegistrar.java Publishes JS choicelist initializers as OSGi services with collision warnings.
javascript-modules-engine-java/src/main/java/.../ActionRegistrar.java Publishes JS actions as OSGi services with collision warnings.
javascript-modules-engine-java/src/main/java/.../AbstractServiceRegistrar.java New shared registrar base for “registry entry → OSGi service” bridging.
javascript-modules-engine-java/src/main/java/.../js/server/JcrHelper.java Adds doExecuteAsSystem helper for migration/system-session execution.
javascript-modules-engine-java/pom.xml Adds provided/test dependencies for JSON + Bean Validation + GraalJS test runtime.
javascript-modules-engine-java/.java-ts-bind/package.json Expands bindings/whitelist for new Java types/methods used by typed APIs.
javascript-create-module/templates/module/vite.config.mjs Updates template comment for the new default server glob.
jahia-test-module/src/react/server/extensions/validators.ts JS fixtures for node validator behaviors covered by Cypress specs.
jahia-test-module/src/react/server/extensions/migrations.ts JS fixtures for migration behaviors covered by Cypress specs.
jahia-test-module/src/react/server/extensions/choicelists.ts JS fixtures for choicelist initializer behaviors covered by Cypress specs.
jahia-test-module/src/react/server/extensions/actions.ts JS fixtures for action behaviors covered by Cypress specs.
jahia-test-module/settings/definitions.cnd Adds fixture node types/properties used by new server-extension tests.
jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg CSRF whitelist for POST action test fixture.
docs/adr/README.md Adds ADR index and references for the new architecture decisions.
docs/adr/0001-javascript-server-extension-points.md ADR: per-type registrars over shared base class.
docs/adr/0002-first-class-registry-types.md ADR: first-class registry types per extension point.
docs/adr/0003-typed-registration-wrappers.md ADR: typed TS-first wrappers with Java escape hatch.
docs/adr/0004-csrf-whitelisting-for-js-actions.md ADR: CSRF whitelist remains module-author responsibility.
docs/adr/0005-js-node-validators-single-bean-validation-bridge.md ADR: single sentinel Bean Validation bridge for validators.
docs/adr/0006-javascript-migrations.md ADR: run migrations via registrar + shared patch status store.
docs/3-reference/3-node-validators/README.md Public documentation for JS node validators.
docs/3-reference/1-cnd-format/README.md Mentions JS-declared choicelist initializers as a CND option.
docs/2-guides/6-migrations/README.md Public documentation for writing JS migrations.
docs/2-guides/5-choicelist-initializers/README.md Public documentation for JS choicelist initializers.
docs/2-guides/4-actions/README.md Public documentation for JS actions + CSRF whitelist recipe.
.chachalog/js-server-extension-points.md Release note describing new server extension points and render-filter priority change.

Comment thread vite-plugin/src/index.ts
* supported.](https://www.npmjs.com/package/@rollup/plugin-multi-entry#supported-input-types)
*
* @default "**‍/*.server.{jsx.tsx}"
* @default "**‍/*.server.{js,jsx,ts,tsx}"
Comment on lines +94 to +109
/** Converts the Java List of org.jahia...ChoiceListValue accumulated so far into plain JS objects. */
const toJsChoiceListValues = (values: List<unknown>): ChoiceListValue[] => {
const result: ChoiceListValue[] = [];
if (values) {
for (let i = 0; i < values.size(); i++) {
// Jahia's ChoiceListValue: getDisplayName(), getValue() (JCR Value), getProperties()
const value = values.get(i) as {
getDisplayName(): string;
getValue(): { getString(): string };
getProperties(): JavaMap<string, unknown> | null;
};
result.push({ label: value.getDisplayName(), value: value.getValue().getString() });
}
}
return result;
};
Comment on lines +6 to +8
JavaScript modules can now declare choicelist initializers, server-side node validators and actions — extension points that previously required a Java module. Use the new `registerChoiceListInitializer`, `registerNodeValidator`, `registerAction` and `registerRenderFilter` functions from `@jahia/javascript-modules-library`.

Note for existing modules using `server.registry.add("render-filter", …)`: a declared `priority` is now honored (it was previously ignored and forced to 0), which may reorder such filters in the render chain.
Comment on lines +80 to +84
if (!registry.hasNodeType(name)) {
getLogger(bundle.getSymbolicName()).info(
"Node type {} is not registered on this instance, nothing to unregister", name);
return;
}
Comment on lines +96 to +99
if (dryRun) {
getLogger(bundle.getSymbolicName()).info("[dry-run] would unregister node type {}", name);
return;
}
Comment on lines +69 to +88
// Snapshot the identifiers first: mutating nodes while paging through a live query result
// (retyping, removing…) would make the pagination skip or repeat nodes.
const identifiers = withSystemSession({ workspace }, (session) => {
const result: string[] = [];
for (let offset = 0; ; offset += SNAPSHOT_PAGE_SIZE) {
const sql2Query = session
.getWorkspace()
.getQueryManager()
.createQuery(query, "JCR-SQL2");
sql2Query.setLimit(SNAPSHOT_PAGE_SIZE);
sql2Query.setOffset(offset);
const iterator = sql2Query.execute().getNodes();
let pageCount = 0;
while (iterator.hasNext()) {
result.push((iterator.nextNode() as JCRNodeWrapper).getIdentifier());
pageCount++;
}
if (pageCount < SNAPSHOT_PAGE_SIZE) break;
}
return result;
romain-pm and others added 2 commits July 22, 2026 10:54
Renames the run-once content-scripting feature from "migration" to
"content patch" — "migration" collided with Jahia core's platform
Migrator and over-narrowed a mechanism whose real job is arbitrary
run-once content/definition fixes (repairs, backfills, retypes), not
only schema migrations.

- registerMigration -> registerContentPatch; context.migrate -> .patch
- MigrationRegistrar/Support -> ContentPatchRegistrar/Support
  (registrars.contentpatches package)
- registry type "migration" -> "content-patch"
- status path prefix /javascript/migrations/ -> /javascript/content-patches/
- config PID + logger namespace ...engine.migrations -> ...engine.contentpatches
- library dir framework/migrations -> framework/contentPatches; all
  exported types Migration* -> ContentPatch*
- test-module fixtures, Cypress spec, guide (docs/2-guides/6-content-patches),
  ADR 0006, and design docs (CONTENT-PATCHES-PLAN/DEMO) renamed; Groovy /
  Sanity / Contentful prior-art references keep their own "migration" term

58 unit tests green; all content-patch semantics (five operations +
skip/failure/persistent-barrier + new status-path prefix) re-verified live
on jahia-discovery 8.2.3.
…nsions

Adds a public, polyglot-free SDK so other OSGi bundles can consume JS-declared
server extensions (server.registry.add(type,...)) and invoke their callbacks:
- JSServerExtensionInvoker (interface) + Impl (@component) in a new exported
  package org.jahia.modules.javascript.modules.engine.sdk
- forEach(type, handler) runs within one pooled GraalVM context; Invoker.call
  executes a JS callable and converts the result to plain Java (no GraalVM types
  leak to consumers, so callers need no polyglot dependency)
- engine bundle now Export-Package's only the sdk package

Enables Formidable to run JS-authored form-field validators server-side (issue
Jahia/formidable#158) without embedding GraalVM or engine internals.
@romain-pm romain-pm changed the title feat: JS modules can declare choicelist initializers, node validators, actions and render filters feat: JS server extension points, TypeScript content patches, and the JSServerExtensionInvoker SDK Jul 22, 2026
Frees the plain 'action' name for the upcoming client-callable actions
(#588) and makes the org.jahia.bin.Action lineage explicit. Registry type
becomes 'node-legacy-action'; Java registrar, types, fixtures, hydrogen
sample and the guide (now 'Legacy Node Actions') renamed accordingly.
Pre-release rename, no compatibility shim.
- .action.ts files are compiled twice by @jahia/vite-plugin: the server
  bundle registers every export as a callable action; the client bundle
  replaces the module with generated fetch stubs (devalue-serialized args
  and results), so islands call server code as plain async functions.
- action(schema, fn) safe wrapper: input validation via any Standard
  Schema compatible library (interface vendored, no dependency added);
  validation issues travel back to the client error.
- Engine: single jsAction dispatch endpoint riding the render servlet
  (visitor session, guests allowed), mandatory X-JS-Action header as CSRF
  defense, engine-owned csrfguard whitelist entry; JSPromise settles
  async handlers through GraalJS microtask draining (unit-tested against
  real GraalJS, incl. never-settling detection).
- Docs: new Actions guide + when-to-use-what vs legacy node actions;
  ADR-0007 (naming) and ADR-0008 (design).
- Fixtures: vite-plugin snapshot fixture, test-module .action.ts + island,
  Cypress spec covering wire protocol, devalue round-trip, validation,
  header requirement and the end-to-end island path.
40 of the branch's files did not conform to the repo prettier config;
formatted wholesale (includes regenerated vite-plugin fixture snapshots).
Applied findings from a fresh-eyes review of the branch's new code:
- RenderFilterRegistrar: class javadoc added; execute/prepare guards made
  symmetric — a missing optional callback is a silent no-op, only a vanished
  registry entry warns.
- JSPromise.Settled renamed Outcome with isSettled() ('a Settled that is not
  done' contradicted itself); GenericActionEndpoint uses SC_OK constants and
  an error(String) overload.
- NodeValidatorProps renamed NodeValidatorDeclaration (aligns with the
  sibling *Declaration interfaces); stale 'Registered action' log message
  disambiguated after the legacy rename.
- Bridge constructors take registryEntry (was: value, colliding mentally
  with polyglot Value); dead getRegistryType() removed.
- vite-plugin actions: export-discovery regex tightened to the documented
  forms (const/function/async function), extension set aligned with the
  default glob, module-name fallback now warns loudly, stub header value
  commented; choicelist accumulated-values mapping documents why properties
  are not surfaced; messageOf() helper extracted in the action adapter.
- registerActionsModule renamed __registerActionsModule and marked
  @internal: it is a vite-plugin-generated-code contract, not a developer
  API. It stays in the main entry point because the engine resolves the
  library as a single shared module at runtime (a subpath entry would not
  resolve there) — recorded in ADR-0008.
- server.registry documentation now steers extension-point registration
  to the typed register* functions; the raw entry shapes are internal
  contracts.
Implements #688 and #689.

- JSPromise moved from ..engine.actions to ..engine.jsengine (general
  GraalJS facility, not an actions concern) and gains settleOrThrow():
  fulfilled value returned, rejection converted to GraalVMException (same
  semantics as a synchronous throw), never-settling promise fails with an
  explicit message.
- All four bridges (choicelists, node validators, legacy node actions,
  render filters) settle JS results through it, so async callbacks now work
  everywhere (microtask-only, like actions). Validators keep their
  fail-closed policy: a rejection lands in the existing catch and blocks
  the save.
- TS signatures accept Promise returns; the legacy-action adapter chains
  the conversion on the handler promise. Docs and ADR-0003 updated; test
  fixtures made async on purpose so the existing Cypress specs exercise the
  async path end-to-end.
- registrars/package-info.java documents the package layout rule.
Content patches are an independent feature (ContentPatchRegistrar implements
the engine Registrar SPI, no dependency on the extension-points mechanism).
Moving them to #697 shrinks this PR by ~2300 LOC and separates two review
audiences. This also neutralizes the mislabeled commit 3b6c507: the
content-patch files it swept in are no longer part of this branch's net diff.

Removed here: registerContentPatch + jcr/operations/types, ContentPatchRegistrar
(+support, tests), the content-patch guide/ADR-0006/plan docs, the Cypress spec,
and the patchTest* test-module fixtures.

Left in place intentionally (not content-patch-exclusive, referenced by other
verticals or generally useful): the org.json/test dependencies in the
engine-java pom, the JCR mutation-method entries in .java-ts-bind, and
JcrHelper.doExecuteAsSystem.
The generic-actions Cypress spec (genericActionTest.cy.ts) uses devalue, added
to tests/package.json, but tests/yarn.lock was not regenerated. CI's immutable
install in the standalone tests/ project rejected it ('the lockfile would have
been modified'), failing on-code-change / static-analysis. This was the only
failing check (build + sonar pass).
@romain-pm

Copy link
Copy Markdown
Author

Heads-up: this branch's integration suite is red for a packaging reason, and the fix is one line.

javascript-modules-engine/pom.xml's new Export-Package (for the SDK package) stops bnd from pulling dependency classes into the bundle — 9154 classes down to 88. The libraries still resolve from Embed-Dependency, but com.oracle.truffle.tools.utils.json does not: it lives in org.graalvm.tools:profiler, a transitive of chromeinspector that was never listed, because bnd used to include it implicitly.

The suite's first spec (engine/graalvmEngineTest.cy.ts) enables polyglot.inspect, GraalVMEngine.activate() then throws NoClassDefFoundError, and the engine never recovers — so every subsequent spec runs without JS views or actions (404 on test pages, 501 on .do endpoints, Passing: 0 everywhere). Details, log excerpt and package diff: #712 (comment)

Fix (adding profiler to Embed-Dependency) is commit 762bba0 on #712, which targets this branch — verified on Jahia 8.2: inspector answers {"Protocol-Version":"1.2","Browser":"GraalVM"}, and with it enabled the test module still registers, pages render and actions answer. Say the word if you'd rather have it as its own PR here.

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