@webiny/api-event-handler-standalone 0.0.0-unstable.5387e5f7c8 → 0.0.0-unstable.eb81deee7e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bulkActions/bulkActionsServer.d.ts +0 -4
- package/bulkActions/bulkActionsServer.js +19 -10
- package/bulkActions/bulkActionsServer.js.map +1 -1
- package/createWebinyApiHandler.d.ts +5 -0
- package/createWebinyApiHandler.js +3 -2
- package/createWebinyApiHandler.js.map +1 -1
- package/package.json +18 -17
- package/scheduler/ScheduledActionRecoverRoute.d.ts +34 -0
- package/scheduler/ScheduledActionRecoverRoute.js +84 -0
- package/scheduler/ScheduledActionRecoverRoute.js.map +1 -0
- package/scheduler/ScheduledActionRunRoute.d.ts +31 -0
- package/scheduler/ScheduledActionRunRoute.js +69 -0
- package/scheduler/ScheduledActionRunRoute.js.map +1 -0
- package/scheduler/abstractions/InternalToken.d.ts +14 -0
- package/scheduler/abstractions/InternalToken.js +5 -0
- package/scheduler/abstractions/InternalToken.js.map +1 -0
- package/scheduler/schedulerServer.d.ts +11 -8
- package/scheduler/schedulerServer.js +51 -32
- package/scheduler/schedulerServer.js.map +1 -1
- package/scheduler/RecoverScheduledActionsHandler.d.ts +0 -29
- package/scheduler/RecoverScheduledActionsHandler.js +0 -68
- package/scheduler/RecoverScheduledActionsHandler.js.map +0 -1
- package/scheduler/ScheduledActionRecoverEventHandler.d.ts +0 -13
- package/scheduler/ScheduledActionRecoverEventHandler.js +0 -5
- package/scheduler/ScheduledActionRecoverEventHandler.js.map +0 -1
- package/scheduler/ScheduledActionRecoverEventType.d.ts +0 -17
- package/scheduler/ScheduledActionRecoverEventType.js +0 -18
- package/scheduler/ScheduledActionRecoverEventType.js.map +0 -1
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
import type { Container } from "@webiny/di";
|
|
2
|
-
/**
|
|
3
|
-
* Empties the trash bins on a timer by dispatching an `EmptyTrashBinsEvent`. The standalone
|
|
4
|
-
* counterpart of the EventBridge rule that does it on AWS.
|
|
5
|
-
*/
|
|
6
2
|
export declare function startBulkActionsServer(rootContainer: Container): void;
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { BulkActionsInternalToken } from "@webiny/api-headless-cms-bulk-actions-standalone";
|
|
2
|
+
const BULK_ACTIONS_HEADER = "x-webiny-bulk-actions-token";
|
|
3
|
+
const serverBase = ()=>`http://localhost:${process.env.PORT || "3002"}`;
|
|
3
4
|
const EMPTY_TRASH_INTERVAL_MS = 21600000;
|
|
4
|
-
const FIRST_RUN_DELAY_MS = 5000;
|
|
5
5
|
function startBulkActionsServer(rootContainer) {
|
|
6
|
-
const
|
|
7
|
-
const event = {
|
|
8
|
-
[EMPTY_TRASH_BINS_EVENT_IDENTIFIER]: true
|
|
9
|
-
};
|
|
6
|
+
const token = rootContainer.resolve(BulkActionsInternalToken).value;
|
|
10
7
|
const trigger = async ()=>{
|
|
11
8
|
try {
|
|
12
|
-
await
|
|
9
|
+
const res = await fetch(`${serverBase()}/empty-trash-bins`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
"content-type": "application/json",
|
|
13
|
+
[BULK_ACTIONS_HEADER]: token
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({})
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const body = await res.text().catch(()=>"");
|
|
19
|
+
console.error(`[bulk-actions] empty-trash-bins returned HTTP ${res.status}: ${body}`);
|
|
20
|
+
}
|
|
13
21
|
} catch (err) {
|
|
14
|
-
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
console.error(`[bulk-actions] failed to reach empty-trash-bins route: ${message}`);
|
|
15
24
|
}
|
|
16
25
|
};
|
|
17
|
-
setTimeout(trigger,
|
|
26
|
+
setTimeout(trigger, 5000);
|
|
18
27
|
setInterval(trigger, EMPTY_TRASH_INTERVAL_MS);
|
|
19
28
|
}
|
|
20
29
|
export { startBulkActionsServer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulkActions/bulkActionsServer.js","sources":["../../src/bulkActions/bulkActionsServer.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport {
|
|
1
|
+
{"version":3,"file":"bulkActions/bulkActionsServer.js","sources":["../../src/bulkActions/bulkActionsServer.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { BulkActionsInternalToken } from \"@webiny/api-headless-cms-bulk-actions-standalone\";\n\nconst BULK_ACTIONS_HEADER = \"x-webiny-bulk-actions-token\";\nconst serverBase = () => `http://localhost:${process.env.PORT || \"3002\"}`;\n\n// Default: trigger every 6 hours (in milliseconds).\nconst EMPTY_TRASH_INTERVAL_MS = 6 * 60 * 60 * 1000;\n\nexport function startBulkActionsServer(rootContainer: Container): void {\n const token = rootContainer.resolve(BulkActionsInternalToken).value;\n\n const trigger = async () => {\n try {\n const res = await fetch(`${serverBase()}/empty-trash-bins`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", [BULK_ACTIONS_HEADER]: token },\n body: JSON.stringify({})\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n console.error(\n `[bulk-actions] empty-trash-bins returned HTTP ${res.status}: ${body}`\n );\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`[bulk-actions] failed to reach empty-trash-bins route: ${message}`);\n }\n };\n\n // Deferred initial trigger + periodic interval.\n setTimeout(trigger, 5000);\n setInterval(trigger, EMPTY_TRASH_INTERVAL_MS);\n}\n"],"names":["BULK_ACTIONS_HEADER","serverBase","process","EMPTY_TRASH_INTERVAL_MS","startBulkActionsServer","rootContainer","token","BulkActionsInternalToken","trigger","res","fetch","JSON","body","console","err","message","Error","String","setTimeout","setInterval"],"mappings":";AAGA,MAAMA,sBAAsB;AAC5B,MAAMC,aAAa,IAAM,CAAC,iBAAiB,EAAEC,QAAQ,GAAG,CAAC,IAAI,IAAI,QAAQ;AAGzE,MAAMC,0BAA0B;AAEzB,SAASC,uBAAuBC,aAAwB;IAC3D,MAAMC,QAAQD,cAAc,OAAO,CAACE,0BAA0B,KAAK;IAEnE,MAAMC,UAAU;QACZ,IAAI;YACA,MAAMC,MAAM,MAAMC,MAAM,GAAGT,aAAa,iBAAiB,CAAC,EAAE;gBACxD,QAAQ;gBACR,SAAS;oBAAE,gBAAgB;oBAAoB,CAACD,oBAAoB,EAAEM;gBAAM;gBAC5E,MAAMK,KAAK,SAAS,CAAC,CAAC;YAC1B;YACA,IAAI,CAACF,IAAI,EAAE,EAAE;gBACT,MAAMG,OAAO,MAAMH,IAAI,IAAI,GAAG,KAAK,CAAC,IAAM;gBAC1CI,QAAQ,KAAK,CACT,CAAC,8CAA8C,EAAEJ,IAAI,MAAM,CAAC,EAAE,EAAEG,MAAM;YAE9E;QACJ,EAAE,OAAOE,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DD,QAAQ,KAAK,CAAC,CAAC,uDAAuD,EAAEE,SAAS;QACrF;IACJ;IAGAG,WAAWV,SAAS;IACpBW,YAAYX,SAASL;AACzB"}
|
|
@@ -11,5 +11,10 @@ export interface CreateWebinyApiHandlerConfig {
|
|
|
11
11
|
* IdP assumption — the variant supplies the complete storage + auth wiring.
|
|
12
12
|
*/
|
|
13
13
|
registerRootStorage: (container: Container) => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Register the per-request storage features (e.g. the CMS storage operations) in the request
|
|
16
|
+
* container, before `HeadlessCmsFeature` builds its storage. Optional.
|
|
17
|
+
*/
|
|
18
|
+
registerRequestStorage?: (container: Container) => void | Promise<void>;
|
|
14
19
|
}
|
|
15
20
|
export declare function createWebinyApiHandler(config: CreateWebinyApiHandlerConfig): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
|
|
@@ -12,7 +12,7 @@ import { NodeHttpIdentityLoaderDecorator } from "./handlers/NodeHttpIdentityLoad
|
|
|
12
12
|
import { NodeHttpTenantLoaderDecorator } from "./handlers/NodeHttpTenantLoaderDecorator.js";
|
|
13
13
|
import { NodeHttpAssumePermissionsDecorator } from "./handlers/NodeHttpAssumePermissionsDecorator.js";
|
|
14
14
|
import { createWebsocketsAuthenticator } from "./websockets/createWebsocketsAuthenticator.js";
|
|
15
|
-
import {
|
|
15
|
+
import { EmptyTrashBinRouteFeature } from "@webiny/api-headless-cms-bulk-actions-standalone";
|
|
16
16
|
function createWebinyApiHandler(config) {
|
|
17
17
|
return createServerHandler({
|
|
18
18
|
root: async (rootContainer)=>{
|
|
@@ -28,11 +28,12 @@ function createWebinyApiHandler(config) {
|
|
|
28
28
|
JwksStoreFeature.register(rootContainer);
|
|
29
29
|
BackgroundTasksStandaloneFeature.register(rootContainer);
|
|
30
30
|
registerSchedulerServer(rootContainer);
|
|
31
|
-
|
|
31
|
+
EmptyTrashBinRouteFeature.register(rootContainer);
|
|
32
32
|
},
|
|
33
33
|
child: async (container)=>{
|
|
34
34
|
await registerApiRequestStack(container, {
|
|
35
35
|
extensions: config.extensions,
|
|
36
|
+
registerRequestStorage: config.registerRequestStorage,
|
|
36
37
|
transports: {
|
|
37
38
|
realtime: (requestContainer)=>{
|
|
38
39
|
WebsocketsStandaloneFeature.register(requestContainer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createWebinyApiHandler.js","sources":["../src/createWebinyApiHandler.ts"],"sourcesContent":["/**\n * Webiny API handler for the standalone Node HTTP server transport — storage-agnostic BASE.\n *\n * The ROOT container wires the Node HTTP transport (NodeHttpFeature = event type + router + HttpFeature)\n * plus the auth/tenant loader decorators (extract token / x-tenant from the IncomingMessage → shared\n * RequestIdentityLoader / RequestTenantLoader). The per-request feature stack is the transport-agnostic\n * `registerApiRequestStack` from `@webiny/api-event-handler-core` — the SAME stack the AWS handler uses.\n * The realtime transport hook installs the server WebSockets transport (vs AWS's API Gateway Management\n * API) with a SINGLE-PROCESS, in-process implementation. Background tasks and the Bree scheduler are\n * ROOT-container singletons (below) — mirroring how the AWS handler registers background tasks at root,\n * and the WebSockets connection manager — rather than per-request transports; the scheduler fires timers\n * in-process (vs AWS's EventBridge). The storage variant (and identity provider) is injected via\n * `registerRootStorage`.\n *\n * The identity provider (e.g. `@webiny/self-hosted-auth`'s JWT IdP) must be registered by the variant\n * in `registerRootStorage`, so the RequestIdentityLoader driven by the identity decorator can resolve it.\n */\nimport { GraphQLSchemaCacheFeature } from \"@webiny/api-graphql\";\nimport { EncryptionKeyCacheFeature } from \"@webiny/api-core/features/encryption/index.js\";\nimport { JwksStoreFeature } from \"@webiny/api-core/idp/index.js\";\nimport type { Container } from \"@webiny/di\";\nimport { createServerHandler, NodeHttpFeature } from \"@webiny/event-handler-standalone\";\nimport { registerExtensions } from \"@webiny/handler\";\nimport { registerApiRequestStack } from \"@webiny/api-event-handler-core\";\nimport {\n ServerConnectionManager,\n NodeWsAdapter,\n WebsocketsStandaloneFeature,\n WebsocketsConnectionManager,\n attachWebsocketsServer\n} from \"@webiny/api-websockets-standalone\";\nimport { BackgroundTasksStandaloneFeature } from \"@webiny/background-tasks-standalone\";\nimport { FileManagerStandaloneFeature } from \"@webiny/api-file-manager-standalone\";\nimport { registerSchedulerServer, startSchedulerServer } from \"~/scheduler/schedulerServer.js\";\nimport { startBulkActionsServer } from \"~/bulkActions/bulkActionsServer.js\";\nimport { NodeHttpIdentityLoaderDecorator } from \"~/handlers/NodeHttpIdentityLoaderDecorator.js\";\nimport { NodeHttpTenantLoaderDecorator } from \"~/handlers/NodeHttpTenantLoaderDecorator.js\";\nimport { NodeHttpAssumePermissionsDecorator } from \"~/handlers/NodeHttpAssumePermissionsDecorator.js\";\nimport { createWebsocketsAuthenticator } from \"~/websockets/createWebsocketsAuthenticator.js\";\nimport { EmptyTrashBinsFeature } from \"@webiny/api-headless-cms-bulk-actions-standalone\";\n\nexport interface CreateWebinyApiHandlerConfig {\n /**\n * Project-defined extensions, applied at register() time.\n */\n extensions: () => Parameters<typeof registerExtensions>[1];\n /**\n * Register ALL root-container storage for this deployment (the DB feature + CMS storage ops +\n * registries) AND the identity provider. Unlike the AWS base, the server base bakes in no DB or\n * IdP assumption — the variant supplies the complete storage + auth wiring.\n */\n registerRootStorage: (container: Container) => void | Promise<void>;\n}\n\nexport function createWebinyApiHandler(config: CreateWebinyApiHandlerConfig) {\n return createServerHandler({\n root: async rootContainer => {\n // ── Transport (Node HTTP) ──────────────────────────────────\n // NodeHttpFeature registers the event type + HttpFeature (router) + the routing terminal.\n NodeHttpFeature.register(rootContainer);\n\n // ── Tenant + auth (extract → shared load) ──────────────────\n // registerDecorator applies LATER registrations as the OUTER wrapper (whose execute()\n // runs first). TENANT must be established before IDENTITY: API-key authentication resolves\n // the key by tenant partition (ApiKeysRepository reads TenantContext.getTenant()), so\n // identity depends on the tenant; the reverse is not true (RequestTenantLoader has no\n // identity dependency). Register identity first (inner) and tenant last (outer) → tenant\n // runs, then identity, then the router.\n rootContainer.registerDecorator(NodeHttpIdentityLoaderDecorator);\n rootContainer.registerDecorator(NodeHttpTenantLoaderDecorator);\n\n // Preview-as. Mirrors the AWS handler.\n rootContainer.registerDecorator(NodeHttpAssumePermissionsDecorator);\n\n // ── Storage + identity provider (variant-supplied) ─────────\n await config.registerRootStorage(rootContainer);\n\n // ── WebSockets transport (root) ────────────────────────────\n // Live-socket registry + adapter live in the root as singletons: the connection manager\n // is shared between the upgrade acceptor (attachWebsocketsServer, below) and the\n // per-request ServerWebsocketsTransport that sends to those live sockets. The persistent\n // connection registry (ConnectionRegistry) is the storage variant's job (e.g. sql).\n rootContainer.register(ServerConnectionManager).inSingletonScope();\n rootContainer.register(NodeWsAdapter).inSingletonScope();\n\n // ── GraphQL schema cache (root) ────────────────────────────\n // Root, so a built schema outlives the request that built it.\n GraphQLSchemaCacheFeature.register(rootContainer);\n\n // ── Encryption key cache (root) ────────────────────────────\n // Root, so the scrypt-derived key is computed once per process instead of per request.\n EncryptionKeyCacheFeature.register(rootContainer);\n // Root, so identity providers' signing keys are fetched once per process, not per request.\n JwksStoreFeature.register(rootContainer);\n\n // ── Background tasks (root) ────────────────────────────────\n // Mirrors the AWS handler registering its background-task transport at root. Where AWS\n // has Step Functions invoke the Lambda once per iteration, a root task loop dispatches a\n // BackgroundTaskEvent per iteration through the EventDispatcher (registered by\n // createServerHandler), and each one runs in a fresh request container.\n BackgroundTasksStandaloneFeature.register(rootContainer);\n\n // ── Scheduler (root) ───────────────────────────────────────\n // The Bree scheduler is a single long-lived instance for ALL tenants, started once at boot\n // (onServer, below) — the counterpart of the WebSockets connection manager, NOT a per-request\n // transport. Registered here as SchedulerService (per-request create/update/delete during\n // mutations manipulate this one live timer set) plus the run/recover event handlers. When a\n // timer fires (outside any request) it dispatches a ScheduledActionEvent, handled in a fresh\n // request container by the same handler AWS uses.\n registerSchedulerServer(rootContainer);\n\n // ── Bulk actions (root) ───────────────────────────────────\n // Registers the EmptyTrashBinsEvent handler that the periodic trigger\n // (startBulkActionsServer, onServer below) dispatches.\n EmptyTrashBinsFeature.register(rootContainer);\n },\n\n child: async container => {\n // The transport-agnostic per-request stack. The realtime hook installs the server\n // WebSockets transport (overriding the domain's NullWebsocketsTransport); it resolves the\n // shared connection manager + adapter from the root. Scheduler is NOT a per-request\n // transport here — it's a root singleton wired in `root` + `onServer` (see above).\n await registerApiRequestStack(container, {\n extensions: config.extensions,\n // Why hooks (and not just registering the transports ourselves): `.register()` calls\n // are otherwise order-independent — you can register Features in any order, because\n // they only REGISTER, they don't RESOLVE during registration (resolution happens\n // later, in Initializers / SchemaFactories). The one thing that makes order matter is\n // a DEFAULT registration: e.g. `WebsocketsFeature` registers `NullWebsocketsTransport`\n // so the abstraction is always resolvable. Overriding it (last-registration-wins) means\n // our transport MUST be registered AFTER that Feature. These hooks are the seams\n // `registerApiRequestStack` provides for exactly that — each runs right after its Null\n // default, so the override is guaranteed without the caller knowing the internal order.\n transports: {\n // Server WebSockets transport; resolves the shared connection manager + adapter\n // from the root (registered as singletons above).\n realtime: requestContainer => {\n WebsocketsStandaloneFeature.register(requestContainer);\n },\n // File-manager storage transport: local disk. Where AWS uses S3 (+ a separate asset-\n // delivery Lambda), the single-process server stores files on disk and serves them\n // in-process — FileManagerStandaloneFeature registers local asset delivery (overriding\n // the domain's null impls), the upload/multipart HTTP routes, and disk file ops.\n fileManager: requestContainer => {\n FileManagerStandaloneFeature.register(requestContainer);\n }\n }\n });\n },\n\n onServer: async (server, rootContainer) => {\n // Attach the WebSockets upgrade handler to the running HTTP server, backed by the shared\n // (root) connection manager so request-time sends reach the live sockets.\n //\n // Authenticate each WebSocket connection from its `?token` JWT so it's registered under\n // the real identity (targeted server→client sends match by identity id). It's a callback\n // the handler supplies rather than something the WS server resolves itself — see\n // createWebsocketsAuthenticator for why (AuthenticationContext is per-request, unreachable\n // from the root container the WS server is attached to).\n const websockets = attachWebsocketsServer({\n server,\n connectionManager: rootContainer.resolve(WebsocketsConnectionManager),\n authenticate: createWebsocketsAuthenticator(rootContainer, config.extensions)\n });\n await websockets.start();\n\n // Start the in-process scheduler timers, then re-arm persisted schedules in the\n // background (see startSchedulerServer).\n await startSchedulerServer(rootContainer);\n\n // Start the periodic empty-trash-bin trigger.\n startBulkActionsServer(rootContainer);\n }\n });\n}\n"],"names":["createWebinyApiHandler","config","createServerHandler","rootContainer","NodeHttpFeature","NodeHttpIdentityLoaderDecorator","NodeHttpTenantLoaderDecorator","NodeHttpAssumePermissionsDecorator","ServerConnectionManager","NodeWsAdapter","GraphQLSchemaCacheFeature","EncryptionKeyCacheFeature","JwksStoreFeature","BackgroundTasksStandaloneFeature","registerSchedulerServer","EmptyTrashBinsFeature","container","registerApiRequestStack","requestContainer","WebsocketsStandaloneFeature","FileManagerStandaloneFeature","server","websockets","attachWebsocketsServer","WebsocketsConnectionManager","createWebsocketsAuthenticator","startSchedulerServer","startBulkActionsServer"],"mappings":";;;;;;;;;;;;;;;AAsDO,SAASA,uBAAuBC,MAAoC;IACvE,OAAOC,oBAAoB;QACvB,MAAM,OAAMC;YAGRC,gBAAgB,QAAQ,CAACD;YASzBA,cAAc,iBAAiB,CAACE;YAChCF,cAAc,iBAAiB,CAACG;YAGhCH,cAAc,iBAAiB,CAACI;YAGhC,MAAMN,OAAO,mBAAmB,CAACE;YAOjCA,cAAc,QAAQ,CAACK,yBAAyB,gBAAgB;YAChEL,cAAc,QAAQ,CAACM,eAAe,gBAAgB;YAItDC,0BAA0B,QAAQ,CAACP;YAInCQ,0BAA0B,QAAQ,CAACR;YAEnCS,iBAAiB,QAAQ,CAACT;YAO1BU,iCAAiC,QAAQ,CAACV;YAS1CW,wBAAwBX;YAKxBY,sBAAsB,QAAQ,CAACZ;QACnC;QAEA,OAAO,OAAMa;YAKT,MAAMC,wBAAwBD,WAAW;gBACrC,YAAYf,OAAO,UAAU;gBAU7B,YAAY;oBAGR,UAAUiB,CAAAA;wBACNC,4BAA4B,QAAQ,CAACD;oBACzC;oBAKA,aAAaA,CAAAA;wBACTE,6BAA6B,QAAQ,CAACF;oBAC1C;gBACJ;YACJ;QACJ;QAEA,UAAU,OAAOG,QAAQlB;YASrB,MAAMmB,aAAaC,uBAAuB;gBACtCF;gBACA,mBAAmBlB,cAAc,OAAO,CAACqB;gBACzC,cAAcC,8BAA8BtB,eAAeF,OAAO,UAAU;YAChF;YACA,MAAMqB,WAAW,KAAK;YAItB,MAAMI,qBAAqBvB;YAG3BwB,uBAAuBxB;QAC3B;IACJ;AACJ"}
|
|
1
|
+
{"version":3,"file":"createWebinyApiHandler.js","sources":["../src/createWebinyApiHandler.ts"],"sourcesContent":["/**\n * Webiny API handler for the standalone Node HTTP server transport — storage-agnostic BASE.\n *\n * The ROOT container wires the Node HTTP transport (NodeHttpFeature = event type + router + HttpFeature)\n * plus the auth/tenant loader decorators (extract token / x-tenant from the IncomingMessage → shared\n * RequestIdentityLoader / RequestTenantLoader). The per-request feature stack is the transport-agnostic\n * `registerApiRequestStack` from `@webiny/api-event-handler-core` — the SAME stack the AWS handler uses.\n * The realtime transport hook installs the server WebSockets transport (vs AWS's API Gateway Management\n * API) with a SINGLE-PROCESS, in-process implementation. Background tasks and the Bree scheduler are\n * ROOT-container singletons (below) — mirroring how the AWS handler registers background tasks at root,\n * and the WebSockets connection manager — rather than per-request transports; the scheduler fires timers\n * in-process (vs AWS's EventBridge). The storage variant (and identity provider) is injected via\n * `registerRootStorage`.\n *\n * The identity provider (e.g. `@webiny/self-hosted-auth`'s JWT IdP) must be registered by the variant\n * in `registerRootStorage`, so the RequestIdentityLoader driven by the identity decorator can resolve it.\n */\nimport { GraphQLSchemaCacheFeature } from \"@webiny/api-graphql\";\nimport { EncryptionKeyCacheFeature } from \"@webiny/api-core/features/encryption/index.js\";\nimport { JwksStoreFeature } from \"@webiny/api-core/idp/index.js\";\nimport type { Container } from \"@webiny/di\";\nimport { createServerHandler, NodeHttpFeature } from \"@webiny/event-handler-standalone\";\nimport { registerExtensions } from \"@webiny/handler\";\nimport { registerApiRequestStack } from \"@webiny/api-event-handler-core\";\nimport {\n ServerConnectionManager,\n NodeWsAdapter,\n WebsocketsStandaloneFeature,\n WebsocketsConnectionManager,\n attachWebsocketsServer\n} from \"@webiny/api-websockets-standalone\";\nimport { BackgroundTasksStandaloneFeature } from \"@webiny/background-tasks-standalone\";\nimport { FileManagerStandaloneFeature } from \"@webiny/api-file-manager-standalone\";\nimport { registerSchedulerServer, startSchedulerServer } from \"~/scheduler/schedulerServer.js\";\nimport { startBulkActionsServer } from \"~/bulkActions/bulkActionsServer.js\";\nimport { NodeHttpIdentityLoaderDecorator } from \"~/handlers/NodeHttpIdentityLoaderDecorator.js\";\nimport { NodeHttpTenantLoaderDecorator } from \"~/handlers/NodeHttpTenantLoaderDecorator.js\";\nimport { NodeHttpAssumePermissionsDecorator } from \"~/handlers/NodeHttpAssumePermissionsDecorator.js\";\nimport { createWebsocketsAuthenticator } from \"~/websockets/createWebsocketsAuthenticator.js\";\nimport { EmptyTrashBinRouteFeature } from \"@webiny/api-headless-cms-bulk-actions-standalone\";\n\nexport interface CreateWebinyApiHandlerConfig {\n /**\n * Project-defined extensions, applied at register() time.\n */\n extensions: () => Parameters<typeof registerExtensions>[1];\n /**\n * Register ALL root-container storage for this deployment (the DB feature + CMS storage ops +\n * registries) AND the identity provider. Unlike the AWS base, the server base bakes in no DB or\n * IdP assumption — the variant supplies the complete storage + auth wiring.\n */\n registerRootStorage: (container: Container) => void | Promise<void>;\n /**\n * Register the per-request storage features (e.g. the CMS storage operations) in the request\n * container, before `HeadlessCmsFeature` builds its storage. Optional.\n */\n registerRequestStorage?: (container: Container) => void | Promise<void>;\n}\n\nexport function createWebinyApiHandler(config: CreateWebinyApiHandlerConfig) {\n return createServerHandler({\n root: async rootContainer => {\n // ── Transport (Node HTTP) ──────────────────────────────────\n // NodeHttpFeature registers the event type + HttpFeature (router) + the routing terminal.\n NodeHttpFeature.register(rootContainer);\n\n // ── Tenant + auth (extract → shared load) ──────────────────\n // registerDecorator applies LATER registrations as the OUTER wrapper (whose execute()\n // runs first). TENANT must be established before IDENTITY: API-key authentication resolves\n // the key by tenant partition (ApiKeysRepository reads TenantContext.getTenant()), so\n // identity depends on the tenant; the reverse is not true (RequestTenantLoader has no\n // identity dependency). Register identity first (inner) and tenant last (outer) → tenant\n // runs, then identity, then the router.\n rootContainer.registerDecorator(NodeHttpIdentityLoaderDecorator);\n rootContainer.registerDecorator(NodeHttpTenantLoaderDecorator);\n\n // Preview-as. Mirrors the AWS handler.\n rootContainer.registerDecorator(NodeHttpAssumePermissionsDecorator);\n\n // ── Storage + identity provider (variant-supplied) ─────────\n await config.registerRootStorage(rootContainer);\n\n // ── WebSockets transport (root) ────────────────────────────\n // Live-socket registry + adapter live in the root as singletons: the connection manager\n // is shared between the upgrade acceptor (attachWebsocketsServer, below) and the\n // per-request ServerWebsocketsTransport that sends to those live sockets. The persistent\n // connection registry (ConnectionRegistry) is the storage variant's job (e.g. sql).\n rootContainer.register(ServerConnectionManager).inSingletonScope();\n rootContainer.register(NodeWsAdapter).inSingletonScope();\n\n // ── GraphQL schema cache (root) ────────────────────────────\n // Root, so a built schema outlives the request that built it.\n GraphQLSchemaCacheFeature.register(rootContainer);\n\n // ── Encryption key cache (root) ────────────────────────────\n // Root, so the scrypt-derived key is computed once per process instead of per request.\n EncryptionKeyCacheFeature.register(rootContainer);\n // Root, so identity providers' signing keys are fetched once per process, not per request.\n JwksStoreFeature.register(rootContainer);\n\n // ── Background tasks (root) ────────────────────────────────\n // Mirrors the AWS handler registering its background-task transport at root. There is no\n // Step Functions / Lambda re-invocation in a single process, so the transport is in-process:\n // WorkerService (the BackgroundTasks/TaskService dispatch abstraction) runs each triggered\n // task in a Node worker_thread that POSTs back to this server's `/background-task` HTTP route\n // (BackgroundTaskRoute), which runs the task loop (continue/timeout/abort) in-process. Root,\n // not per-request, is load-bearing: the shared InternalToken (registered here as a singleton)\n // gates the route against the worker's callback, so dispatcher and route MUST see the SAME\n // token — a per-request registration would mint a fresh token per request and always 403.\n // The route is an HttpRoute; the per-request HttpRouter collects it via the parent chain.\n BackgroundTasksStandaloneFeature.register(rootContainer);\n\n // ── Scheduler (root) ───────────────────────────────────────\n // The Bree scheduler is a single long-lived instance for ALL tenants, started once at boot\n // (onServer, below) — the counterpart of the WebSockets connection manager, NOT a per-request\n // transport. Registered here as SchedulerService (per-request create/update/delete during\n // mutations manipulate this one live timer set) plus the run/recover HTTP routes + internal\n // token. When a timer fires (outside any request) it POSTs `/scheduled-action-run`, which\n // rebuilds the tenant's request context and executes the action.\n registerSchedulerServer(rootContainer);\n\n // ── Bulk actions (root) ───────────────────────────────────\n // Registers the `/empty-trash-bins` HTTP route + internal token so the periodic trigger\n // (startBulkActionsServer, onServer below) can POST to it after the server is listening.\n EmptyTrashBinRouteFeature.register(rootContainer);\n },\n\n child: async container => {\n // The transport-agnostic per-request stack. The realtime hook installs the server\n // WebSockets transport (overriding the domain's NullWebsocketsTransport); it resolves the\n // shared connection manager + adapter from the root. Scheduler is NOT a per-request\n // transport here — it's a root singleton wired in `root` + `onServer` (see above).\n await registerApiRequestStack(container, {\n extensions: config.extensions,\n registerRequestStorage: config.registerRequestStorage,\n // Why hooks (and not just registering the transports ourselves): `.register()` calls\n // are otherwise order-independent — you can register Features in any order, because\n // they only REGISTER, they don't RESOLVE during registration (resolution happens\n // later, in Initializers / SchemaFactories). The one thing that makes order matter is\n // a DEFAULT registration: e.g. `WebsocketsFeature` registers `NullWebsocketsTransport`\n // so the abstraction is always resolvable. Overriding it (last-registration-wins) means\n // our transport MUST be registered AFTER that Feature. These hooks are the seams\n // `registerApiRequestStack` provides for exactly that — each runs right after its Null\n // default, so the override is guaranteed without the caller knowing the internal order.\n transports: {\n // Server WebSockets transport; resolves the shared connection manager + adapter\n // from the root (registered as singletons above).\n realtime: requestContainer => {\n WebsocketsStandaloneFeature.register(requestContainer);\n },\n // File-manager storage transport: local disk. Where AWS uses S3 (+ a separate asset-\n // delivery Lambda), the single-process server stores files on disk and serves them\n // in-process — FileManagerStandaloneFeature registers local asset delivery (overriding\n // the domain's null impls), the upload/multipart HTTP routes, and disk file ops.\n fileManager: requestContainer => {\n FileManagerStandaloneFeature.register(requestContainer);\n }\n }\n });\n },\n\n onServer: async (server, rootContainer) => {\n // Attach the WebSockets upgrade handler to the running HTTP server, backed by the shared\n // (root) connection manager so request-time sends reach the live sockets.\n //\n // Authenticate each WebSocket connection from its `?token` JWT so it's registered under\n // the real identity (targeted server→client sends match by identity id). It's a callback\n // the handler supplies rather than something the WS server resolves itself — see\n // createWebsocketsAuthenticator for why (AuthenticationContext is per-request, unreachable\n // from the root container the WS server is attached to).\n const websockets = attachWebsocketsServer({\n server,\n connectionManager: rootContainer.resolve(WebsocketsConnectionManager),\n authenticate: createWebsocketsAuthenticator(rootContainer, config.extensions)\n });\n await websockets.start();\n\n // Start the in-process scheduler timers, then re-arm persisted schedules (deferred until\n // the server is listening — see startSchedulerServer).\n await startSchedulerServer(rootContainer);\n\n // Start the periodic empty-trash-bin trigger (deferred until the server is listening).\n startBulkActionsServer(rootContainer);\n }\n });\n}\n"],"names":["createWebinyApiHandler","config","createServerHandler","rootContainer","NodeHttpFeature","NodeHttpIdentityLoaderDecorator","NodeHttpTenantLoaderDecorator","NodeHttpAssumePermissionsDecorator","ServerConnectionManager","NodeWsAdapter","GraphQLSchemaCacheFeature","EncryptionKeyCacheFeature","JwksStoreFeature","BackgroundTasksStandaloneFeature","registerSchedulerServer","EmptyTrashBinRouteFeature","container","registerApiRequestStack","requestContainer","WebsocketsStandaloneFeature","FileManagerStandaloneFeature","server","websockets","attachWebsocketsServer","WebsocketsConnectionManager","createWebsocketsAuthenticator","startSchedulerServer","startBulkActionsServer"],"mappings":";;;;;;;;;;;;;;;AA2DO,SAASA,uBAAuBC,MAAoC;IACvE,OAAOC,oBAAoB;QACvB,MAAM,OAAMC;YAGRC,gBAAgB,QAAQ,CAACD;YASzBA,cAAc,iBAAiB,CAACE;YAChCF,cAAc,iBAAiB,CAACG;YAGhCH,cAAc,iBAAiB,CAACI;YAGhC,MAAMN,OAAO,mBAAmB,CAACE;YAOjCA,cAAc,QAAQ,CAACK,yBAAyB,gBAAgB;YAChEL,cAAc,QAAQ,CAACM,eAAe,gBAAgB;YAItDC,0BAA0B,QAAQ,CAACP;YAInCQ,0BAA0B,QAAQ,CAACR;YAEnCS,iBAAiB,QAAQ,CAACT;YAY1BU,iCAAiC,QAAQ,CAACV;YAS1CW,wBAAwBX;YAKxBY,0BAA0B,QAAQ,CAACZ;QACvC;QAEA,OAAO,OAAMa;YAKT,MAAMC,wBAAwBD,WAAW;gBACrC,YAAYf,OAAO,UAAU;gBAC7B,wBAAwBA,OAAO,sBAAsB;gBAUrD,YAAY;oBAGR,UAAUiB,CAAAA;wBACNC,4BAA4B,QAAQ,CAACD;oBACzC;oBAKA,aAAaA,CAAAA;wBACTE,6BAA6B,QAAQ,CAACF;oBAC1C;gBACJ;YACJ;QACJ;QAEA,UAAU,OAAOG,QAAQlB;YASrB,MAAMmB,aAAaC,uBAAuB;gBACtCF;gBACA,mBAAmBlB,cAAc,OAAO,CAACqB;gBACzC,cAAcC,8BAA8BtB,eAAeF,OAAO,UAAU;YAChF;YACA,MAAMqB,WAAW,KAAK;YAItB,MAAMI,qBAAqBvB;YAG3BwB,uBAAuBxB;QAC3B;IACJ;AACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/api-event-handler-standalone",
|
|
3
|
-
"version": "0.0.0-unstable.
|
|
3
|
+
"version": "0.0.0-unstable.eb81deee7e",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -18,26 +18,27 @@
|
|
|
18
18
|
],
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webiny/api-core": "0.0.0-unstable.
|
|
22
|
-
"@webiny/api-event-handler-core": "0.0.0-unstable.
|
|
23
|
-
"@webiny/api-file-manager-standalone": "0.0.0-unstable.
|
|
24
|
-
"@webiny/api-graphql": "0.0.0-unstable.
|
|
25
|
-
"@webiny/api-headless-cms-bulk-actions-standalone": "0.0.0-unstable.
|
|
26
|
-
"@webiny/api-scheduler": "0.0.0-unstable.
|
|
27
|
-
"@webiny/api-scheduler-standalone": "0.0.0-unstable.
|
|
28
|
-
"@webiny/api-websockets-standalone": "0.0.0-unstable.
|
|
29
|
-
"@webiny/background-tasks-standalone": "0.0.0-unstable.
|
|
30
|
-
"@webiny/event-handler-core": "0.0.0-unstable.
|
|
31
|
-
"@webiny/event-handler-standalone": "0.0.0-unstable.
|
|
32
|
-
"@webiny/feature": "0.0.0-unstable.
|
|
33
|
-
"@webiny/handler": "0.0.0-unstable.
|
|
21
|
+
"@webiny/api-core": "0.0.0-unstable.eb81deee7e",
|
|
22
|
+
"@webiny/api-event-handler-core": "0.0.0-unstable.eb81deee7e",
|
|
23
|
+
"@webiny/api-file-manager-standalone": "0.0.0-unstable.eb81deee7e",
|
|
24
|
+
"@webiny/api-graphql": "0.0.0-unstable.eb81deee7e",
|
|
25
|
+
"@webiny/api-headless-cms-bulk-actions-standalone": "0.0.0-unstable.eb81deee7e",
|
|
26
|
+
"@webiny/api-scheduler": "0.0.0-unstable.eb81deee7e",
|
|
27
|
+
"@webiny/api-scheduler-standalone": "0.0.0-unstable.eb81deee7e",
|
|
28
|
+
"@webiny/api-websockets-standalone": "0.0.0-unstable.eb81deee7e",
|
|
29
|
+
"@webiny/background-tasks-standalone": "0.0.0-unstable.eb81deee7e",
|
|
30
|
+
"@webiny/event-handler-core": "0.0.0-unstable.eb81deee7e",
|
|
31
|
+
"@webiny/event-handler-standalone": "0.0.0-unstable.eb81deee7e",
|
|
32
|
+
"@webiny/feature": "0.0.0-unstable.eb81deee7e",
|
|
33
|
+
"@webiny/handler": "0.0.0-unstable.eb81deee7e",
|
|
34
|
+
"@webiny/stdlib": "0.0.17"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
|
-
"@webiny/di": "^
|
|
37
|
+
"@webiny/di": "^1.1.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@webiny/build-tools": "0.0.0-unstable.
|
|
40
|
-
"@webiny/di": "
|
|
40
|
+
"@webiny/build-tools": "0.0.0-unstable.eb81deee7e",
|
|
41
|
+
"@webiny/di": "1.1.0",
|
|
41
42
|
"rimraf": "6.1.3",
|
|
42
43
|
"typescript": "7.0.2",
|
|
43
44
|
"vitest": "4.1.11"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler } from "@webiny/event-handler-core";
|
|
2
|
+
import type { Container } from "@webiny/feature/api";
|
|
3
|
+
import { SchedulerInternalToken } from "./abstractions/InternalToken.js";
|
|
4
|
+
/**
|
|
5
|
+
* Re-arms the persisted (pending) scheduled actions for a tenant into the root Bree singleton — used
|
|
6
|
+
* at boot to restore schedules after a restart. Runs the full request-context bootstrap (like the run
|
|
7
|
+
* route) so ListScheduledActionsUseCase has its CMS model, then hands the results to the singleton's
|
|
8
|
+
* `recover()`. Defaults to the root tenant when no tenant is given.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: recovers ONE tenant per call. Boot recovery for additional (non-root) tenants would enumerate
|
|
11
|
+
* tenants and call this per tenant — left as a follow-up; single/root-tenant deployments are covered.
|
|
12
|
+
*/
|
|
13
|
+
declare class ScheduledActionRecoverRouteImpl implements HttpRouteHandler.Interface {
|
|
14
|
+
private readonly container;
|
|
15
|
+
private readonly internalToken;
|
|
16
|
+
constructor(container: Container, internalToken: SchedulerInternalToken.Interface);
|
|
17
|
+
handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response): Promise<import("@webiny/event-handler-core").IHttpResponseBuilder>;
|
|
18
|
+
}
|
|
19
|
+
export declare const ScheduledActionRecoverRoute: typeof ScheduledActionRecoverRouteImpl & {
|
|
20
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
21
|
+
};
|
|
22
|
+
declare class ScheduledActionRecoverRouteDefinitionImpl implements HttpRouteDefinition.Interface {
|
|
23
|
+
readonly name = "scheduled-action-recover";
|
|
24
|
+
readonly method = "POST";
|
|
25
|
+
readonly path = "/scheduled-action-recover";
|
|
26
|
+
readonly handler: typeof ScheduledActionRecoverRouteImpl & {
|
|
27
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** What the router matches on. Zero dependencies, so building it costs nothing. */
|
|
31
|
+
export declare const ScheduledActionRecoverRouteDefinition: typeof ScheduledActionRecoverRouteDefinitionImpl & {
|
|
32
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRouteDefinition>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler, RequestContainer } from "@webiny/event-handler-core";
|
|
2
|
+
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
3
|
+
import { GetRootTenantUseCase } from "@webiny/api-core/features/tenancy/GetRootTenant/index.js";
|
|
4
|
+
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
5
|
+
import { ListScheduledActionsUseCase } from "@webiny/api-scheduler/features/ListScheduledActions/index.js";
|
|
6
|
+
import { SchedulerInternalToken } from "./abstractions/InternalToken.js";
|
|
7
|
+
import { SchedulerSingleton } from "./abstractions/SchedulerSingleton.js";
|
|
8
|
+
const INTERNAL_HEADER = "x-webiny-scheduler-token";
|
|
9
|
+
class ScheduledActionRecoverRouteImpl {
|
|
10
|
+
constructor(container, internalToken){
|
|
11
|
+
this.container = container;
|
|
12
|
+
this.internalToken = internalToken;
|
|
13
|
+
}
|
|
14
|
+
async handle(request, response) {
|
|
15
|
+
if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) return response.status(403).json({
|
|
16
|
+
error: "Forbidden."
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
let tenant = request.body?.tenant;
|
|
20
|
+
if (!tenant) {
|
|
21
|
+
const rootResult = await this.container.resolve(GetRootTenantUseCase).execute();
|
|
22
|
+
if (rootResult.isFail()) return response.json({
|
|
23
|
+
status: "ok",
|
|
24
|
+
recovered: 0
|
|
25
|
+
});
|
|
26
|
+
tenant = rootResult.value.id;
|
|
27
|
+
}
|
|
28
|
+
this.container.resolve(RawTenantId).set(tenant);
|
|
29
|
+
await this.container.resolve(RequestTenantLoader).establish();
|
|
30
|
+
const listResult = await this.container.resolve(IdentityContext).withoutAuthorization(()=>this.container.resolve(ListScheduledActionsUseCase).execute({
|
|
31
|
+
where: {},
|
|
32
|
+
limit: 1000
|
|
33
|
+
}));
|
|
34
|
+
if (listResult.isFail()) return response.status(500).json({
|
|
35
|
+
status: "error",
|
|
36
|
+
error: {
|
|
37
|
+
message: listResult.error.message
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const pending = listResult.value.items.map((action)=>({
|
|
41
|
+
id: action.id,
|
|
42
|
+
namespace: action.namespace,
|
|
43
|
+
tenant: tenant,
|
|
44
|
+
scheduledFor: action.scheduledFor
|
|
45
|
+
}));
|
|
46
|
+
await this.container.resolve(SchedulerSingleton).recover(pending);
|
|
47
|
+
return response.json({
|
|
48
|
+
status: "ok",
|
|
49
|
+
recovered: pending.length
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
console.error(`Scheduled action recover route error: ${message}`);
|
|
54
|
+
return response.status(500).json({
|
|
55
|
+
status: "error",
|
|
56
|
+
error: {
|
|
57
|
+
message
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const ScheduledActionRecoverRoute = HttpRouteHandler.createImplementation({
|
|
64
|
+
implementation: ScheduledActionRecoverRouteImpl,
|
|
65
|
+
dependencies: [
|
|
66
|
+
RequestContainer,
|
|
67
|
+
SchedulerInternalToken
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
class ScheduledActionRecoverRouteDefinitionImpl {
|
|
71
|
+
constructor(){
|
|
72
|
+
this.name = "scheduled-action-recover";
|
|
73
|
+
this.method = "POST";
|
|
74
|
+
this.path = "/scheduled-action-recover";
|
|
75
|
+
this.handler = ScheduledActionRecoverRoute;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const ScheduledActionRecoverRouteDefinition = HttpRouteDefinition.createImplementation({
|
|
79
|
+
implementation: ScheduledActionRecoverRouteDefinitionImpl,
|
|
80
|
+
dependencies: []
|
|
81
|
+
});
|
|
82
|
+
export { ScheduledActionRecoverRoute, ScheduledActionRecoverRouteDefinition };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=ScheduledActionRecoverRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler/ScheduledActionRecoverRoute.js","sources":["../../src/scheduler/ScheduledActionRecoverRoute.ts"],"sourcesContent":["import {\n HttpRouteDefinition,\n HttpRouteHandler,\n RequestContainer\n} from \"@webiny/event-handler-core\";\nimport {\n RawTenantId,\n RequestTenantLoader\n} from \"@webiny/api-core/features/requestContext/index.js\";\nimport { GetRootTenantUseCase } from \"@webiny/api-core/features/tenancy/GetRootTenant/index.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport { ListScheduledActionsUseCase } from \"@webiny/api-scheduler/features/ListScheduledActions/index.js\";\nimport type { Container } from \"@webiny/feature/api\";\nimport { SchedulerInternalToken } from \"./abstractions/InternalToken.js\";\nimport { SchedulerSingleton } from \"./abstractions/SchedulerSingleton.js\";\n\nconst INTERNAL_HEADER = \"x-webiny-scheduler-token\";\n\n/**\n * Re-arms the persisted (pending) scheduled actions for a tenant into the root Bree singleton — used\n * at boot to restore schedules after a restart. Runs the full request-context bootstrap (like the run\n * route) so ListScheduledActionsUseCase has its CMS model, then hands the results to the singleton's\n * `recover()`. Defaults to the root tenant when no tenant is given.\n *\n * NOTE: recovers ONE tenant per call. Boot recovery for additional (non-root) tenants would enumerate\n * tenants and call this per tenant — left as a follow-up; single/root-tenant deployments are covered.\n */\nclass ScheduledActionRecoverRouteImpl implements HttpRouteHandler.Interface {\n public constructor(\n private readonly container: Container,\n private readonly internalToken: SchedulerInternalToken.Interface\n ) {}\n\n public async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {\n if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) {\n return response.status(403).json({ error: \"Forbidden.\" });\n }\n\n try {\n let tenant: string | undefined = request.body?.tenant;\n if (!tenant) {\n const rootResult = await this.container.resolve(GetRootTenantUseCase).execute();\n if (rootResult.isFail()) {\n return response.json({ status: \"ok\", recovered: 0 });\n }\n tenant = rootResult.value.id;\n }\n\n this.container.resolve(RawTenantId).set(tenant);\n await this.container.resolve(RequestTenantLoader).establish();\n\n // At boot there's no request and no identity, so we skip authorization for the list — the\n // same thing ExecuteScheduledActionUseCase does on the run route. Without it, the\n // permission check in ListScheduledActions sees the anonymous identity and fails with\n // \"Not authorized!\".\n const listResult = await this.container\n .resolve(IdentityContext)\n .withoutAuthorization(() =>\n this.container\n .resolve(ListScheduledActionsUseCase)\n .execute({ where: {}, limit: 1000 })\n );\n\n if (listResult.isFail()) {\n return response\n .status(500)\n .json({ status: \"error\", error: { message: listResult.error.message } });\n }\n\n const pending = listResult.value.items.map(action => ({\n id: action.id,\n namespace: action.namespace,\n tenant: tenant as string,\n scheduledFor: action.scheduledFor\n }));\n\n await this.container.resolve(SchedulerSingleton).recover(pending);\n\n return response.json({ status: \"ok\", recovered: pending.length });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Scheduled action recover route error: ${message}`);\n return response.status(500).json({ status: \"error\", error: { message } });\n }\n }\n}\n\nexport const ScheduledActionRecoverRoute = HttpRouteHandler.createImplementation({\n implementation: ScheduledActionRecoverRouteImpl,\n dependencies: [RequestContainer, SchedulerInternalToken]\n});\n\nclass ScheduledActionRecoverRouteDefinitionImpl implements HttpRouteDefinition.Interface {\n readonly name = \"scheduled-action-recover\";\n readonly method = \"POST\";\n readonly path = \"/scheduled-action-recover\";\n readonly handler = ScheduledActionRecoverRoute;\n}\n\n/** What the router matches on. Zero dependencies, so building it costs nothing. */\nexport const ScheduledActionRecoverRouteDefinition = HttpRouteDefinition.createImplementation({\n implementation: ScheduledActionRecoverRouteDefinitionImpl,\n dependencies: []\n});\n"],"names":["INTERNAL_HEADER","ScheduledActionRecoverRouteImpl","container","internalToken","request","response","tenant","rootResult","GetRootTenantUseCase","RawTenantId","RequestTenantLoader","listResult","IdentityContext","ListScheduledActionsUseCase","pending","action","SchedulerSingleton","err","message","Error","String","console","ScheduledActionRecoverRoute","HttpRouteHandler","RequestContainer","SchedulerInternalToken","ScheduledActionRecoverRouteDefinitionImpl","ScheduledActionRecoverRouteDefinition","HttpRouteDefinition"],"mappings":";;;;;;;AAgBA,MAAMA,kBAAkB;AAWxB,MAAMC;IACF,YACqBC,SAAoB,EACpBC,aAA+C,CAClE;aAFmBD,SAAS,GAATA;aACAC,aAAa,GAAbA;IAClB;IAEH,MAAa,OAAOC,OAAiC,EAAEC,QAAmC,EAAE;QACxF,IAAID,QAAQ,OAAO,CAACJ,gBAAgB,KAAK,IAAI,CAAC,aAAa,CAAC,KAAK,EAC7D,OAAOK,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAAa;QAG3D,IAAI;YACA,IAAIC,SAA6BF,QAAQ,IAAI,EAAE;YAC/C,IAAI,CAACE,QAAQ;gBACT,MAAMC,aAAa,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACC,sBAAsB,OAAO;gBAC7E,IAAID,WAAW,MAAM,IACjB,OAAOF,SAAS,IAAI,CAAC;oBAAE,QAAQ;oBAAM,WAAW;gBAAE;gBAEtDC,SAASC,WAAW,KAAK,CAAC,EAAE;YAChC;YAEA,IAAI,CAAC,SAAS,CAAC,OAAO,CAACE,aAAa,GAAG,CAACH;YACxC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACI,qBAAqB,SAAS;YAM3D,MAAMC,aAAa,MAAM,IAAI,CAAC,SAAS,CAClC,OAAO,CAACC,iBACR,oBAAoB,CAAC,IAClB,IAAI,CAAC,SAAS,CACT,OAAO,CAACC,6BACR,OAAO,CAAC;oBAAE,OAAO,CAAC;oBAAG,OAAO;gBAAK;YAG9C,IAAIF,WAAW,MAAM,IACjB,OAAON,SACF,MAAM,CAAC,KACP,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAE,SAASM,WAAW,KAAK,CAAC,OAAO;gBAAC;YAAE;YAG9E,MAAMG,UAAUH,WAAW,KAAK,CAAC,KAAK,CAAC,GAAG,CAACI,CAAAA,SAAW;oBAClD,IAAIA,OAAO,EAAE;oBACb,WAAWA,OAAO,SAAS;oBAC3B,QAAQT;oBACR,cAAcS,OAAO,YAAY;gBACrC;YAEA,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACC,oBAAoB,OAAO,CAACF;YAEzD,OAAOT,SAAS,IAAI,CAAC;gBAAE,QAAQ;gBAAM,WAAWS,QAAQ,MAAM;YAAC;QACnE,EAAE,OAAOG,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DI,QAAQ,KAAK,CAAC,CAAC,sCAAsC,EAAEH,SAAS;YAChE,OAAOb,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAEa;gBAAQ;YAAE;QAC3E;IACJ;AACJ;AAEO,MAAMI,8BAA8BC,iBAAiB,oBAAoB,CAAC;IAC7E,gBAAgBtB;IAChB,cAAc;QAACuB;QAAkBC;KAAuB;AAC5D;AAEA,MAAMC;;aACO,IAAI,GAAG;aACP,MAAM,GAAG;aACT,IAAI,GAAG;aACP,OAAO,GAAGJ;;AACvB;AAGO,MAAMK,wCAAwCC,oBAAoB,oBAAoB,CAAC;IAC1F,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler } from "@webiny/event-handler-core";
|
|
2
|
+
import type { Container } from "@webiny/feature/api";
|
|
3
|
+
import { SchedulerInternalToken } from "./abstractions/InternalToken.js";
|
|
4
|
+
/**
|
|
5
|
+
* Runs a single scheduled action. The in-process Bree singleton (root) POSTs here when a timer fires —
|
|
6
|
+
* the timer fires OUTSIDE any HTTP request, so this route rebuilds the full request context (tenant +
|
|
7
|
+
* CMS models via the request-context initializers + contextual schemas) for the action's tenant, then
|
|
8
|
+
* runs ExecuteScheduledActionUseCase. Mirrors the background-task run route.
|
|
9
|
+
*/
|
|
10
|
+
declare class ScheduledActionRunRouteImpl implements HttpRouteHandler.Interface {
|
|
11
|
+
private readonly container;
|
|
12
|
+
private readonly internalToken;
|
|
13
|
+
constructor(container: Container, internalToken: SchedulerInternalToken.Interface);
|
|
14
|
+
handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response): Promise<import("@webiny/event-handler-core").IHttpResponseBuilder>;
|
|
15
|
+
}
|
|
16
|
+
export declare const ScheduledActionRunRoute: typeof ScheduledActionRunRouteImpl & {
|
|
17
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
18
|
+
};
|
|
19
|
+
declare class ScheduledActionRunRouteDefinitionImpl implements HttpRouteDefinition.Interface {
|
|
20
|
+
readonly name = "scheduled-action-run";
|
|
21
|
+
readonly method = "POST";
|
|
22
|
+
readonly path = "/scheduled-action-run";
|
|
23
|
+
readonly handler: typeof ScheduledActionRunRouteImpl & {
|
|
24
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** What the router matches on. Zero dependencies, so building it costs nothing. */
|
|
28
|
+
export declare const ScheduledActionRunRouteDefinition: typeof ScheduledActionRunRouteDefinitionImpl & {
|
|
29
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRouteDefinition>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler, RequestContainer } from "@webiny/event-handler-core";
|
|
2
|
+
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
3
|
+
import { ExecuteScheduledActionUseCase } from "@webiny/api-scheduler/features/ExecuteScheduledAction/index.js";
|
|
4
|
+
import { SchedulerInternalToken } from "./abstractions/InternalToken.js";
|
|
5
|
+
const INTERNAL_HEADER = "x-webiny-scheduler-token";
|
|
6
|
+
class ScheduledActionRunRouteImpl {
|
|
7
|
+
constructor(container, internalToken){
|
|
8
|
+
this.container = container;
|
|
9
|
+
this.internalToken = internalToken;
|
|
10
|
+
}
|
|
11
|
+
async handle(request, response) {
|
|
12
|
+
if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) return response.status(403).json({
|
|
13
|
+
error: "Forbidden."
|
|
14
|
+
});
|
|
15
|
+
const { id, namespace, tenant } = request.body ?? {};
|
|
16
|
+
if (!id || !namespace || !tenant) return response.status(400).json({
|
|
17
|
+
error: "Missing id, namespace or tenant."
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
this.container.resolve(RawTenantId).set(tenant);
|
|
21
|
+
await this.container.resolve(RequestTenantLoader).establish();
|
|
22
|
+
const result = await this.container.resolve(ExecuteScheduledActionUseCase).execute({
|
|
23
|
+
id,
|
|
24
|
+
namespace,
|
|
25
|
+
tenant
|
|
26
|
+
});
|
|
27
|
+
if (result.isFail()) return response.status(500).json({
|
|
28
|
+
status: "error",
|
|
29
|
+
error: {
|
|
30
|
+
message: result.error.message
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return response.json({
|
|
34
|
+
status: "ok"
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(`Scheduled action run route error: ${message}`);
|
|
39
|
+
return response.status(500).json({
|
|
40
|
+
status: "error",
|
|
41
|
+
error: {
|
|
42
|
+
message
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const ScheduledActionRunRoute = HttpRouteHandler.createImplementation({
|
|
49
|
+
implementation: ScheduledActionRunRouteImpl,
|
|
50
|
+
dependencies: [
|
|
51
|
+
RequestContainer,
|
|
52
|
+
SchedulerInternalToken
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
class ScheduledActionRunRouteDefinitionImpl {
|
|
56
|
+
constructor(){
|
|
57
|
+
this.name = "scheduled-action-run";
|
|
58
|
+
this.method = "POST";
|
|
59
|
+
this.path = "/scheduled-action-run";
|
|
60
|
+
this.handler = ScheduledActionRunRoute;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const ScheduledActionRunRouteDefinition = HttpRouteDefinition.createImplementation({
|
|
64
|
+
implementation: ScheduledActionRunRouteDefinitionImpl,
|
|
65
|
+
dependencies: []
|
|
66
|
+
});
|
|
67
|
+
export { ScheduledActionRunRoute, ScheduledActionRunRouteDefinition };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=ScheduledActionRunRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler/ScheduledActionRunRoute.js","sources":["../../src/scheduler/ScheduledActionRunRoute.ts"],"sourcesContent":["import {\n HttpRouteDefinition,\n HttpRouteHandler,\n RequestContainer\n} from \"@webiny/event-handler-core\";\nimport {\n RawTenantId,\n RequestTenantLoader\n} from \"@webiny/api-core/features/requestContext/index.js\";\nimport { ExecuteScheduledActionUseCase } from \"@webiny/api-scheduler/features/ExecuteScheduledAction/index.js\";\nimport type { Container } from \"@webiny/feature/api\";\nimport { SchedulerInternalToken } from \"./abstractions/InternalToken.js\";\n\n/* Shared between the Bree singleton and this route to gate access. */\nconst INTERNAL_HEADER = \"x-webiny-scheduler-token\";\n\n/**\n * Runs a single scheduled action. The in-process Bree singleton (root) POSTs here when a timer fires —\n * the timer fires OUTSIDE any HTTP request, so this route rebuilds the full request context (tenant +\n * CMS models via the request-context initializers + contextual schemas) for the action's tenant, then\n * runs ExecuteScheduledActionUseCase. Mirrors the background-task run route.\n */\nclass ScheduledActionRunRouteImpl implements HttpRouteHandler.Interface {\n public constructor(\n private readonly container: Container,\n private readonly internalToken: SchedulerInternalToken.Interface\n ) {}\n\n public async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {\n if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) {\n return response.status(403).json({ error: \"Forbidden.\" });\n }\n\n const { id, namespace, tenant } = request.body ?? {};\n if (!id || !namespace || !tenant) {\n return response.status(400).json({ error: \"Missing id, namespace or tenant.\" });\n }\n\n try {\n this.container.resolve(RawTenantId).set(tenant);\n await this.container.resolve(RequestTenantLoader).establish();\n\n const result = await this.container\n .resolve(ExecuteScheduledActionUseCase)\n .execute({ id, namespace, tenant });\n\n if (result.isFail()) {\n return response\n .status(500)\n .json({ status: \"error\", error: { message: result.error.message } });\n }\n\n return response.json({ status: \"ok\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Scheduled action run route error: ${message}`);\n return response.status(500).json({ status: \"error\", error: { message } });\n }\n }\n}\n\nexport const ScheduledActionRunRoute = HttpRouteHandler.createImplementation({\n implementation: ScheduledActionRunRouteImpl,\n dependencies: [RequestContainer, SchedulerInternalToken]\n});\n\nclass ScheduledActionRunRouteDefinitionImpl implements HttpRouteDefinition.Interface {\n readonly name = \"scheduled-action-run\";\n readonly method = \"POST\";\n readonly path = \"/scheduled-action-run\";\n readonly handler = ScheduledActionRunRoute;\n}\n\n/** What the router matches on. Zero dependencies, so building it costs nothing. */\nexport const ScheduledActionRunRouteDefinition = HttpRouteDefinition.createImplementation({\n implementation: ScheduledActionRunRouteDefinitionImpl,\n dependencies: []\n});\n"],"names":["INTERNAL_HEADER","ScheduledActionRunRouteImpl","container","internalToken","request","response","id","namespace","tenant","RawTenantId","RequestTenantLoader","result","ExecuteScheduledActionUseCase","err","message","Error","String","console","ScheduledActionRunRoute","HttpRouteHandler","RequestContainer","SchedulerInternalToken","ScheduledActionRunRouteDefinitionImpl","ScheduledActionRunRouteDefinition","HttpRouteDefinition"],"mappings":";;;;AAcA,MAAMA,kBAAkB;AAQxB,MAAMC;IACF,YACqBC,SAAoB,EACpBC,aAA+C,CAClE;aAFmBD,SAAS,GAATA;aACAC,aAAa,GAAbA;IAClB;IAEH,MAAa,OAAOC,OAAiC,EAAEC,QAAmC,EAAE;QACxF,IAAID,QAAQ,OAAO,CAACJ,gBAAgB,KAAK,IAAI,CAAC,aAAa,CAAC,KAAK,EAC7D,OAAOK,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAAa;QAG3D,MAAM,EAAEC,EAAE,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGJ,QAAQ,IAAI,IAAI,CAAC;QACnD,IAAI,CAACE,MAAM,CAACC,aAAa,CAACC,QACtB,OAAOH,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAAmC;QAGjF,IAAI;YACA,IAAI,CAAC,SAAS,CAAC,OAAO,CAACI,aAAa,GAAG,CAACD;YACxC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACE,qBAAqB,SAAS;YAE3D,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAC9B,OAAO,CAACC,+BACR,OAAO,CAAC;gBAAEN;gBAAIC;gBAAWC;YAAO;YAErC,IAAIG,OAAO,MAAM,IACb,OAAON,SACF,MAAM,CAAC,KACP,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAE,SAASM,OAAO,KAAK,CAAC,OAAO;gBAAC;YAAE;YAG1E,OAAON,SAAS,IAAI,CAAC;gBAAE,QAAQ;YAAK;QACxC,EAAE,OAAOQ,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DI,QAAQ,KAAK,CAAC,CAAC,kCAAkC,EAAEH,SAAS;YAC5D,OAAOT,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAES;gBAAQ;YAAE;QAC3E;IACJ;AACJ;AAEO,MAAMI,0BAA0BC,iBAAiB,oBAAoB,CAAC;IACzE,gBAAgBlB;IAChB,cAAc;QAACmB;QAAkBC;KAAuB;AAC5D;AAEA,MAAMC;;aACO,IAAI,GAAG;aACP,MAAM,GAAG;aACT,IAAI,GAAG;aACP,OAAO,GAAGJ;;AACvB;AAGO,MAAMK,oCAAoCC,oBAAoB,oBAAoB,CAAC;IACtF,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process token generated at startup, shared between the in-process Bree scheduler singleton and
|
|
3
|
+
* the scheduler routes via DI. The singleton sends it as a header when a timer fires; the routes
|
|
4
|
+
* reject requests without a matching value. Single-process only — for multi-server, replace with a
|
|
5
|
+
* shared secret. Mirrors the background-tasks InternalToken.
|
|
6
|
+
*/
|
|
7
|
+
interface IInternalToken {
|
|
8
|
+
readonly value: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const SchedulerInternalToken: import("@webiny/di").Abstraction<IInternalToken>;
|
|
11
|
+
export declare namespace SchedulerInternalToken {
|
|
12
|
+
type Interface = IInternalToken;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler/abstractions/InternalToken.js","sources":["../../../src/scheduler/abstractions/InternalToken.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\n\n/**\n * Per-process token generated at startup, shared between the in-process Bree scheduler singleton and\n * the scheduler routes via DI. The singleton sends it as a header when a timer fires; the routes\n * reject requests without a matching value. Single-process only — for multi-server, replace with a\n * shared secret. Mirrors the background-tasks InternalToken.\n */\ninterface IInternalToken {\n readonly value: string;\n}\n\nexport const SchedulerInternalToken = createAbstraction<IInternalToken>(\n \"SchedulerServer/InternalToken\"\n);\n\nexport namespace SchedulerInternalToken {\n export type Interface = IInternalToken;\n}\n"],"names":["SchedulerInternalToken","createAbstraction"],"mappings":";AAYO,MAAMA,yBAAyBC,kBAClC"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { Container } from "@webiny/di";
|
|
2
2
|
/**
|
|
3
3
|
* ROOT wiring for the standalone (Bree, in-process) scheduler. Unlike AWS (per-request EventBridge
|
|
4
|
-
* binding), the server holds ONE long-lived Bree instance for all tenants, started once at boot —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* binding), the server holds ONE long-lived Bree instance for all tenants, started once at boot — the
|
|
5
|
+
* counterpart of the WebSockets connection manager. Registered as `SchedulerService` so per-request
|
|
6
|
+
* create/update/delete (during GraphQL mutations) manipulate that single live timer set.
|
|
7
7
|
*
|
|
8
|
-
* When a timer fires (outside any request), the singleton
|
|
9
|
-
*
|
|
8
|
+
* When a timer fires (outside any request), the singleton POSTs to `/scheduled-action-run`, which
|
|
9
|
+
* rebuilds the request context for the action's tenant and executes it (see the route).
|
|
10
10
|
*/
|
|
11
11
|
export declare function registerSchedulerServer(rootContainer: Container): void;
|
|
12
12
|
/**
|
|
13
|
-
* Boot step (onServer): start the timers, then re-arm
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Boot step (onServer): start the timers, then re-arm persisted schedules by POSTing the recover route
|
|
14
|
+
* (root tenant).
|
|
15
|
+
*
|
|
16
|
+
* `onServer` runs BEFORE the HTTP server starts listening, so the recover POST (which hits this same
|
|
17
|
+
* server) is DEFERRED to the next tick — by then `createServerHandler` has returned and the runner has
|
|
18
|
+
* called `.listen()`. Fire-and-forget: a recovery failure must never block or crash startup.
|
|
16
19
|
*/
|
|
17
20
|
export declare function startSchedulerServer(rootContainer: Container): Promise<void>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { uuid } from "@webiny/stdlib";
|
|
2
2
|
import { SchedulerService } from "@webiny/api-scheduler/shared/abstractions.js";
|
|
3
|
-
import { ScheduledActionLambdaHandler } from "@webiny/api-scheduler";
|
|
4
3
|
import { BreeSchedulerService } from "@webiny/api-scheduler-standalone";
|
|
4
|
+
import { SchedulerInternalToken } from "./abstractions/InternalToken.js";
|
|
5
5
|
import { SchedulerSingleton } from "./abstractions/SchedulerSingleton.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ScheduledActionRunRouteDefinition } from "./ScheduledActionRunRoute.js";
|
|
7
|
+
import { ScheduledActionRecoverRouteDefinition } from "./ScheduledActionRecoverRoute.js";
|
|
8
|
+
const SCHEDULER_HEADER = "x-webiny-scheduler-token";
|
|
8
9
|
const consoleLogger = {
|
|
9
10
|
trace: (...args)=>console.trace(...args),
|
|
10
11
|
debug: (...args)=>console.debug(...args),
|
|
@@ -14,48 +15,66 @@ const consoleLogger = {
|
|
|
14
15
|
fatal: (...args)=>console.error(...args),
|
|
15
16
|
log: (...args)=>console.log(...args)
|
|
16
17
|
};
|
|
18
|
+
const serverBase = ()=>`http://localhost:${process.env.PORT || "3002"}`;
|
|
17
19
|
function registerSchedulerServer(rootContainer) {
|
|
18
|
-
const
|
|
20
|
+
const token = uuid();
|
|
21
|
+
rootContainer.registerInstance(SchedulerInternalToken, {
|
|
22
|
+
value: token
|
|
23
|
+
});
|
|
19
24
|
const service = new BreeSchedulerService({
|
|
20
25
|
logger: consoleLogger,
|
|
21
26
|
onTrigger: async (id, namespace, tenant)=>{
|
|
22
|
-
|
|
23
|
-
[SCHEDULED_ACTION_EVENT_IDENTIFIER]: {
|
|
24
|
-
id,
|
|
25
|
-
namespace,
|
|
26
|
-
tenant,
|
|
27
|
-
scheduleFor: new Date().toISOString()
|
|
28
|
-
}
|
|
29
|
-
};
|
|
27
|
+
console.log(`[scheduler] timer fired for "${id}" (namespace=${namespace}, tenant=${tenant}); calling run route`);
|
|
30
28
|
try {
|
|
31
|
-
await
|
|
29
|
+
const res = await fetch(`${serverBase()}/scheduled-action-run`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
[SCHEDULER_HEADER]: token
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
id,
|
|
37
|
+
namespace,
|
|
38
|
+
tenant
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const body = await res.text().catch(()=>"");
|
|
43
|
+
console.error(`[scheduler] run route returned HTTP ${res.status} for "${id}": ${body}`);
|
|
44
|
+
}
|
|
32
45
|
} catch (err) {
|
|
33
|
-
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
console.error(`Scheduler trigger for "${id}" failed to reach the run route: ${message}`);
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
});
|
|
37
51
|
rootContainer.registerInstance(SchedulerSingleton, service);
|
|
38
52
|
rootContainer.registerInstance(SchedulerService, service);
|
|
39
|
-
rootContainer.register(
|
|
40
|
-
rootContainer.register(
|
|
41
|
-
rootContainer.register(ScheduledActionRecoverEventType);
|
|
42
|
-
rootContainer.register(RecoverScheduledActionsHandler);
|
|
43
|
-
}
|
|
44
|
-
async function recoverPendingSchedules(dispatcher) {
|
|
45
|
-
const event = {
|
|
46
|
-
[SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER]: true
|
|
47
|
-
};
|
|
48
|
-
try {
|
|
49
|
-
const result = await dispatcher.dispatch(event);
|
|
50
|
-
console.log(`[scheduler] boot recovery: re-armed ${result.recovered} pending action(s)`);
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.error("[scheduler] boot recovery failed:", err);
|
|
53
|
-
}
|
|
53
|
+
rootContainer.register(ScheduledActionRunRouteDefinition);
|
|
54
|
+
rootContainer.register(ScheduledActionRecoverRouteDefinition);
|
|
54
55
|
}
|
|
55
56
|
async function startSchedulerServer(rootContainer) {
|
|
56
57
|
await rootContainer.resolve(SchedulerSingleton).start();
|
|
57
|
-
const
|
|
58
|
-
|
|
58
|
+
const token = rootContainer.resolve(SchedulerInternalToken).value;
|
|
59
|
+
console.log(`[scheduler] boot: re-arming persisted schedules via ${serverBase()} ...`);
|
|
60
|
+
setTimeout(async ()=>{
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(`${serverBase()}/scheduled-action-recover`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"content-type": "application/json",
|
|
66
|
+
[SCHEDULER_HEADER]: token
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({})
|
|
69
|
+
});
|
|
70
|
+
const body = await res.json().catch(()=>({}));
|
|
71
|
+
if (!res.ok) return void console.error(`[scheduler] boot recovery failed: HTTP ${res.status}`, body);
|
|
72
|
+
console.log(`[scheduler] boot recovery: re-armed ${body.recovered ?? "?"} pending action(s)`);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
console.error(`[scheduler] boot recovery failed to reach the recover route: ${message}`);
|
|
76
|
+
}
|
|
77
|
+
}, 1000);
|
|
59
78
|
}
|
|
60
79
|
export { registerSchedulerServer, startSchedulerServer };
|
|
61
80
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler/schedulerServer.js","sources":["../../src/scheduler/schedulerServer.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { EventDispatcher } from \"@webiny/event-handler-core\";\nimport { ScheduledActionEventType } from \"@webiny/event-handler-core\";\nimport { SCHEDULED_ACTION_EVENT_IDENTIFIER } from \"@webiny/event-handler-core\";\nimport type { IScheduledActionEvent } from \"@webiny/event-handler-core\";\nimport { SchedulerService } from \"@webiny/api-scheduler/shared/abstractions.js\";\nimport { ScheduledActionLambdaHandler } from \"@webiny/api-scheduler\";\nimport { BreeSchedulerService } from \"@webiny/api-scheduler-standalone\";\nimport type { Logger } from \"@webiny/api-core/features/logger/abstractions.js\";\nimport { SchedulerSingleton } from \"./abstractions/SchedulerSingleton.js\";\nimport { ScheduledActionRecoverEventType } from \"./ScheduledActionRecoverEventType.js\";\nimport { SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER } from \"./ScheduledActionRecoverEventType.js\";\nimport type { IScheduledActionRecoverEvent } from \"./ScheduledActionRecoverEventType.js\";\nimport { RecoverScheduledActionsHandler } from \"./RecoverScheduledActionsHandler.js\";\nimport { ScheduledActionRecoverEventHandler } from \"./ScheduledActionRecoverEventHandler.js\";\n\n/**\n * Minimal console-backed logger for the root scheduler singleton. The real DI Logger is registered\n * per-request (via ApiCoreFeature/LoggerFeature), so it isn't resolvable here at the root where the\n * singleton is built. And even if we bridged to it, timers fire outside any request — there's no\n * request context for a context-aware logger to enrich — so console is functionally equivalent, not\n * a downgrade. Revisit (a shared boot-time logger for all root singletons) tracked in\n * https://github.com/webiny/webiny-js/issues/5446.\n */\nconst consoleLogger: Logger.Interface = {\n trace: (...args: any[]) => console.trace(...args),\n debug: (...args: any[]) => console.debug(...args),\n info: (...args: any[]) => console.info(...args),\n warn: (...args: any[]) => console.warn(...args),\n error: (...args: any[]) => console.error(...args),\n fatal: (...args: any[]) => console.error(...args),\n log: (...args: any[]) => console.log(...args)\n};\n\n/**\n * ROOT wiring for the standalone (Bree, in-process) scheduler. Unlike AWS (per-request EventBridge\n * binding), the server holds ONE long-lived Bree instance for all tenants, started once at boot —\n * the counterpart of the WebSockets connection manager. Registered as `SchedulerService` so\n * per-request create/update/delete (during GraphQL mutations) manipulate that single live timer set.\n *\n * When a timer fires (outside any request), the singleton dispatches a `ScheduledActionEvent`. The\n * handler is the one AWS runs for an EventBridge Scheduler invocation, in a fresh request container.\n */\nexport function registerSchedulerServer(rootContainer: Container): void {\n const dispatcher = rootContainer.resolve(EventDispatcher);\n\n const service = new BreeSchedulerService({\n logger: consoleLogger,\n onTrigger: async (id, namespace, tenant) => {\n /*\n * `scheduleFor` is the time it fired. The event type needs one to recognise the event,\n * and nothing downstream reads it.\n */\n const event: IScheduledActionEvent = {\n [SCHEDULED_ACTION_EVENT_IDENTIFIER]: {\n id,\n namespace,\n tenant,\n scheduleFor: new Date().toISOString()\n }\n };\n try {\n await dispatcher.dispatch(event);\n } catch (err) {\n console.error(`[scheduler] scheduled action \"${id}\" failed:`, err);\n }\n }\n });\n\n // Register the ONE Bree instance under TWO tokens — two views of the same object:\n //\n // - SchedulerService: the hosting-agnostic contract (create/update/delete/exists) that AWS\n // implements too. This is what per-request GraphQL mutations resolve to schedule/reschedule.\n // - SchedulerSingleton: typed as the CONCRETE BreeSchedulerService, so it also exposes the\n // methods that aren't on that contract — start() and recover(). Those are Bree-only: AWS's\n // EventBridge is managed infra (nothing to start) and persists its own schedules (nothing to\n // recover), so they don't belong on the shared interface. The boot step + recover handler\n // resolve this token precisely because they need start()/recover().\n //\n // registerInstance (not register) because it's a single live object holding all tenants' timers —\n // every caller must get the SAME instance, not a per-scope construction. Registered at root, so\n // per-request child containers inherit it via the parent chain; nothing registers a per-request\n // SchedulerService default that would shadow it (unlike e.g. the WS transport's Null default).\n rootContainer.registerInstance(SchedulerSingleton, service);\n rootContainer.registerInstance(SchedulerService, service);\n\n rootContainer.register(ScheduledActionEventType);\n rootContainer.register(ScheduledActionLambdaHandler);\n\n rootContainer.register(ScheduledActionRecoverEventType);\n rootContainer.register(RecoverScheduledActionsHandler);\n}\n\nasync function recoverPendingSchedules(dispatcher: EventDispatcher.Interface): Promise<void> {\n const event: IScheduledActionRecoverEvent = {\n [SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER]: true\n };\n\n try {\n const result = await dispatcher.dispatch<ScheduledActionRecoverEventHandler.Result>(event);\n console.log(`[scheduler] boot recovery: re-armed ${result.recovered} pending action(s)`);\n } catch (err) {\n console.error(\"[scheduler] boot recovery failed:\", err);\n }\n}\n\n/**\n * Boot step (onServer): start the timers, then re-arm the persisted schedules. Recovery runs in the\n * background: it executes overdue actions one by one, and the server should not wait for that\n * before it starts listening. A failure is logged and never crashes startup.\n */\nexport async function startSchedulerServer(rootContainer: Container): Promise<void> {\n await rootContainer.resolve(SchedulerSingleton).start();\n\n const dispatcher = rootContainer.resolve(EventDispatcher);\n void recoverPendingSchedules(dispatcher);\n}\n"],"names":["consoleLogger","args","console","registerSchedulerServer","rootContainer","dispatcher","EventDispatcher","service","BreeSchedulerService","id","namespace","tenant","event","SCHEDULED_ACTION_EVENT_IDENTIFIER","Date","err","SchedulerSingleton","SchedulerService","ScheduledActionEventType","ScheduledActionLambdaHandler","ScheduledActionRecoverEventType","RecoverScheduledActionsHandler","recoverPendingSchedules","SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER","result","startSchedulerServer"],"mappings":";;;;;;;AAwBA,MAAMA,gBAAkC;IACpC,OAAO,CAAC,GAAGC,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,MAAM,CAAC,GAAGA,OAAgBC,QAAQ,IAAI,IAAID;IAC1C,MAAM,CAAC,GAAGA,OAAgBC,QAAQ,IAAI,IAAID;IAC1C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,KAAK,CAAC,GAAGA,OAAgBC,QAAQ,GAAG,IAAID;AAC5C;AAWO,SAASE,wBAAwBC,aAAwB;IAC5D,MAAMC,aAAaD,cAAc,OAAO,CAACE;IAEzC,MAAMC,UAAU,IAAIC,qBAAqB;QACrC,QAAQR;QACR,WAAW,OAAOS,IAAIC,WAAWC;YAK7B,MAAMC,QAA+B;gBACjC,CAACC,kCAAkC,EAAE;oBACjCJ;oBACAC;oBACAC;oBACA,aAAa,IAAIG,OAAO,WAAW;gBACvC;YACJ;YACA,IAAI;gBACA,MAAMT,WAAW,QAAQ,CAACO;YAC9B,EAAE,OAAOG,KAAK;gBACVb,QAAQ,KAAK,CAAC,CAAC,8BAA8B,EAAEO,GAAG,SAAS,CAAC,EAAEM;YAClE;QACJ;IACJ;IAgBAX,cAAc,gBAAgB,CAACY,oBAAoBT;IACnDH,cAAc,gBAAgB,CAACa,kBAAkBV;IAEjDH,cAAc,QAAQ,CAACc;IACvBd,cAAc,QAAQ,CAACe;IAEvBf,cAAc,QAAQ,CAACgB;IACvBhB,cAAc,QAAQ,CAACiB;AAC3B;AAEA,eAAeC,wBAAwBjB,UAAqC;IACxE,MAAMO,QAAsC;QACxC,CAACW,0CAA0C,EAAE;IACjD;IAEA,IAAI;QACA,MAAMC,SAAS,MAAMnB,WAAW,QAAQ,CAA4CO;QACpFV,QAAQ,GAAG,CAAC,CAAC,oCAAoC,EAAEsB,OAAO,SAAS,CAAC,kBAAkB,CAAC;IAC3F,EAAE,OAAOT,KAAK;QACVb,QAAQ,KAAK,CAAC,qCAAqCa;IACvD;AACJ;AAOO,eAAeU,qBAAqBrB,aAAwB;IAC/D,MAAMA,cAAc,OAAO,CAACY,oBAAoB,KAAK;IAErD,MAAMX,aAAaD,cAAc,OAAO,CAACE;IACpCgB,wBAAwBjB;AACjC"}
|
|
1
|
+
{"version":3,"file":"scheduler/schedulerServer.js","sources":["../../src/scheduler/schedulerServer.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { uuid } from \"@webiny/stdlib\";\nimport { SchedulerService } from \"@webiny/api-scheduler/shared/abstractions.js\";\nimport { BreeSchedulerService } from \"@webiny/api-scheduler-standalone\";\nimport type { Logger } from \"@webiny/api-core/features/logger/abstractions.js\";\nimport { SchedulerInternalToken } from \"./abstractions/InternalToken.js\";\nimport { SchedulerSingleton } from \"./abstractions/SchedulerSingleton.js\";\nimport { ScheduledActionRunRouteDefinition } from \"./ScheduledActionRunRoute.js\";\nimport { ScheduledActionRecoverRouteDefinition } from \"./ScheduledActionRecoverRoute.js\";\n\nconst SCHEDULER_HEADER = \"x-webiny-scheduler-token\";\n\n/**\n * Minimal console-backed logger for the root scheduler singleton. The real DI Logger is registered\n * per-request (via ApiCoreFeature/LoggerFeature), so it isn't resolvable here at the root where the\n * singleton is built. And even if we bridged to it, timers fire outside any request — there's no\n * request context for a context-aware logger to enrich — so console is functionally equivalent, not\n * a downgrade. Revisit (a shared boot-time logger for all root singletons) tracked in\n * https://github.com/webiny/webiny-js/issues/5446.\n */\nconst consoleLogger: Logger.Interface = {\n trace: (...args: any[]) => console.trace(...args),\n debug: (...args: any[]) => console.debug(...args),\n info: (...args: any[]) => console.info(...args),\n warn: (...args: any[]) => console.warn(...args),\n error: (...args: any[]) => console.error(...args),\n fatal: (...args: any[]) => console.error(...args),\n log: (...args: any[]) => console.log(...args)\n};\n\n// Self-callback base. process.env.PORT is the resolved listening port injected by spawnApiServer;\n// the 3002 fallback matches findFreePort's search base (and the background-tasks default) so every\n// self-callback agrees, though in practice PORT is always set. A shared helper + fail-loud handling\n// is tracked in https://github.com/webiny/webiny-js/issues/5448.\nconst serverBase = () => `http://localhost:${process.env.PORT || \"3002\"}`;\n\n/**\n * ROOT wiring for the standalone (Bree, in-process) scheduler. Unlike AWS (per-request EventBridge\n * binding), the server holds ONE long-lived Bree instance for all tenants, started once at boot — the\n * counterpart of the WebSockets connection manager. Registered as `SchedulerService` so per-request\n * create/update/delete (during GraphQL mutations) manipulate that single live timer set.\n *\n * When a timer fires (outside any request), the singleton POSTs to `/scheduled-action-run`, which\n * rebuilds the request context for the action's tenant and executes it (see the route).\n */\nexport function registerSchedulerServer(rootContainer: Container): void {\n const token = uuid();\n rootContainer.registerInstance(SchedulerInternalToken, { value: token });\n\n const service = new BreeSchedulerService({\n logger: consoleLogger,\n onTrigger: async (id, namespace, tenant) => {\n console.log(\n `[scheduler] timer fired for \"${id}\" (namespace=${namespace}, tenant=${tenant}); calling run route`\n );\n try {\n const res = await fetch(`${serverBase()}/scheduled-action-run`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", [SCHEDULER_HEADER]: token },\n body: JSON.stringify({ id, namespace, tenant })\n });\n // fetch only throws on network errors; a 403 or 500 comes back as a normal non-ok\n // response, so we check for that explicitly — otherwise a failed run would be silent.\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n console.error(\n `[scheduler] run route returned HTTP ${res.status} for \"${id}\": ${body}`\n );\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `Scheduler trigger for \"${id}\" failed to reach the run route: ${message}`\n );\n }\n }\n });\n\n // Register the ONE Bree instance under TWO tokens — two views of the same object:\n //\n // - SchedulerService: the hosting-agnostic contract (create/update/delete/exists) that AWS\n // implements too. This is what per-request GraphQL mutations resolve to schedule/reschedule.\n // - SchedulerSingleton: typed as the CONCRETE BreeSchedulerService, so it also exposes the\n // methods that aren't on that contract — start() and recover(). Those are Bree-only: AWS's\n // EventBridge is managed infra (nothing to start) and persists its own schedules (nothing to\n // recover), so they don't belong on the shared interface. The boot step + recover route resolve\n // this token precisely because they need start()/recover().\n //\n // registerInstance (not register) because it's a single live object holding all tenants' timers —\n // every caller must get the SAME instance, not a per-scope construction. Registered at root, so\n // per-request child containers inherit it via the parent chain; nothing registers a per-request\n // SchedulerService default that would shadow it (unlike e.g. the WS transport's Null default).\n rootContainer.registerInstance(SchedulerSingleton, service);\n rootContainer.registerInstance(SchedulerService, service);\n\n rootContainer.register(ScheduledActionRunRouteDefinition);\n rootContainer.register(ScheduledActionRecoverRouteDefinition);\n}\n\n/**\n * Boot step (onServer): start the timers, then re-arm persisted schedules by POSTing the recover route\n * (root tenant).\n *\n * `onServer` runs BEFORE the HTTP server starts listening, so the recover POST (which hits this same\n * server) is DEFERRED to the next tick — by then `createServerHandler` has returned and the runner has\n * called `.listen()`. Fire-and-forget: a recovery failure must never block or crash startup.\n */\nexport async function startSchedulerServer(rootContainer: Container): Promise<void> {\n await rootContainer.resolve(SchedulerSingleton).start();\n\n const token = rootContainer.resolve(SchedulerInternalToken).value;\n\n // Deferred: give the runner a moment to bind the listener before we call back into it.\n console.log(`[scheduler] boot: re-arming persisted schedules via ${serverBase()} ...`);\n setTimeout(async () => {\n try {\n const res = await fetch(`${serverBase()}/scheduled-action-recover`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", [SCHEDULER_HEADER]: token },\n body: JSON.stringify({})\n });\n const body = await res.json().catch(() => ({}) as Record<string, unknown>);\n // fetch only throws on network errors; a 403 or 500 comes back as a normal non-ok\n // response, so we check for it here (a token-mismatch 403 used to fail silently).\n if (!res.ok) {\n console.error(`[scheduler] boot recovery failed: HTTP ${res.status}`, body);\n return;\n }\n console.log(\n `[scheduler] boot recovery: re-armed ${body.recovered ?? \"?\"} pending action(s)`\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[scheduler] boot recovery failed to reach the recover route: ${message}`\n );\n }\n }, 1000);\n}\n"],"names":["SCHEDULER_HEADER","consoleLogger","args","console","serverBase","process","registerSchedulerServer","rootContainer","token","uuid","SchedulerInternalToken","service","BreeSchedulerService","id","namespace","tenant","res","fetch","JSON","body","err","message","Error","String","SchedulerSingleton","SchedulerService","ScheduledActionRunRouteDefinition","ScheduledActionRecoverRouteDefinition","startSchedulerServer","setTimeout"],"mappings":";;;;;;;AAUA,MAAMA,mBAAmB;AAUzB,MAAMC,gBAAkC;IACpC,OAAO,CAAC,GAAGC,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,MAAM,CAAC,GAAGA,OAAgBC,QAAQ,IAAI,IAAID;IAC1C,MAAM,CAAC,GAAGA,OAAgBC,QAAQ,IAAI,IAAID;IAC1C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,OAAO,CAAC,GAAGA,OAAgBC,QAAQ,KAAK,IAAID;IAC5C,KAAK,CAAC,GAAGA,OAAgBC,QAAQ,GAAG,IAAID;AAC5C;AAMA,MAAME,aAAa,IAAM,CAAC,iBAAiB,EAAEC,QAAQ,GAAG,CAAC,IAAI,IAAI,QAAQ;AAWlE,SAASC,wBAAwBC,aAAwB;IAC5D,MAAMC,QAAQC;IACdF,cAAc,gBAAgB,CAACG,wBAAwB;QAAE,OAAOF;IAAM;IAEtE,MAAMG,UAAU,IAAIC,qBAAqB;QACrC,QAAQX;QACR,WAAW,OAAOY,IAAIC,WAAWC;YAC7BZ,QAAQ,GAAG,CACP,CAAC,6BAA6B,EAAEU,GAAG,aAAa,EAAEC,UAAU,SAAS,EAAEC,OAAO,oBAAoB,CAAC;YAEvG,IAAI;gBACA,MAAMC,MAAM,MAAMC,MAAM,GAAGb,aAAa,qBAAqB,CAAC,EAAE;oBAC5D,QAAQ;oBACR,SAAS;wBAAE,gBAAgB;wBAAoB,CAACJ,iBAAiB,EAAEQ;oBAAM;oBACzE,MAAMU,KAAK,SAAS,CAAC;wBAAEL;wBAAIC;wBAAWC;oBAAO;gBACjD;gBAGA,IAAI,CAACC,IAAI,EAAE,EAAE;oBACT,MAAMG,OAAO,MAAMH,IAAI,IAAI,GAAG,KAAK,CAAC,IAAM;oBAC1Cb,QAAQ,KAAK,CACT,CAAC,oCAAoC,EAAEa,IAAI,MAAM,CAAC,MAAM,EAAEH,GAAG,GAAG,EAAEM,MAAM;gBAEhF;YACJ,EAAE,OAAOC,KAAK;gBACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;gBAC5DjB,QAAQ,KAAK,CACT,CAAC,uBAAuB,EAAEU,GAAG,iCAAiC,EAAEQ,SAAS;YAEjF;QACJ;IACJ;IAgBAd,cAAc,gBAAgB,CAACiB,oBAAoBb;IACnDJ,cAAc,gBAAgB,CAACkB,kBAAkBd;IAEjDJ,cAAc,QAAQ,CAACmB;IACvBnB,cAAc,QAAQ,CAACoB;AAC3B;AAUO,eAAeC,qBAAqBrB,aAAwB;IAC/D,MAAMA,cAAc,OAAO,CAACiB,oBAAoB,KAAK;IAErD,MAAMhB,QAAQD,cAAc,OAAO,CAACG,wBAAwB,KAAK;IAGjEP,QAAQ,GAAG,CAAC,CAAC,oDAAoD,EAAEC,aAAa,IAAI,CAAC;IACrFyB,WAAW;QACP,IAAI;YACA,MAAMb,MAAM,MAAMC,MAAM,GAAGb,aAAa,yBAAyB,CAAC,EAAE;gBAChE,QAAQ;gBACR,SAAS;oBAAE,gBAAgB;oBAAoB,CAACJ,iBAAiB,EAAEQ;gBAAM;gBACzE,MAAMU,KAAK,SAAS,CAAC,CAAC;YAC1B;YACA,MAAMC,OAAO,MAAMH,IAAI,IAAI,GAAG,KAAK,CAAC,IAAO,EAAC;YAG5C,IAAI,CAACA,IAAI,EAAE,EAAE,YACTb,QAAQ,KAAK,CAAC,CAAC,uCAAuC,EAAEa,IAAI,MAAM,EAAE,EAAEG;YAG1EhB,QAAQ,GAAG,CACP,CAAC,oCAAoC,EAAEgB,KAAK,SAAS,IAAI,IAAI,kBAAkB,CAAC;QAExF,EAAE,OAAOC,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DjB,QAAQ,KAAK,CACT,CAAC,6DAA6D,EAAEkB,SAAS;QAEjF;IACJ,GAAG;AACP"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { RawTenantId } from "@webiny/api-core/features/requestContext/index.js";
|
|
2
|
-
import { RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
3
|
-
import { GetRootTenantUseCase } from "@webiny/api-core/features/tenancy/GetRootTenant/index.js";
|
|
4
|
-
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
5
|
-
import { ListScheduledActionsUseCase } from "@webiny/api-scheduler/features/ListScheduledActions/index.js";
|
|
6
|
-
import { ScheduledActionRecoverEventHandler } from "./ScheduledActionRecoverEventHandler.js";
|
|
7
|
-
import { SchedulerSingleton } from "./abstractions/SchedulerSingleton.js";
|
|
8
|
-
/**
|
|
9
|
-
* Re-arms the root tenant's pending scheduled actions in the Bree singleton. Overdue ones run at
|
|
10
|
-
* once (see `BreeSchedulerService.recover`).
|
|
11
|
-
*
|
|
12
|
-
* Root tenant only. Recovering other tenants means enumerating them and recovering each; until
|
|
13
|
-
* then a restart drops their pending schedules.
|
|
14
|
-
*/
|
|
15
|
-
declare class RecoverScheduledActionsHandlerImpl implements ScheduledActionRecoverEventHandler.Interface {
|
|
16
|
-
private readonly getRootTenant;
|
|
17
|
-
private readonly rawTenantId;
|
|
18
|
-
private readonly tenantLoader;
|
|
19
|
-
private readonly identityContext;
|
|
20
|
-
private readonly listScheduledActions;
|
|
21
|
-
private readonly scheduler;
|
|
22
|
-
constructor(getRootTenant: GetRootTenantUseCase.Interface, rawTenantId: RawTenantId.Interface, tenantLoader: RequestTenantLoader.Interface, identityContext: IdentityContext.Interface, listScheduledActions: ListScheduledActionsUseCase.Interface, scheduler: SchedulerSingleton.Interface);
|
|
23
|
-
execute(): Promise<ScheduledActionRecoverEventHandler.Result>;
|
|
24
|
-
private recoverAllPending;
|
|
25
|
-
}
|
|
26
|
-
export declare const RecoverScheduledActionsHandler: typeof RecoverScheduledActionsHandlerImpl & {
|
|
27
|
-
__abstraction: import("@webiny/di").Abstraction<import("./ScheduledActionRecoverEventHandler.js").IScheduledActionRecoverEventHandler>;
|
|
28
|
-
};
|
|
29
|
-
export {};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
2
|
-
import { GetRootTenantUseCase } from "@webiny/api-core/features/tenancy/GetRootTenant/index.js";
|
|
3
|
-
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
4
|
-
import { ListScheduledActionsUseCase } from "@webiny/api-scheduler/features/ListScheduledActions/index.js";
|
|
5
|
-
import { ScheduledActionRecoverEventHandler } from "./ScheduledActionRecoverEventHandler.js";
|
|
6
|
-
import { SchedulerSingleton } from "./abstractions/SchedulerSingleton.js";
|
|
7
|
-
const PAGE_SIZE = 1000;
|
|
8
|
-
class RecoverScheduledActionsHandlerImpl {
|
|
9
|
-
constructor(getRootTenant, rawTenantId, tenantLoader, identityContext, listScheduledActions, scheduler){
|
|
10
|
-
this.getRootTenant = getRootTenant;
|
|
11
|
-
this.rawTenantId = rawTenantId;
|
|
12
|
-
this.tenantLoader = tenantLoader;
|
|
13
|
-
this.identityContext = identityContext;
|
|
14
|
-
this.listScheduledActions = listScheduledActions;
|
|
15
|
-
this.scheduler = scheduler;
|
|
16
|
-
}
|
|
17
|
-
async execute() {
|
|
18
|
-
const rootResult = await this.getRootTenant.execute();
|
|
19
|
-
if (rootResult.isFail()) return {
|
|
20
|
-
recovered: 0
|
|
21
|
-
};
|
|
22
|
-
const tenant = rootResult.value.id;
|
|
23
|
-
this.rawTenantId.set(tenant);
|
|
24
|
-
await this.tenantLoader.establish();
|
|
25
|
-
const recovered = await this.identityContext.withoutAuthorization(()=>this.recoverAllPending(tenant));
|
|
26
|
-
return {
|
|
27
|
-
recovered
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
async recoverAllPending(tenant) {
|
|
31
|
-
let recovered = 0;
|
|
32
|
-
let after;
|
|
33
|
-
do {
|
|
34
|
-
const listResult = await this.listScheduledActions.execute({
|
|
35
|
-
where: {},
|
|
36
|
-
limit: PAGE_SIZE,
|
|
37
|
-
after
|
|
38
|
-
});
|
|
39
|
-
if (listResult.isFail()) throw listResult.error;
|
|
40
|
-
const page = [];
|
|
41
|
-
for (const action of listResult.value.items)page.push({
|
|
42
|
-
id: action.id,
|
|
43
|
-
namespace: action.namespace,
|
|
44
|
-
tenant,
|
|
45
|
-
scheduledFor: action.scheduledFor
|
|
46
|
-
});
|
|
47
|
-
await this.scheduler.recover(page);
|
|
48
|
-
recovered += page.length;
|
|
49
|
-
const { meta } = listResult.value;
|
|
50
|
-
after = meta.hasMoreItems && meta.cursor ? meta.cursor : void 0;
|
|
51
|
-
}while (after)
|
|
52
|
-
return recovered;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const RecoverScheduledActionsHandler = ScheduledActionRecoverEventHandler.createImplementation({
|
|
56
|
-
implementation: RecoverScheduledActionsHandlerImpl,
|
|
57
|
-
dependencies: [
|
|
58
|
-
GetRootTenantUseCase,
|
|
59
|
-
RawTenantId,
|
|
60
|
-
RequestTenantLoader,
|
|
61
|
-
IdentityContext,
|
|
62
|
-
ListScheduledActionsUseCase,
|
|
63
|
-
SchedulerSingleton
|
|
64
|
-
]
|
|
65
|
-
});
|
|
66
|
-
export { RecoverScheduledActionsHandler };
|
|
67
|
-
|
|
68
|
-
//# sourceMappingURL=RecoverScheduledActionsHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler/RecoverScheduledActionsHandler.js","sources":["../../src/scheduler/RecoverScheduledActionsHandler.ts"],"sourcesContent":["import { RawTenantId } from \"@webiny/api-core/features/requestContext/index.js\";\nimport { RequestTenantLoader } from \"@webiny/api-core/features/requestContext/index.js\";\nimport { GetRootTenantUseCase } from \"@webiny/api-core/features/tenancy/GetRootTenant/index.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport { ListScheduledActionsUseCase } from \"@webiny/api-scheduler/features/ListScheduledActions/index.js\";\nimport { ScheduledActionRecoverEventHandler } from \"./ScheduledActionRecoverEventHandler.js\";\nimport type { IPendingAction } from \"@webiny/api-scheduler-standalone\";\nimport { SchedulerSingleton } from \"./abstractions/SchedulerSingleton.js\";\n\n// Pending actions read per page.\nconst PAGE_SIZE = 1000;\n\n/**\n * Re-arms the root tenant's pending scheduled actions in the Bree singleton. Overdue ones run at\n * once (see `BreeSchedulerService.recover`).\n *\n * Root tenant only. Recovering other tenants means enumerating them and recovering each; until\n * then a restart drops their pending schedules.\n */\nclass RecoverScheduledActionsHandlerImpl implements ScheduledActionRecoverEventHandler.Interface {\n public constructor(\n private readonly getRootTenant: GetRootTenantUseCase.Interface,\n private readonly rawTenantId: RawTenantId.Interface,\n private readonly tenantLoader: RequestTenantLoader.Interface,\n private readonly identityContext: IdentityContext.Interface,\n private readonly listScheduledActions: ListScheduledActionsUseCase.Interface,\n private readonly scheduler: SchedulerSingleton.Interface\n ) {}\n\n public async execute(): Promise<ScheduledActionRecoverEventHandler.Result> {\n const rootResult = await this.getRootTenant.execute();\n if (rootResult.isFail()) {\n // No root tenant yet: a fresh install has nothing to recover.\n return { recovered: 0 };\n }\n const tenant = rootResult.value.id;\n\n this.rawTenantId.set(tenant);\n await this.tenantLoader.establish();\n\n /*\n * Nobody is signed in at boot. Listing without authorization is what the run side does too\n * (ExecuteScheduledActionUseCase); otherwise the anonymous identity fails the permission check.\n */\n const recovered = await this.identityContext.withoutAuthorization(() => {\n return this.recoverAllPending(tenant);\n });\n\n return { recovered };\n }\n\n /*\n * Re-arms page by page, so a lookup that fails on a later page still leaves the earlier pages\n * armed. The failure is rethrown and logged by the boot step.\n */\n private async recoverAllPending(tenant: string): Promise<number> {\n let recovered = 0;\n let after: string | undefined;\n\n do {\n const listResult = await this.listScheduledActions.execute({\n where: {},\n limit: PAGE_SIZE,\n after\n });\n if (listResult.isFail()) {\n throw listResult.error;\n }\n\n const page: IPendingAction[] = [];\n for (const action of listResult.value.items) {\n page.push({\n id: action.id,\n namespace: action.namespace,\n tenant,\n scheduledFor: action.scheduledFor\n });\n }\n\n await this.scheduler.recover(page);\n recovered += page.length;\n\n const { meta } = listResult.value;\n after = meta.hasMoreItems && meta.cursor ? meta.cursor : undefined;\n } while (after);\n\n return recovered;\n }\n}\n\nexport const RecoverScheduledActionsHandler =\n ScheduledActionRecoverEventHandler.createImplementation({\n implementation: RecoverScheduledActionsHandlerImpl,\n dependencies: [\n GetRootTenantUseCase,\n RawTenantId,\n RequestTenantLoader,\n IdentityContext,\n ListScheduledActionsUseCase,\n SchedulerSingleton\n ]\n });\n"],"names":["PAGE_SIZE","RecoverScheduledActionsHandlerImpl","getRootTenant","rawTenantId","tenantLoader","identityContext","listScheduledActions","scheduler","rootResult","tenant","recovered","after","listResult","page","action","meta","undefined","RecoverScheduledActionsHandler","ScheduledActionRecoverEventHandler","GetRootTenantUseCase","RawTenantId","RequestTenantLoader","IdentityContext","ListScheduledActionsUseCase","SchedulerSingleton"],"mappings":";;;;;;AAUA,MAAMA,YAAY;AASlB,MAAMC;IACF,YACqBC,aAA6C,EAC7CC,WAAkC,EAClCC,YAA2C,EAC3CC,eAA0C,EAC1CC,oBAA2D,EAC3DC,SAAuC,CAC1D;aANmBL,aAAa,GAAbA;aACAC,WAAW,GAAXA;aACAC,YAAY,GAAZA;aACAC,eAAe,GAAfA;aACAC,oBAAoB,GAApBA;aACAC,SAAS,GAATA;IAClB;IAEH,MAAa,UAA8D;QACvE,MAAMC,aAAa,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO;QACnD,IAAIA,WAAW,MAAM,IAEjB,OAAO;YAAE,WAAW;QAAE;QAE1B,MAAMC,SAASD,WAAW,KAAK,CAAC,EAAE;QAElC,IAAI,CAAC,WAAW,CAAC,GAAG,CAACC;QACrB,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS;QAMjC,MAAMC,YAAY,MAAM,IAAI,CAAC,eAAe,CAAC,oBAAoB,CAAC,IACvD,IAAI,CAAC,iBAAiB,CAACD;QAGlC,OAAO;YAAEC;QAAU;IACvB;IAMA,MAAc,kBAAkBD,MAAc,EAAmB;QAC7D,IAAIC,YAAY;QAChB,IAAIC;QAEJ,GAAG;YACC,MAAMC,aAAa,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC;gBACvD,OAAO,CAAC;gBACR,OAAOZ;gBACPW;YACJ;YACA,IAAIC,WAAW,MAAM,IACjB,MAAMA,WAAW,KAAK;YAG1B,MAAMC,OAAyB,EAAE;YACjC,KAAK,MAAMC,UAAUF,WAAW,KAAK,CAAC,KAAK,CACvCC,KAAK,IAAI,CAAC;gBACN,IAAIC,OAAO,EAAE;gBACb,WAAWA,OAAO,SAAS;gBAC3BL;gBACA,cAAcK,OAAO,YAAY;YACrC;YAGJ,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACD;YAC7BH,aAAaG,KAAK,MAAM;YAExB,MAAM,EAAEE,IAAI,EAAE,GAAGH,WAAW,KAAK;YACjCD,QAAQI,KAAK,YAAY,IAAIA,KAAK,MAAM,GAAGA,KAAK,MAAM,GAAGC;QAC7D,QAASL,MAAO;QAEhB,OAAOD;IACX;AACJ;AAEO,MAAMO,iCACTC,mCAAmC,oBAAoB,CAAC;IACpD,gBAAgBjB;IAChB,cAAc;QACVkB;QACAC;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Abstraction } from "@webiny/di";
|
|
2
|
-
import type { IEventHandler } from "@webiny/event-handler-core";
|
|
3
|
-
import type { IScheduledActionRecoverEvent } from "./ScheduledActionRecoverEventType.js";
|
|
4
|
-
export interface IScheduledActionRecoverResult {
|
|
5
|
-
recovered: number;
|
|
6
|
-
}
|
|
7
|
-
export interface IScheduledActionRecoverEventHandler extends IEventHandler<IScheduledActionRecoverEvent, IScheduledActionRecoverResult> {
|
|
8
|
-
}
|
|
9
|
-
export declare const ScheduledActionRecoverEventHandler: Abstraction<IScheduledActionRecoverEventHandler>;
|
|
10
|
-
export declare namespace ScheduledActionRecoverEventHandler {
|
|
11
|
-
type Interface = IScheduledActionRecoverEventHandler;
|
|
12
|
-
type Result = IScheduledActionRecoverResult;
|
|
13
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler/ScheduledActionRecoverEventHandler.js","sources":["../../src/scheduler/ScheduledActionRecoverEventHandler.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\nimport type { IEventHandler } from \"@webiny/event-handler-core\";\nimport type { IScheduledActionRecoverEvent } from \"./ScheduledActionRecoverEventType.js\";\n\nexport interface IScheduledActionRecoverResult {\n recovered: number;\n}\n\nexport interface IScheduledActionRecoverEventHandler extends IEventHandler<\n IScheduledActionRecoverEvent,\n IScheduledActionRecoverResult\n> {}\n\nexport const ScheduledActionRecoverEventHandler =\n new Abstraction<IScheduledActionRecoverEventHandler>(\"ScheduledActionRecoverEventHandler\");\n\nexport namespace ScheduledActionRecoverEventHandler {\n export type Interface = IScheduledActionRecoverEventHandler;\n export type Result = IScheduledActionRecoverResult;\n}\n"],"names":["ScheduledActionRecoverEventHandler","Abstraction"],"mappings":";AAaO,MAAMA,qCACT,IAAIC,YAAiD"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { IEventType } from "@webiny/event-handler-core";
|
|
2
|
-
export declare const SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER = "WebinyScheduledActionRecover";
|
|
3
|
-
/**
|
|
4
|
-
* Asks the scheduler to re-arm the root tenant's pending scheduled actions. Dispatched once at
|
|
5
|
-
* boot: Bree keeps its timers in memory, so a restart loses every schedule that has not fired yet.
|
|
6
|
-
*/
|
|
7
|
-
export interface IScheduledActionRecoverEvent {
|
|
8
|
-
[SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER]: true;
|
|
9
|
-
}
|
|
10
|
-
declare class ScheduledActionRecoverEventTypeImpl implements IEventType<IScheduledActionRecoverEvent> {
|
|
11
|
-
canHandle(event: any): event is IScheduledActionRecoverEvent;
|
|
12
|
-
getHandlerAbstraction(): import("@webiny/di").Abstraction<import("./ScheduledActionRecoverEventHandler.js").IScheduledActionRecoverEventHandler>;
|
|
13
|
-
}
|
|
14
|
-
export declare const ScheduledActionRecoverEventType: typeof ScheduledActionRecoverEventTypeImpl & {
|
|
15
|
-
__abstraction: import("@webiny/di").Abstraction<IEventType<any>>;
|
|
16
|
-
};
|
|
17
|
-
export {};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { EventType } from "@webiny/event-handler-core";
|
|
2
|
-
import { ScheduledActionRecoverEventHandler } from "./ScheduledActionRecoverEventHandler.js";
|
|
3
|
-
const SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER = "WebinyScheduledActionRecover";
|
|
4
|
-
class ScheduledActionRecoverEventTypeImpl {
|
|
5
|
-
canHandle(event) {
|
|
6
|
-
return event?.[SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER] === true;
|
|
7
|
-
}
|
|
8
|
-
getHandlerAbstraction() {
|
|
9
|
-
return ScheduledActionRecoverEventHandler;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
const ScheduledActionRecoverEventType = EventType.createImplementation({
|
|
13
|
-
implementation: ScheduledActionRecoverEventTypeImpl,
|
|
14
|
-
dependencies: []
|
|
15
|
-
});
|
|
16
|
-
export { SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER, ScheduledActionRecoverEventType };
|
|
17
|
-
|
|
18
|
-
//# sourceMappingURL=ScheduledActionRecoverEventType.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler/ScheduledActionRecoverEventType.js","sources":["../../src/scheduler/ScheduledActionRecoverEventType.ts"],"sourcesContent":["import { EventType } from \"@webiny/event-handler-core\";\nimport type { IEventType } from \"@webiny/event-handler-core\";\nimport { ScheduledActionRecoverEventHandler } from \"./ScheduledActionRecoverEventHandler.js\";\n\nexport const SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER = \"WebinyScheduledActionRecover\";\n\n/**\n * Asks the scheduler to re-arm the root tenant's pending scheduled actions. Dispatched once at\n * boot: Bree keeps its timers in memory, so a restart loses every schedule that has not fired yet.\n */\nexport interface IScheduledActionRecoverEvent {\n [SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER]: true;\n}\n\nclass ScheduledActionRecoverEventTypeImpl implements IEventType<IScheduledActionRecoverEvent> {\n canHandle(event: any): event is IScheduledActionRecoverEvent {\n return event?.[SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER] === true;\n }\n\n getHandlerAbstraction() {\n return ScheduledActionRecoverEventHandler;\n }\n}\n\nexport const ScheduledActionRecoverEventType = EventType.createImplementation({\n implementation: ScheduledActionRecoverEventTypeImpl,\n dependencies: []\n});\n"],"names":["SCHEDULED_ACTION_RECOVER_EVENT_IDENTIFIER","ScheduledActionRecoverEventTypeImpl","event","ScheduledActionRecoverEventHandler","ScheduledActionRecoverEventType","EventType"],"mappings":";;AAIO,MAAMA,4CAA4C;AAUzD,MAAMC;IACF,UAAUC,KAAU,EAAyC;QACzD,OAAOA,OAAO,CAACF,0CAA0C,KAAK;IAClE;IAEA,wBAAwB;QACpB,OAAOG;IACX;AACJ;AAEO,MAAMC,kCAAkCC,UAAU,oBAAoB,CAAC;IAC1E,gBAAgBJ;IAChB,cAAc,EAAE;AACpB"}
|