55 * plus the auth/tenant loader decorators (extract token / x-tenant from the IncomingMessage → shared
66 * RequestIdentityLoader / RequestTenantLoader). The per-request feature stack is the transport-agnostic
77 * `registerApiRequestStack` from `@webiny/api-event-handler-core` — the SAME stack the AWS handler uses.
8- * It is called with NEITHER the realtime nor the scheduler hook: those are AWS-specific (API Gateway
9- * WebSockets / EventBridge Scheduler) and this transport has no equivalent — the hooks being optional
10- * is the point. The storage variant (and its identity provider) is injected via `registerRootStorage`.
8+ * Both interleave hooks are supplied with SINGLE-PROCESS, in-process equivalents of the AWS transports:
9+ * the realtime hook installs the server WebSockets transport (vs AWS's API Gateway Management API), and
10+ * the scheduler hook installs the Bree/in-process scheduler (vs AWS's EventBridge Scheduler). Background
11+ * tasks are wired in the ROOT container (below), mirroring how the AWS handler registers its background-
12+ * task transport at root. The storage variant (and its identity provider) is injected via `registerRootStorage`.
1113 *
1214 * The identity provider (e.g. `@webiny/self-hosted-auth`'s JWT IdP) must be registered by the variant
1315 * in `registerRootStorage`, so the RequestIdentityLoader driven by the identity decorator can resolve it.
@@ -23,6 +25,9 @@ import {
2325 WebsocketsConnectionManager ,
2426 attachWebsocketsServer
2527} from "@webiny/api-websockets-server" ;
28+ import { BackgroundTasksServerFeature } from "@webiny/background-tasks-server" ;
29+ import { registerSchedulerServerExtension } from "@webiny/api-scheduler-server" ;
30+ import { FileManagerServerFeature } from "@webiny/api-file-manager-server" ;
2631import { NodeHttpIdentityLoaderDecorator } from "~/handlers/NodeHttpIdentityLoaderDecorator.js" ;
2732import { NodeHttpTenantLoaderDecorator } from "~/handlers/NodeHttpTenantLoaderDecorator.js" ;
2833
@@ -63,25 +68,55 @@ export function createWebinyApiHandler(config: CreateWebinyApiHandlerConfig) {
6368 // connection registry (ConnectionRegistry) is the storage variant's job (e.g. sql).
6469 container . register ( ServerConnectionManager ) . inSingletonScope ( ) ;
6570 container . register ( NodeWsAdapter ) . inSingletonScope ( ) ;
71+
72+ // ── Background tasks (root) ────────────────────────────────
73+ // Mirrors the AWS handler registering its background-task transport at root. There is no
74+ // Step Functions / Lambda re-invocation in a single process, so the transport is in-process:
75+ // WorkerService (the BackgroundTasks/TaskService dispatch abstraction) runs each triggered
76+ // task in a Node worker_thread that POSTs back to this server's `/background-task` HTTP route
77+ // (BackgroundTaskRoute), which runs the task loop (continue/timeout/abort) in-process. Root,
78+ // not per-request, is load-bearing: the shared InternalToken (registered here as a singleton)
79+ // gates the route against the worker's callback, so dispatcher and route MUST see the SAME
80+ // token — a per-request registration would mint a fresh token per request and always 403.
81+ // The route is an HttpRoute; the per-request HttpRouter collects it via the parent chain.
82+ BackgroundTasksServerFeature . register ( container ) ;
6683 } ,
6784
6885 request : async container => {
6986 // The transport-agnostic per-request stack. The realtime hook installs the server
7087 // WebSockets transport (overriding the domain's NullWebsocketsTransport); it resolves the
71- // shared connection manager + adapter from the root. No scheduler hook — that's AWS-only.
88+ // shared connection manager + adapter from the root. The scheduler hook installs the
89+ // Bree/in-process scheduler transport (the single-process equivalent of EventBridge).
7290 await registerApiRequestStack ( container , {
7391 extensions : config . extensions ,
74- // Why a hook (and not just registering the transport ourselves): `.register()` calls
92+ // Why hooks (and not just registering the transports ourselves): `.register()` calls
7593 // are otherwise order-independent — you can register Features in any order, because
7694 // they only REGISTER, they don't RESOLVE during registration (resolution happens
7795 // later, in Initializers / SchemaFactories). The one thing that makes order matter is
78- // a DEFAULT registration: `WebsocketsFeature` registers `NullWebsocketsTransport` so
79- // the abstraction is always resolvable. Overriding it (last-registration-wins) means
80- // our transport MUST be registered AFTER `WebsocketsFeature`. This hook is the seam
81- // `registerApiRequestStack` provides for exactly that — it runs right after the Null
96+ // a DEFAULT registration: e.g. `WebsocketsFeature` registers `NullWebsocketsTransport`
97+ // so the abstraction is always resolvable. Overriding it (last-registration-wins) means
98+ // our transport MUST be registered AFTER that Feature. These hooks are the seams
99+ // `registerApiRequestStack` provides for exactly that — each runs right after its Null
82100 // default, so the override is guaranteed without the caller knowing the internal order.
83- registerRealtimeTransport : requestContainer => {
84- requestContainer . register ( ServerWebsocketsTransport ) ;
101+ transports : {
102+ // Server WebSockets transport; resolves the shared connection manager + adapter
103+ // from the root (registered as singletons above).
104+ realtime : requestContainer => {
105+ requestContainer . register ( ServerWebsocketsTransport ) ;
106+ } ,
107+ // Scheduler transport: the Bree/in-process extension. Where AWS bridges EventBridge
108+ // Scheduler, the single-process server drives delayed/scheduled action triggers with
109+ // in-process timers (Bree).
110+ scheduler : requestContainer => {
111+ registerSchedulerServerExtension ( requestContainer ) ;
112+ } ,
113+ // File-manager storage transport: local disk. Where AWS uses S3 (+ a separate asset-
114+ // delivery Lambda), the single-process server stores files on disk and serves them
115+ // in-process — FileManagerServerFeature registers local asset delivery (overriding
116+ // the domain's null impls), the upload/multipart HTTP routes, and disk file ops.
117+ fileManager : requestContainer => {
118+ FileManagerServerFeature . register ( requestContainer ) ;
119+ }
85120 }
86121 } ) ;
87122 } ,
0 commit comments