[APPS-2792] Add: in-process local execution for backend functions - #479
Draft
tyffical wants to merge 2 commits into
Draft
[APPS-2792] Add: in-process local execution for backend functions#479tyffical wants to merge 2 commits into
tyffical wants to merge 2 commits into
Conversation
This was referenced Aug 7, 2026
Executes a backend function by importing its real *.backend.ts file directly (via an injected loadModule), inside the Vite dev server's own process -- no bundling, no forked child process. The dev server is already the isolation boundary from production, so a crash or hang here only affects the developer's own dev server; process-level isolation is deliberately not added preemptively. $.Actions calls resolve through a Proxy (ported from the render.ts $.Actions logic) that invokes an injected executeAction function directly -- no IPC needed, since there's no separate process to cross. The same executeAction backs an action-catalog typed-wrapper registration and an apps-backend runtime-context registration, both loaded through the same loadModule (so they resolve against the customer's own project, not build-plugins' dependency tree) mirroring what the removed generated wrapper module used to do textually. The remote call itself is still a stub pending the single-action execution endpoint. The $ context passed to the customer's module -- exposed via globalThis.$, since the customer's own function takes its own real arguments rather than a $ parameter -- carries only backendFunctionArgs, Actions, and Source, verified by test, so that once a real auth token is wired in for real action execution, it can live in a module-private closure the customer's imported code has no way to reach.
tyffical
force-pushed
the
tiffany.trinh/apps-2792-in-process-execution
branch
from
August 7, 2026 19:15
98ded08 to
7b74053
Compare
executeScriptLocally writes globalThis.$ synchronously on every call, which two concurrent calls can race on -- the existing concurrency test never read globalThis.$ from either customer function, so it couldn't catch this. Add a test that does, confirming the race is real in this un-serialized base; skipped here since the fix (serializing executions via a promise-chain queue) lands in the Hardening milestone stacked on this PR. Co-Authored-By: Claude <noreply@anthropic.com>
tyffical
added a commit
that referenced
this pull request
Aug 10, 2026
Runs the readOwnArgsAfterDelay concurrency check through the real, serialized executeScriptLocally entrypoint (its test.skip counterpart against PR #479's un-serialized base fails with cross-contaminated args). Passing here confirms the enqueue/queueTail promise-chain mutex actually closes the globalThis.$ race, not just reorders interleaved work.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
child_process.fork()-based isolation entirely: the Vite dev server is already the isolation boundary from production, so a crash or hang in a customer's own local dev server is a contained, recoverable failure, not something that needs a separate forked child process.data:URL, the dev server can just directly import the customer's real*.backend.tsfile. This PR ships that simplified design from the start, rather than shipping the bundle-based version and rewriting it later.Architecture
executeScriptLocally(local-execution.ts) introduces three collaborating pieces: an injectedloadModulestanding in forserver.ssrLoadModule, aglobalThis.$context populated once per call, and a$.ActionsProxy that turns nested property access into a singleexecuteActioncall.Changes
executeScriptLocally, which imports a backend function's real file directly via an injectedloadModule(the dev server's realserver.ssrLoadModule, or a test double) — no bundling, no wrapper module, nodata:URL.$.ActionsProxy (nested-property-path walk →{fqn, inputs, connectionId}) from the closed fork-based prototype as a direct in-process function call to an injectedExecuteAction, which now carriesconnectionIdfrom day one instead of dropping it.registerActionCatalogIfInstalled/registerBackendRuntimeIfInstalled, which register@datadog/action-catalog'ssetExecuteActionImplementationand@datadog/apps-backend'ssetBackenddirectly in TypeScript, loaded through the sameloadModule(so they resolve against the customer's own project, not build-plugins' own dependency tree) — this replaces what the removed generated wrapper module used to do textually.$context passed to the customer's module (exposed viaglobalThis.$, since the customer's real function takes its own arguments, not a$parameter) carries onlybackendFunctionArgs,Actions, andSource— verified by test — so a real auth token can later live in a module-private closure the customer's imported code has no way to reach.loadModule-result correctness,$.Actionscall resolution (includingconnectionIdforwarding) and validation, sync/async error propagation, timeout behavior, action-catalog typed-wrapper routing, and the no-token-exposure invariant.QA Instructions
yarn test:unit packages/plugins/apps/src/vite/local-execution.test.ts # Expected: Test Suites: 1 passed / Tests: 13 passed ✅ VERIFIEDyarn test:unit packages/plugins/apps # Expected: Test Suites: 23 passed / Tests: 298 passed ✅ VERIFIEDyarn workspace @dd/apps-plugin run typecheck # Expected: no output, clean exit ✅ VERIFIEDnpx eslint packages/plugins/apps/src/vite/local-execution.ts packages/plugins/apps/src/vite/local-execution.test.ts --quiet # Expected: no output, clean exit ✅ VERIFIEDNo manual local or staging QA for this PR specifically: this module isn't wired into
createDevServerMiddlewareyet, so there's nonpm run devrequest path that reachesexecuteScriptLocally()— nothing a human can click through yet, matching the same situation the original fork-based prototype (#461) was in. The tests above exercise a realloadModulecontract (the same shapeserver.ssrLoadModulefulfills), not a mocked substitute for the interesting logic. Real local + staging manual QA becomes possible once this is wired into the dev server (follow-up PR, #481).Blast Radius
Out of Scope / Follow-ups
handleExecuteAction, threading a realLoadModulefromserver.ssrLoadModule)$.ActionsexecutionExecuteActionstays a caller-supplied stub until thenDocumentation