You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The GraalJS server runtime is microtask-only: no timers, no fetch, no Node built-ins. Today this is discovered at runtime: setTimeout in an action returns {"error":"setTimeout is not defined"} on the first call (probed; the message itself is good). It is the #1 wall newcomers from the JS ecosystem will hit, and it also silently rules out most I/O-using npm packages (SDKs, DB clients).
Proposal:
An ESLint rule (shipped in the scaffolded eslint.config.js) flagging setTimeout/setInterval/fetch/process/Node built-in imports in server files (*.server.*, *.action.*, extension files) — editor squiggles instead of runtime errors.
Optionally, a build-time scan in the vite plugin warning when the server bundle contains references to unavailable globals (best-effort, catches transitive cases the lint rule can't).
A prominent "runtime boundary" section in the getting-started docs listing what is and isn't available.
Technical plan (agent-executable)
Written for execution by a Claude (Opus/Sonnet) agent inside the Jahia Cortex harness. No running Jahia needed.
Harness setup
Open Cortex; cortex-sources → javascript-modules @ main; yarn install at repo root.
Design decision
Config-only, using ESLint core rules (no-restricted-globals, no-restricted-imports, no-restricted-properties) scoped to server-file globs. No new eslint-plugin package — it gets ~90% of the value at ~0% of the maintenance cost. A flow-aware plugin (chasing transitive imports) is explicitly out of scope; note it as a possible follow-up.
Author the restriction block (identical in all three configs — consider exporting it from a tiny shared file at repo root for the repo's own packages; the scaffold template gets an inlined copy since scaffolds are standalone):
{files: ["**/*.server.{js,jsx,ts,tsx}","**/*.action.{js,ts}"],rules: {"no-restricted-globals": ["error",
...["setTimeout","setInterval","clearTimeout","clearInterval","fetch","XMLHttpRequest","WebSocket","AbortController","process","Buffer","require"].map((name)=>({
name,message: `${name} is not available in the Jahia server runtime (microtask-only: no timers, no async I/O, no Node API). See docs/3-reference/<runtime-boundary-page>.`,})),],"no-restricted-imports": ["error",{patterns: [{group: ["node:*"],message: "Node built-ins are not available in the Jahia server runtime."}],}],},}
Verify the allowlist before finalizing: grep the engine's init scripts (javascript-modules-engine/src/main/resources/META-INF/js/) and GraalVM context setup for globals that ARE provided (e.g. console, URL, TextEncoder, queueMicrotask — include in the doc, and do NOT restrict anything actually available). Add restrictions only for names verified absent — validate by evaluating each name in a deployed module (a throwaway view logging typeof setTimeout etc. against a jahia-docker instance, or reuse the findings table from EPIC EPIC: JavaScript Modules developer experience (DX) improvements #698's evaluation: setTimeout/fetch confirmed undefined).
Docs: new docs/3-reference/ page "Server runtime boundary": what's available, what isn't, the microtask-only async rule (mirror the wording already used in the library's registrar JSDoc), how the lint rule maps to it, and the runtime error you get if you bypass lint ({"error":"setTimeout is not defined"}).
Bare Node imports ("fs", "path" without the node: prefix): add the explicit list to no-restricted-importspaths (import from node:module builtins list at authoring time — hardcode the list, don't compute it in config).
Verification
New test javascript-create-module/tests/eslint-server-restrictions.test.js (node --test): scaffold into a tmpdir (reuse the non-interactive mode from Non-interactive mode for @jahia/create-module #701 if merged, else copy the template), write a probe.server.tsx containing setTimeout(() => {}, 1) and import fs from "node:fs", run eslint via child_process with the scaffold's config, assert exit ≠ 0 and both rule IDs appear in the output; then assert a clean file passes.
Repo CI: yarn lint stays green on samples/hydrogen + jahia-test-module after adding the block (fix any real violations it finds — each one is a latent bug worth its own attention; do not weaken the rule to pass).
Confirm .client.tsx files are NOT affected (write a client file using fetch — must lint clean).
Acceptance criteria
Scaffolded modules get editor squiggles + yarn lint errors for timers/fetch/Node APIs in *.server.* / *.action.* files, with messages linking the boundary doc.
Client files unaffected.
Samples + test module carry the same block and lint clean.
Runtime-boundary reference page exists and is linked from the rule messages and the actions guide.
Every restricted global verified absent in the actual runtime (no false restrictions).
Part of EPIC #698.
The GraalJS server runtime is microtask-only: no timers, no
fetch, no Node built-ins. Today this is discovered at runtime:setTimeoutin an action returns{"error":"setTimeout is not defined"}on the first call (probed; the message itself is good). It is the #1 wall newcomers from the JS ecosystem will hit, and it also silently rules out most I/O-using npm packages (SDKs, DB clients).Proposal:
eslint.config.js) flaggingsetTimeout/setInterval/fetch/process/Node built-in imports in server files (*.server.*,*.action.*, extension files) — editor squiggles instead of runtime errors.Technical plan (agent-executable)
Harness setup
Open Cortex;
cortex-sources→javascript-modules@main;yarn installat repo root.Design decision
Config-only, using ESLint core rules (
no-restricted-globals,no-restricted-imports,no-restricted-properties) scoped to server-file globs. No new eslint-plugin package — it gets ~90% of the value at ~0% of the maintenance cost. A flow-aware plugin (chasing transitive imports) is explicitly out of scope; note it as a possible follow-up.Code map
javascript-create-module/templates/module/eslint.config.jssamples/hydrogen/eslint.config.js,jahia-test-moduleeslint configdocs/3-reference/Implementation steps
javascript-modules-engine/src/main/resources/META-INF/js/) and GraalVM context setup for globals that ARE provided (e.g.console,URL,TextEncoder,queueMicrotask— include in the doc, and do NOT restrict anything actually available). Add restrictions only for names verified absent — validate by evaluating each name in a deployed module (a throwaway view loggingtypeof setTimeoutetc. against ajahia-dockerinstance, or reuse the findings table from EPIC EPIC: JavaScript Modules developer experience (DX) improvements #698's evaluation:setTimeout/fetchconfirmed undefined).docs/3-reference/page "Server runtime boundary": what's available, what isn't, the microtask-only async rule (mirror the wording already used in the library's registrar JSDoc), how the lint rule maps to it, and the runtime error you get if you bypass lint ({"error":"setTimeout is not defined"})."fs","path"without thenode:prefix): add the explicit list tono-restricted-importspaths(import fromnode:modulebuiltins list at authoring time — hardcode the list, don't compute it in config).Verification
javascript-create-module/tests/eslint-server-restrictions.test.js(node --test): scaffold into a tmpdir (reuse the non-interactive mode from Non-interactive mode for @jahia/create-module #701 if merged, else copy the template), write aprobe.server.tsxcontainingsetTimeout(() => {}, 1)andimport fs from "node:fs", runeslintviachild_processwith the scaffold's config, assert exit ≠ 0 and both rule IDs appear in the output; then assert a clean file passes.yarn lintstays green onsamples/hydrogen+jahia-test-moduleafter adding the block (fix any real violations it finds — each one is a latent bug worth its own attention; do not weaken the rule to pass)..client.tsxfiles are NOT affected (write a client file usingfetch— must lint clean).Acceptance criteria
yarn linterrors for timers/fetch/Node APIs in*.server.*/*.action.*files, with messages linking the boundary doc.