@webiny/background-tasks-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/BackgroundTasksStandaloneFeature.d.ts +0 -4
- package/BackgroundTasksStandaloneFeature.js +9 -8
- package/BackgroundTasksStandaloneFeature.js.map +1 -1
- package/domain/InternalToken.d.ts +8 -0
- package/domain/InternalToken.js +5 -0
- package/domain/InternalToken.js.map +1 -0
- package/package.json +9 -8
- package/routes/BackgroundTaskRoute.d.ts +25 -0
- package/routes/BackgroundTaskRoute.js +66 -0
- package/routes/BackgroundTaskRoute.js.map +1 -0
- package/service/WorkerTaskService.d.ts +16 -0
- package/service/WorkerTaskService.js +87 -0
- package/service/WorkerTaskService.js.map +1 -0
- package/worker/TaskOrchestrator.d.ts +12 -0
- package/worker/TaskOrchestrator.js +120 -0
- package/worker/TaskOrchestrator.js.map +1 -0
- package/worker/TaskOrchestratorMessage.d.ts +28 -0
- package/worker/TaskOrchestratorMessage.js +0 -0
- package/worker/workerEntry.d.ts +1 -0
- package/worker/workerEntry.js +12 -0
- package/worker/workerEntry.js.map +1 -0
- package/domain/TaskLoop.d.ts +0 -15
- package/domain/TaskLoop.js +0 -5
- package/domain/TaskLoop.js.map +0 -1
- package/handlers/InProcessBackgroundTaskHandler.d.ts +0 -22
- package/handlers/InProcessBackgroundTaskHandler.js +0 -34
- package/handlers/InProcessBackgroundTaskHandler.js.map +0 -1
- package/loop/DispatchingTaskLoop.d.ts +0 -17
- package/loop/DispatchingTaskLoop.js +0 -48
- package/loop/DispatchingTaskLoop.js.map +0 -1
- package/service/InProcessTaskService.d.ts +0 -19
- package/service/InProcessTaskService.js +0 -39
- package/service/InProcessTaskService.js.map +0 -1
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background tasks for the standalone server. Register at the root container: the loop is one
|
|
3
|
-
* long-lived object for the whole process, and it needs the root's `EventDispatcher`.
|
|
4
|
-
*/
|
|
5
1
|
export declare const BackgroundTasksStandaloneFeature: {
|
|
6
2
|
name: string;
|
|
7
3
|
register(container: import("@webiny/di").Container): void;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createFeature } from "@webiny/feature/api";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { uuid } from "@webiny/stdlib";
|
|
3
|
+
import { WorkerService } from "./service/WorkerTaskService.js";
|
|
4
|
+
import { BackgroundTaskRouteDefinition } from "./routes/BackgroundTaskRoute.js";
|
|
5
|
+
import { InternalToken } from "./domain/InternalToken.js";
|
|
6
6
|
const BackgroundTasksStandaloneFeature = createFeature({
|
|
7
7
|
name: "BackgroundTasksServer",
|
|
8
8
|
register (container) {
|
|
9
|
-
container.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
container.register(
|
|
9
|
+
container.registerInstance(InternalToken, {
|
|
10
|
+
value: uuid()
|
|
11
|
+
});
|
|
12
|
+
container.register(WorkerService);
|
|
13
|
+
container.register(BackgroundTaskRouteDefinition);
|
|
13
14
|
}
|
|
14
15
|
});
|
|
15
16
|
export { BackgroundTasksStandaloneFeature };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackgroundTasksStandaloneFeature.js","sources":["../src/BackgroundTasksStandaloneFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport {
|
|
1
|
+
{"version":3,"file":"BackgroundTasksStandaloneFeature.js","sources":["../src/BackgroundTasksStandaloneFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { uuid } from \"@webiny/stdlib\";\nimport { WorkerService } from \"~/service/WorkerTaskService.js\";\nimport { BackgroundTaskRouteDefinition } from \"~/routes/BackgroundTaskRoute.js\";\nimport { InternalToken } from \"~/domain/InternalToken.js\";\n\nexport const BackgroundTasksStandaloneFeature = createFeature({\n name: \"BackgroundTasksServer\",\n register(container) {\n container.registerInstance(InternalToken, { value: uuid() });\n container.register(WorkerService);\n container.register(BackgroundTaskRouteDefinition);\n }\n});\n"],"names":["BackgroundTasksStandaloneFeature","createFeature","container","InternalToken","uuid","WorkerService","BackgroundTaskRouteDefinition"],"mappings":";;;;;AAMO,MAAMA,mCAAmCC,cAAc;IAC1D,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,gBAAgB,CAACC,eAAe;YAAE,OAAOC;QAAO;QAC1DF,UAAU,QAAQ,CAACG;QACnBH,UAAU,QAAQ,CAACI;IACvB;AACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain/InternalToken.js","sources":["../../src/domain/InternalToken.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\n\n/*\n * Per-process token generated at startup, shared between WorkerService and BackgroundTaskRoute\n * via DI. Workers send it as a header; the route rejects requests without a matching value.\n * This only works within a single server process. For multi-server deployments where workers\n * on one server may hit another server's route, replace with a shared secret (env var or\n * config service).\n */\n\ninterface IInternalToken {\n readonly value: string;\n}\n\nexport const InternalToken = createAbstraction<IInternalToken>(\"BackgroundTasks/InternalToken\");\n\nexport namespace InternalToken {\n export type Interface = IInternalToken;\n}\n"],"names":["InternalToken","createAbstraction"],"mappings":";AAcO,MAAMA,gBAAgBC,kBAAkC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/background-tasks-standalone",
|
|
3
|
-
"version": "0.0.0-unstable.
|
|
3
|
+
"version": "0.0.0-unstable.eb81deee7e",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@webiny/api-core": "0.0.0-unstable.
|
|
18
|
-
"@webiny/background-tasks": "0.0.0-unstable.
|
|
19
|
-
"@webiny/event-handler-core": "0.0.0-unstable.
|
|
20
|
-
"@webiny/feature": "0.0.0-unstable.
|
|
21
|
-
"@webiny/
|
|
17
|
+
"@webiny/api-core": "0.0.0-unstable.eb81deee7e",
|
|
18
|
+
"@webiny/background-tasks": "0.0.0-unstable.eb81deee7e",
|
|
19
|
+
"@webiny/event-handler-core": "0.0.0-unstable.eb81deee7e",
|
|
20
|
+
"@webiny/feature": "0.0.0-unstable.eb81deee7e",
|
|
21
|
+
"@webiny/stdlib": "0.0.17",
|
|
22
|
+
"@webiny/utils": "0.0.0-unstable.eb81deee7e"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@webiny/build-tools": "0.0.0-unstable.
|
|
25
|
-
"@webiny/di": "
|
|
25
|
+
"@webiny/build-tools": "0.0.0-unstable.eb81deee7e",
|
|
26
|
+
"@webiny/di": "1.1.0",
|
|
26
27
|
"rimraf": "6.1.3",
|
|
27
28
|
"typescript": "7.0.2",
|
|
28
29
|
"vitest": "4.1.11"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler } from "@webiny/event-handler-core";
|
|
2
|
+
import type { Container } from "@webiny/feature/api";
|
|
3
|
+
import { InternalToken } from "../domain/InternalToken.js";
|
|
4
|
+
declare class BackgroundTaskRouteImpl implements HttpRouteHandler.Interface {
|
|
5
|
+
private readonly container;
|
|
6
|
+
private readonly internalToken;
|
|
7
|
+
constructor(container: Container, internalToken: InternalToken.Interface);
|
|
8
|
+
handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response): Promise<import("@webiny/event-handler-core").IHttpResponseBuilder>;
|
|
9
|
+
}
|
|
10
|
+
export declare const BackgroundTaskRoute: typeof BackgroundTaskRouteImpl & {
|
|
11
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
12
|
+
};
|
|
13
|
+
declare class BackgroundTaskRouteDefinitionImpl implements HttpRouteDefinition.Interface {
|
|
14
|
+
readonly name = "background-task";
|
|
15
|
+
readonly method = "POST";
|
|
16
|
+
readonly path = "/background-task";
|
|
17
|
+
readonly handler: typeof BackgroundTaskRouteImpl & {
|
|
18
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** What the router matches on. Zero dependencies, so building it costs nothing. */
|
|
22
|
+
export declare const BackgroundTaskRouteDefinition: typeof BackgroundTaskRouteDefinitionImpl & {
|
|
23
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRouteDefinition>;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler, RequestContainer } from "@webiny/event-handler-core";
|
|
2
|
+
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
3
|
+
import { TaskRunner } from "@webiny/background-tasks/api/runner/index.js";
|
|
4
|
+
import { TaskEventValidation } from "@webiny/background-tasks/api/runner/TaskEventValidation.js";
|
|
5
|
+
import { ProcessTimer } from "../timer/ProcessTimer.js";
|
|
6
|
+
import { InternalToken } from "../domain/InternalToken.js";
|
|
7
|
+
const INTERNAL_HEADER = "x-webiny-background-task-token";
|
|
8
|
+
class BackgroundTaskRouteImpl {
|
|
9
|
+
constructor(container, internalToken){
|
|
10
|
+
this.container = container;
|
|
11
|
+
this.internalToken = internalToken;
|
|
12
|
+
}
|
|
13
|
+
async handle(request, response) {
|
|
14
|
+
if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) return response.status(403).json({
|
|
15
|
+
error: "Forbidden."
|
|
16
|
+
});
|
|
17
|
+
const taskEvent = request.body;
|
|
18
|
+
if (!taskEvent || !taskEvent.webinyTaskId) return response.status(400).json({
|
|
19
|
+
error: "Missing webinyTaskId in request body."
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
if (taskEvent.tenant) {
|
|
23
|
+
this.container.resolve(RawTenantId).set(taskEvent.tenant);
|
|
24
|
+
await this.container.resolve(RequestTenantLoader).establish();
|
|
25
|
+
}
|
|
26
|
+
const ctx = {
|
|
27
|
+
container: this.container
|
|
28
|
+
};
|
|
29
|
+
const timer = new ProcessTimer();
|
|
30
|
+
const runner = new TaskRunner(ctx, timer, new TaskEventValidation());
|
|
31
|
+
const result = await runner.run(taskEvent);
|
|
32
|
+
return response.json(result);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35
|
+
console.error(`Background task route error: ${message}`);
|
|
36
|
+
return response.status(500).json({
|
|
37
|
+
status: "error",
|
|
38
|
+
error: {
|
|
39
|
+
message
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const BackgroundTaskRoute = HttpRouteHandler.createImplementation({
|
|
46
|
+
implementation: BackgroundTaskRouteImpl,
|
|
47
|
+
dependencies: [
|
|
48
|
+
RequestContainer,
|
|
49
|
+
InternalToken
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
class BackgroundTaskRouteDefinitionImpl {
|
|
53
|
+
constructor(){
|
|
54
|
+
this.name = "background-task";
|
|
55
|
+
this.method = "POST";
|
|
56
|
+
this.path = "/background-task";
|
|
57
|
+
this.handler = BackgroundTaskRoute;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const BackgroundTaskRouteDefinition = HttpRouteDefinition.createImplementation({
|
|
61
|
+
implementation: BackgroundTaskRouteDefinitionImpl,
|
|
62
|
+
dependencies: []
|
|
63
|
+
});
|
|
64
|
+
export { BackgroundTaskRoute, BackgroundTaskRouteDefinition };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=BackgroundTaskRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes/BackgroundTaskRoute.js","sources":["../../src/routes/BackgroundTaskRoute.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 { TaskRunner } from \"@webiny/background-tasks/api/runner/index.js\";\nimport { TaskEventValidation } from \"@webiny/background-tasks/api/runner/TaskEventValidation.js\";\nimport type { Context } from \"@webiny/background-tasks/api/types.js\";\nimport type { Container } from \"@webiny/feature/api\";\nimport { ProcessTimer } from \"~/timer/ProcessTimer.js\";\nimport { InternalToken } from \"~/domain/InternalToken.js\";\n\n/* Shared between worker and route to gate access. */\nconst INTERNAL_HEADER = \"x-webiny-background-task-token\";\n\nclass BackgroundTaskRouteImpl implements HttpRouteHandler.Interface {\n public constructor(\n private readonly container: Container,\n private readonly internalToken: InternalToken.Interface\n ) {}\n\n public async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {\n /* Reject requests without a matching internal token. */\n if (request.headers[INTERNAL_HEADER] !== this.internalToken.value) {\n return response.status(403).json({ error: \"Forbidden.\" });\n }\n\n const taskEvent = request.body;\n\n if (!taskEvent || !taskEvent.webinyTaskId) {\n return response.status(400).json({ error: \"Missing webinyTaskId in request body.\" });\n }\n\n try {\n if (taskEvent.tenant) {\n this.container.resolve(RawTenantId).set(taskEvent.tenant);\n await this.container.resolve(RequestTenantLoader).establish();\n }\n\n // TaskRunner still takes the legacy context object, which now only carries the container.\n const ctx: Record<string, any> = { container: this.container };\n\n const timer = new ProcessTimer();\n const runner = new TaskRunner(ctx as Context, timer, new TaskEventValidation());\n const result = await runner.run(taskEvent);\n\n return response.json(result);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Background task route error: ${message}`);\n return response.status(500).json({ status: \"error\", error: { message } });\n }\n }\n}\n\nexport const BackgroundTaskRoute = HttpRouteHandler.createImplementation({\n implementation: BackgroundTaskRouteImpl,\n dependencies: [RequestContainer, InternalToken]\n});\n\nclass BackgroundTaskRouteDefinitionImpl implements HttpRouteDefinition.Interface {\n readonly name = \"background-task\";\n readonly method = \"POST\";\n readonly path = \"/background-task\";\n readonly handler = BackgroundTaskRoute;\n}\n\n/** What the router matches on. Zero dependencies, so building it costs nothing. */\nexport const BackgroundTaskRouteDefinition = HttpRouteDefinition.createImplementation({\n implementation: BackgroundTaskRouteDefinitionImpl,\n dependencies: []\n});\n"],"names":["INTERNAL_HEADER","BackgroundTaskRouteImpl","container","internalToken","request","response","taskEvent","RawTenantId","RequestTenantLoader","ctx","timer","ProcessTimer","runner","TaskRunner","TaskEventValidation","result","err","message","Error","String","console","BackgroundTaskRoute","HttpRouteHandler","RequestContainer","InternalToken","BackgroundTaskRouteDefinitionImpl","BackgroundTaskRouteDefinition","HttpRouteDefinition"],"mappings":";;;;;;AAiBA,MAAMA,kBAAkB;AAExB,MAAMC;IACF,YACqBC,SAAoB,EACpBC,aAAsC,CACzD;aAFmBD,SAAS,GAATA;aACAC,aAAa,GAAbA;IAClB;IAEH,MAAa,OAAOC,OAAiC,EAAEC,QAAmC,EAAE;QAExF,IAAID,QAAQ,OAAO,CAACJ,gBAAgB,KAAK,IAAI,CAAC,aAAa,CAAC,KAAK,EAC7D,OAAOK,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAAa;QAG3D,MAAMC,YAAYF,QAAQ,IAAI;QAE9B,IAAI,CAACE,aAAa,CAACA,UAAU,YAAY,EACrC,OAAOD,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAAwC;QAGtF,IAAI;YACA,IAAIC,UAAU,MAAM,EAAE;gBAClB,IAAI,CAAC,SAAS,CAAC,OAAO,CAACC,aAAa,GAAG,CAACD,UAAU,MAAM;gBACxD,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACE,qBAAqB,SAAS;YAC/D;YAGA,MAAMC,MAA2B;gBAAE,WAAW,IAAI,CAAC,SAAS;YAAC;YAE7D,MAAMC,QAAQ,IAAIC;YAClB,MAAMC,SAAS,IAAIC,WAAWJ,KAAgBC,OAAO,IAAII;YACzD,MAAMC,SAAS,MAAMH,OAAO,GAAG,CAACN;YAEhC,OAAOD,SAAS,IAAI,CAACU;QACzB,EAAE,OAAOC,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DI,QAAQ,KAAK,CAAC,CAAC,6BAA6B,EAAEH,SAAS;YACvD,OAAOZ,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAEY;gBAAQ;YAAE;QAC3E;IACJ;AACJ;AAEO,MAAMI,sBAAsBC,iBAAiB,oBAAoB,CAAC;IACrE,gBAAgBrB;IAChB,cAAc;QAACsB;QAAkBC;KAAc;AACnD;AAEA,MAAMC;;aACO,IAAI,GAAG;aACP,MAAM,GAAG;aACT,IAAI,GAAG;aACP,OAAO,GAAGJ;;AACvB;AAGO,MAAMK,gCAAgCC,oBAAoB,oBAAoB,CAAC;IAClF,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
|
|
2
|
+
import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
|
|
3
|
+
import { InternalToken } from "../domain/InternalToken.js";
|
|
4
|
+
declare class WorkerServiceImpl implements TaskService.Interface {
|
|
5
|
+
private readonly tenantContext;
|
|
6
|
+
private readonly internalToken;
|
|
7
|
+
private readonly serverUrl;
|
|
8
|
+
private readonly handles;
|
|
9
|
+
constructor(tenantContext: TenantContext.Interface, internalToken: InternalToken.Interface);
|
|
10
|
+
send(task: TaskService.SendTaskParams, delay: number): Promise<unknown>;
|
|
11
|
+
fetch(task: TaskService.Task): Promise<Record<string, unknown> | null>;
|
|
12
|
+
}
|
|
13
|
+
export declare const WorkerService: typeof WorkerServiceImpl & {
|
|
14
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/background-tasks/api/domain/TaskService.js").ITaskService>;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
|
|
3
|
+
import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
|
|
4
|
+
import { InternalToken } from "../domain/InternalToken.js";
|
|
5
|
+
const DEFAULT_SERVER_PORT = 3002;
|
|
6
|
+
const DEFAULT_MAX_DURATION_MS = 86400000;
|
|
7
|
+
class WorkerServiceImpl {
|
|
8
|
+
constructor(tenantContext, internalToken){
|
|
9
|
+
this.tenantContext = tenantContext;
|
|
10
|
+
this.internalToken = internalToken;
|
|
11
|
+
this.handles = new Map();
|
|
12
|
+
const port = process.env.PORT || DEFAULT_SERVER_PORT;
|
|
13
|
+
this.serverUrl = `http://localhost:${port}/background-task`;
|
|
14
|
+
}
|
|
15
|
+
async send(task, delay) {
|
|
16
|
+
const tenant = this.tenantContext.getTenant();
|
|
17
|
+
if (!tenant) {
|
|
18
|
+
console.error("Tenant not found.");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const worker = new Worker(new URL("../worker/workerEntry.js", import.meta.url));
|
|
22
|
+
const handle = {
|
|
23
|
+
worker,
|
|
24
|
+
startedAt: Date.now(),
|
|
25
|
+
taskId: task.id,
|
|
26
|
+
status: "running",
|
|
27
|
+
exitCode: null
|
|
28
|
+
};
|
|
29
|
+
this.handles.set(task.id, handle);
|
|
30
|
+
worker.on("message", (msg)=>{
|
|
31
|
+
if ("done" === msg.type) handle.status = "done";
|
|
32
|
+
else if ("error" === msg.type) {
|
|
33
|
+
handle.status = "error";
|
|
34
|
+
console.error(`Background task "${task.id}" (${task.definitionId}) failed:`, msg.error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
worker.on("error", (err)=>{
|
|
38
|
+
handle.status = "error";
|
|
39
|
+
console.error(`Worker error for task "${task.id}": ${err.message}`);
|
|
40
|
+
});
|
|
41
|
+
worker.on("exit", (code)=>{
|
|
42
|
+
handle.exitCode = code;
|
|
43
|
+
if ("running" === handle.status) handle.status = 0 === code ? "done" : "error";
|
|
44
|
+
setTimeout(()=>{
|
|
45
|
+
this.handles.delete(task.id);
|
|
46
|
+
}, 60000);
|
|
47
|
+
});
|
|
48
|
+
worker.postMessage({
|
|
49
|
+
type: "start",
|
|
50
|
+
taskEvent: {
|
|
51
|
+
webinyTaskId: task.id,
|
|
52
|
+
webinyTaskDefinitionId: task.definitionId,
|
|
53
|
+
tenant: tenant.id,
|
|
54
|
+
delay,
|
|
55
|
+
endpoint: this.serverUrl,
|
|
56
|
+
executionName: task.id,
|
|
57
|
+
stateMachineId: ""
|
|
58
|
+
},
|
|
59
|
+
serverUrl: this.serverUrl,
|
|
60
|
+
maxDurationMs: DEFAULT_MAX_DURATION_MS,
|
|
61
|
+
internalToken: this.internalToken.value
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
workerId: worker.threadId,
|
|
65
|
+
taskId: task.id
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async fetch(task) {
|
|
69
|
+
const handle = this.handles.get(task.id);
|
|
70
|
+
if (!handle) return null;
|
|
71
|
+
return {
|
|
72
|
+
status: handle.status,
|
|
73
|
+
startedAt: handle.startedAt,
|
|
74
|
+
exitCode: handle.exitCode
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const WorkerService = TaskService.createImplementation({
|
|
79
|
+
implementation: WorkerServiceImpl,
|
|
80
|
+
dependencies: [
|
|
81
|
+
TenantContext,
|
|
82
|
+
InternalToken
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
export { WorkerService };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=WorkerTaskService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service/WorkerTaskService.js","sources":["../../src/service/WorkerTaskService.ts"],"sourcesContent":["import { Worker } from \"node:worker_threads\";\nimport type { WorkerToParentMessage } from \"~/worker/TaskOrchestratorMessage.js\";\nimport { TaskService } from \"@webiny/background-tasks/api/domain/TaskService.js\";\nimport { TenantContext } from \"@webiny/api-core/exports/api/tenancy.js\";\nimport { InternalToken } from \"~/domain/InternalToken.js\";\n\n// Matches findFreePort's search base in spawnApiServer (and the scheduler's self-callback default), so\n// every self-callback in the standalone hosting type agrees on the same fallback. In practice never used:\n// spawnApiServer always injects the resolved port as process.env.PORT.\nconst DEFAULT_SERVER_PORT = 3002;\nconst DEFAULT_MAX_DURATION_MS = 86_400_000;\n\ninterface WorkerHandle {\n readonly worker: Worker;\n readonly startedAt: number;\n readonly taskId: string;\n status: \"running\" | \"done\" | \"error\";\n exitCode: number | null;\n}\n\nclass WorkerServiceImpl implements TaskService.Interface {\n private readonly serverUrl: string;\n private readonly handles: Map<string, WorkerHandle> = new Map();\n\n public constructor(\n private readonly tenantContext: TenantContext.Interface,\n private readonly internalToken: InternalToken.Interface\n ) {\n // Single-process standalone hosting type: the worker POSTs the task back to THIS server's\n // `/background-task` route. The port is the one the server actually listens on — injected at\n // runtime as `process.env.PORT` by `spawnApiServer` (dynamic, chosen via findFreePort), NOT a\n // build-time value. It cannot be a build param: the port isn't known until the process starts.\n const port = process.env.PORT || DEFAULT_SERVER_PORT;\n this.serverUrl = `http://localhost:${port}/background-task`;\n }\n\n public async send(task: TaskService.SendTaskParams, delay: number): Promise<unknown> {\n const tenant = this.tenantContext.getTenant();\n if (!tenant) {\n console.error(\"Tenant not found.\");\n return null;\n }\n\n // Resolved relative to this module (import.meta.url), not the process cwd, so it works no\n // matter where the server is launched. The app bundler recognizes this exact shape and emits\n // the worker as its own chunk into build/ (the server build sets assetPrefix \"auto\" so the\n // chunk URL resolves relative to the running handler — see createRsbuildConfig). Un-bundled\n // (dev), import.meta.url is the real dist file and this resolves to dist/worker/workerEntry.js.\n const worker = new Worker(new URL(\"../worker/workerEntry.js\", import.meta.url));\n\n const handle: WorkerHandle = {\n worker,\n startedAt: Date.now(),\n taskId: task.id,\n status: \"running\",\n exitCode: null\n };\n\n this.handles.set(task.id, handle);\n\n worker.on(\"message\", (msg: WorkerToParentMessage) => {\n if (msg.type === \"done\") {\n handle.status = \"done\";\n } else if (msg.type === \"error\") {\n handle.status = \"error\";\n // Surface the worker's error — otherwise a failed task shows only as a stuck record.\n console.error(\n `Background task \"${task.id}\" (${task.definitionId}) failed:`,\n msg.error\n );\n }\n });\n\n worker.on(\"error\", (err: Error) => {\n handle.status = \"error\";\n console.error(`Worker error for task \"${task.id}\": ${err.message}`);\n });\n\n worker.on(\"exit\", (code: number) => {\n handle.exitCode = code;\n if (handle.status === \"running\") {\n handle.status = code === 0 ? \"done\" : \"error\";\n }\n /* Clean up handle after 60s to allow pending fetch() calls. */\n setTimeout(() => {\n this.handles.delete(task.id);\n }, 60_000);\n });\n\n worker.postMessage({\n type: \"start\",\n taskEvent: {\n webinyTaskId: task.id,\n webinyTaskDefinitionId: task.definitionId,\n tenant: tenant.id,\n delay,\n // Satisfy the shared runner's AWS-Step-Functions-shaped validation. No SFN in a single\n // process: executionName is a stable run id (the task id); endpoint/stateMachineId are\n // validate-only off-AWS.\n endpoint: this.serverUrl,\n executionName: task.id,\n stateMachineId: \"\"\n },\n serverUrl: this.serverUrl,\n maxDurationMs: DEFAULT_MAX_DURATION_MS,\n internalToken: this.internalToken.value\n });\n\n return { workerId: worker.threadId, taskId: task.id };\n }\n\n public async fetch(task: TaskService.Task): Promise<Record<string, unknown> | null> {\n const handle = this.handles.get(task.id);\n if (!handle) {\n return null;\n }\n\n return {\n status: handle.status,\n startedAt: handle.startedAt,\n exitCode: handle.exitCode\n };\n }\n}\n\nexport const WorkerService = TaskService.createImplementation({\n implementation: WorkerServiceImpl,\n dependencies: [TenantContext, InternalToken]\n});\n"],"names":["DEFAULT_SERVER_PORT","DEFAULT_MAX_DURATION_MS","WorkerServiceImpl","tenantContext","internalToken","Map","port","process","task","delay","tenant","console","worker","Worker","URL","handle","Date","msg","err","code","setTimeout","WorkerService","TaskService","TenantContext","InternalToken"],"mappings":";;;;AASA,MAAMA,sBAAsB;AAC5B,MAAMC,0BAA0B;AAUhC,MAAMC;IAIF,YACqBC,aAAsC,EACtCC,aAAsC,CACzD;aAFmBD,aAAa,GAAbA;aACAC,aAAa,GAAbA;aAJJ,OAAO,GAA8B,IAAIC;QAUtD,MAAMC,OAAOC,QAAQ,GAAG,CAAC,IAAI,IAAIP;QACjC,IAAI,CAAC,SAAS,GAAG,CAAC,iBAAiB,EAAEM,KAAK,gBAAgB,CAAC;IAC/D;IAEA,MAAa,KAAKE,IAAgC,EAAEC,KAAa,EAAoB;QACjF,MAAMC,SAAS,IAAI,CAAC,aAAa,CAAC,SAAS;QAC3C,IAAI,CAACA,QAAQ;YACTC,QAAQ,KAAK,CAAC;YACd,OAAO;QACX;QAOA,MAAMC,SAAS,IAAIC,OAAO,IAAIC,IAAI,4BAA4B,YAAY,GAAG;QAE7E,MAAMC,SAAuB;YACzBH;YACA,WAAWI,KAAK,GAAG;YACnB,QAAQR,KAAK,EAAE;YACf,QAAQ;YACR,UAAU;QACd;QAEA,IAAI,CAAC,OAAO,CAAC,GAAG,CAACA,KAAK,EAAE,EAAEO;QAE1BH,OAAO,EAAE,CAAC,WAAW,CAACK;YAClB,IAAIA,AAAa,WAAbA,IAAI,IAAI,EACRF,OAAO,MAAM,GAAG;iBACb,IAAIE,AAAa,YAAbA,IAAI,IAAI,EAAc;gBAC7BF,OAAO,MAAM,GAAG;gBAEhBJ,QAAQ,KAAK,CACT,CAAC,iBAAiB,EAAEH,KAAK,EAAE,CAAC,GAAG,EAAEA,KAAK,YAAY,CAAC,SAAS,CAAC,EAC7DS,IAAI,KAAK;YAEjB;QACJ;QAEAL,OAAO,EAAE,CAAC,SAAS,CAACM;YAChBH,OAAO,MAAM,GAAG;YAChBJ,QAAQ,KAAK,CAAC,CAAC,uBAAuB,EAAEH,KAAK,EAAE,CAAC,GAAG,EAAEU,IAAI,OAAO,EAAE;QACtE;QAEAN,OAAO,EAAE,CAAC,QAAQ,CAACO;YACfJ,OAAO,QAAQ,GAAGI;YAClB,IAAIJ,AAAkB,cAAlBA,OAAO,MAAM,EACbA,OAAO,MAAM,GAAGI,AAAS,MAATA,OAAa,SAAS;YAG1CC,WAAW;gBACP,IAAI,CAAC,OAAO,CAAC,MAAM,CAACZ,KAAK,EAAE;YAC/B,GAAG;QACP;QAEAI,OAAO,WAAW,CAAC;YACf,MAAM;YACN,WAAW;gBACP,cAAcJ,KAAK,EAAE;gBACrB,wBAAwBA,KAAK,YAAY;gBACzC,QAAQE,OAAO,EAAE;gBACjBD;gBAIA,UAAU,IAAI,CAAC,SAAS;gBACxB,eAAeD,KAAK,EAAE;gBACtB,gBAAgB;YACpB;YACA,WAAW,IAAI,CAAC,SAAS;YACzB,eAAeP;YACf,eAAe,IAAI,CAAC,aAAa,CAAC,KAAK;QAC3C;QAEA,OAAO;YAAE,UAAUW,OAAO,QAAQ;YAAE,QAAQJ,KAAK,EAAE;QAAC;IACxD;IAEA,MAAa,MAAMA,IAAsB,EAA2C;QAChF,MAAMO,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAACP,KAAK,EAAE;QACvC,IAAI,CAACO,QACD,OAAO;QAGX,OAAO;YACH,QAAQA,OAAO,MAAM;YACrB,WAAWA,OAAO,SAAS;YAC3B,UAAUA,OAAO,QAAQ;QAC7B;IACJ;AACJ;AAEO,MAAMM,gBAAgBC,YAAY,oBAAoB,CAAC;IAC1D,gBAAgBpB;IAChB,cAAc;QAACqB;QAAeC;KAAc;AAChD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StartMessage, WorkerToParentMessage } from "./TaskOrchestratorMessage.js";
|
|
2
|
+
export declare class TaskOrchestrator {
|
|
3
|
+
private readonly serverUrl;
|
|
4
|
+
private readonly taskEvent;
|
|
5
|
+
private readonly timer;
|
|
6
|
+
private readonly internalToken;
|
|
7
|
+
private readonly postMessage;
|
|
8
|
+
constructor(message: StartMessage, postMessage: (msg: WorkerToParentMessage) => void);
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
private post;
|
|
11
|
+
private wait;
|
|
12
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import node_http from "node:http";
|
|
2
|
+
import { ProcessTimer } from "../timer/ProcessTimer.js";
|
|
3
|
+
class TaskOrchestrator {
|
|
4
|
+
constructor(message, postMessage){
|
|
5
|
+
this.serverUrl = message.serverUrl;
|
|
6
|
+
this.taskEvent = message.taskEvent;
|
|
7
|
+
this.timer = new ProcessTimer(message.maxDurationMs);
|
|
8
|
+
this.internalToken = message.internalToken;
|
|
9
|
+
this.postMessage = postMessage;
|
|
10
|
+
}
|
|
11
|
+
async run() {
|
|
12
|
+
const taskId = this.taskEvent.webinyTaskId;
|
|
13
|
+
try {
|
|
14
|
+
if (this.taskEvent.delay > 0) await this.wait(1000 * this.taskEvent.delay);
|
|
15
|
+
let input = {};
|
|
16
|
+
let continueLoop = true;
|
|
17
|
+
while(continueLoop){
|
|
18
|
+
if (this.timer.getRemainingMilliseconds() <= 0) return void this.postMessage({
|
|
19
|
+
type: "error",
|
|
20
|
+
taskId,
|
|
21
|
+
error: "Task exceeded maximum duration."
|
|
22
|
+
});
|
|
23
|
+
const payload = {
|
|
24
|
+
...this.taskEvent,
|
|
25
|
+
input
|
|
26
|
+
};
|
|
27
|
+
const response = await this.post(payload);
|
|
28
|
+
switch(response.status){
|
|
29
|
+
case "continue":
|
|
30
|
+
input = response.input || {};
|
|
31
|
+
if (response.wait && response.wait > 0) await this.wait(1000 * response.wait);
|
|
32
|
+
break;
|
|
33
|
+
case "done":
|
|
34
|
+
this.postMessage({
|
|
35
|
+
type: "done",
|
|
36
|
+
taskId,
|
|
37
|
+
result: response
|
|
38
|
+
});
|
|
39
|
+
continueLoop = false;
|
|
40
|
+
break;
|
|
41
|
+
case "error":
|
|
42
|
+
this.postMessage({
|
|
43
|
+
type: "error",
|
|
44
|
+
taskId,
|
|
45
|
+
error: JSON.stringify(response)
|
|
46
|
+
});
|
|
47
|
+
continueLoop = false;
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
this.postMessage({
|
|
51
|
+
type: "error",
|
|
52
|
+
taskId,
|
|
53
|
+
error: `Unknown response status: ${response.status}`
|
|
54
|
+
});
|
|
55
|
+
continueLoop = false;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61
|
+
this.postMessage({
|
|
62
|
+
type: "error",
|
|
63
|
+
taskId,
|
|
64
|
+
error: message
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
post(payload) {
|
|
69
|
+
return new Promise((resolve, reject)=>{
|
|
70
|
+
const url = new URL(this.serverUrl);
|
|
71
|
+
const body = JSON.stringify(payload);
|
|
72
|
+
const remaining = this.timer.getRemainingMilliseconds();
|
|
73
|
+
let deadline;
|
|
74
|
+
const fail = (error)=>{
|
|
75
|
+
clearTimeout(deadline);
|
|
76
|
+
reject(error);
|
|
77
|
+
};
|
|
78
|
+
const req = node_http.request({
|
|
79
|
+
hostname: url.hostname,
|
|
80
|
+
port: url.port,
|
|
81
|
+
path: url.pathname,
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"content-type": "application/json",
|
|
85
|
+
"content-length": Buffer.byteLength(body),
|
|
86
|
+
"x-webiny-background-task-token": this.internalToken
|
|
87
|
+
}
|
|
88
|
+
}, (res)=>{
|
|
89
|
+
let data = "";
|
|
90
|
+
res.on("data", (chunk)=>{
|
|
91
|
+
data += chunk;
|
|
92
|
+
});
|
|
93
|
+
res.on("error", fail);
|
|
94
|
+
res.on("end", ()=>{
|
|
95
|
+
clearTimeout(deadline);
|
|
96
|
+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) return void reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
97
|
+
try {
|
|
98
|
+
resolve(JSON.parse(data));
|
|
99
|
+
} catch {
|
|
100
|
+
reject(new Error(`Invalid JSON response: ${data}`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
deadline = setTimeout(()=>{
|
|
105
|
+
const exceeded = new Error("Task exceeded maximum duration.");
|
|
106
|
+
req.destroy(exceeded);
|
|
107
|
+
fail(exceeded);
|
|
108
|
+
}, remaining);
|
|
109
|
+
req.on("error", fail);
|
|
110
|
+
req.write(body);
|
|
111
|
+
req.end();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
wait(ms) {
|
|
115
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export { TaskOrchestrator };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=TaskOrchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker/TaskOrchestrator.js","sources":["../../src/worker/TaskOrchestrator.ts"],"sourcesContent":["import http from \"node:http\";\nimport type { Timer } from \"@webiny/utils/features/Timer/abstraction.js\";\nimport type { StartMessage, WorkerToParentMessage } from \"./TaskOrchestratorMessage.js\";\nimport { ProcessTimer } from \"~/timer/ProcessTimer.js\";\n\ninterface TaskResponse {\n status: string;\n input?: Record<string, unknown>;\n wait?: number;\n [key: string]: unknown;\n}\n\n/* Dumb HTTP client loop. It posts the task event to the running server's background-task\n * endpoint, then continues, exits, or errors based on the response status. ProcessTimer is the\n * safety net for the worker's maximum allowed duration. */\nexport class TaskOrchestrator {\n private readonly serverUrl: string;\n private readonly taskEvent: StartMessage[\"taskEvent\"];\n private readonly timer: Timer.Interface;\n private readonly internalToken: string;\n private readonly postMessage: (msg: WorkerToParentMessage) => void;\n\n public constructor(message: StartMessage, postMessage: (msg: WorkerToParentMessage) => void) {\n this.serverUrl = message.serverUrl;\n this.taskEvent = message.taskEvent;\n this.timer = new ProcessTimer(message.maxDurationMs);\n this.internalToken = message.internalToken;\n this.postMessage = postMessage;\n }\n\n public async run(): Promise<void> {\n const taskId = this.taskEvent.webinyTaskId;\n\n try {\n if (this.taskEvent.delay > 0) {\n await this.wait(this.taskEvent.delay * 1000);\n }\n\n let input: Record<string, unknown> = {};\n let continueLoop = true;\n\n while (continueLoop) {\n if (this.timer.getRemainingMilliseconds() <= 0) {\n this.postMessage({\n type: \"error\",\n taskId,\n error: \"Task exceeded maximum duration.\"\n });\n return;\n }\n\n const payload = {\n ...this.taskEvent,\n input\n };\n\n const response = await this.post(payload);\n\n switch (response.status) {\n case \"continue\": {\n input = response.input || {};\n if (response.wait && response.wait > 0) {\n await this.wait(response.wait * 1000);\n }\n break;\n }\n case \"done\": {\n this.postMessage({ type: \"done\", taskId, result: response });\n continueLoop = false;\n break;\n }\n case \"error\": {\n this.postMessage({\n type: \"error\",\n taskId,\n error: JSON.stringify(response)\n });\n continueLoop = false;\n break;\n }\n default: {\n this.postMessage({\n type: \"error\",\n taskId,\n error: `Unknown response status: ${response.status}`\n });\n continueLoop = false;\n break;\n }\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.postMessage({ type: \"error\", taskId, error: message });\n }\n }\n\n private post(payload: Record<string, unknown>): Promise<TaskResponse> {\n return new Promise((resolve, reject) => {\n const url = new URL(this.serverUrl);\n const body = JSON.stringify(payload);\n\n /*\n * The server answers only once the iteration finishes, so this is how long one iteration\n * may take. It used to be a fixed 60s, well short of the task's own budget: an AI call\n * that retried through a provider overload failed the task for running a little over a\n * minute. Bounded by what is left of the budget instead.\n *\n * A deadline, not the request's `timeout` option. That one only measures silence and\n * restarts on every chunk, so a response trickling in could outlive the budget and still\n * come back as `done`.\n */\n const remaining = this.timer.getRemainingMilliseconds();\n let deadline: NodeJS.Timeout | undefined;\n\n const fail = (error: Error) => {\n clearTimeout(deadline);\n reject(error);\n };\n\n const req = http.request(\n {\n hostname: url.hostname,\n port: url.port,\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(body),\n \"x-webiny-background-task-token\": this.internalToken\n }\n },\n res => {\n let data = \"\";\n res.on(\"data\", chunk => {\n data += chunk;\n });\n // Where a deadline that fires mid-response surfaces.\n res.on(\"error\", fail);\n res.on(\"end\", () => {\n clearTimeout(deadline);\n if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {\n reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n return;\n }\n try {\n resolve(JSON.parse(data) as TaskResponse);\n } catch {\n reject(new Error(`Invalid JSON response: ${data}`));\n }\n });\n }\n );\n\n deadline = setTimeout(() => {\n const exceeded = new Error(\"Task exceeded maximum duration.\");\n req.destroy(exceeded);\n fail(exceeded);\n }, remaining);\n\n req.on(\"error\", fail);\n req.write(body);\n req.end();\n });\n }\n\n private wait(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"names":["TaskOrchestrator","message","postMessage","ProcessTimer","taskId","input","continueLoop","payload","response","JSON","err","Error","String","Promise","resolve","reject","url","URL","body","remaining","deadline","fail","error","clearTimeout","req","http","Buffer","res","data","chunk","setTimeout","exceeded","ms"],"mappings":";;AAeO,MAAMA;IAOT,YAAmBC,OAAqB,EAAEC,WAAiD,CAAE;QACzF,IAAI,CAAC,SAAS,GAAGD,QAAQ,SAAS;QAClC,IAAI,CAAC,SAAS,GAAGA,QAAQ,SAAS;QAClC,IAAI,CAAC,KAAK,GAAG,IAAIE,aAAaF,QAAQ,aAAa;QACnD,IAAI,CAAC,aAAa,GAAGA,QAAQ,aAAa;QAC1C,IAAI,CAAC,WAAW,GAAGC;IACvB;IAEA,MAAa,MAAqB;QAC9B,MAAME,SAAS,IAAI,CAAC,SAAS,CAAC,YAAY;QAE1C,IAAI;YACA,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,GACvB,MAAM,IAAI,CAAC,IAAI,CAAC,AAAuB,OAAvB,IAAI,CAAC,SAAS,CAAC,KAAK;YAGxC,IAAIC,QAAiC,CAAC;YACtC,IAAIC,eAAe;YAEnB,MAAOA,aAAc;gBACjB,IAAI,IAAI,CAAC,KAAK,CAAC,wBAAwB,MAAM,GAAG,YAC5C,IAAI,CAAC,WAAW,CAAC;oBACb,MAAM;oBACNF;oBACA,OAAO;gBACX;gBAIJ,MAAMG,UAAU;oBACZ,GAAG,IAAI,CAAC,SAAS;oBACjBF;gBACJ;gBAEA,MAAMG,WAAW,MAAM,IAAI,CAAC,IAAI,CAACD;gBAEjC,OAAQC,SAAS,MAAM;oBACnB,KAAK;wBACDH,QAAQG,SAAS,KAAK,IAAI,CAAC;wBAC3B,IAAIA,SAAS,IAAI,IAAIA,SAAS,IAAI,GAAG,GACjC,MAAM,IAAI,CAAC,IAAI,CAACA,AAAgB,OAAhBA,SAAS,IAAI;wBAEjC;oBAEJ,KAAK;wBACD,IAAI,CAAC,WAAW,CAAC;4BAAE,MAAM;4BAAQJ;4BAAQ,QAAQI;wBAAS;wBAC1DF,eAAe;wBACf;oBAEJ,KAAK;wBACD,IAAI,CAAC,WAAW,CAAC;4BACb,MAAM;4BACNF;4BACA,OAAOK,KAAK,SAAS,CAACD;wBAC1B;wBACAF,eAAe;wBACf;oBAEJ;wBACI,IAAI,CAAC,WAAW,CAAC;4BACb,MAAM;4BACNF;4BACA,OAAO,CAAC,yBAAyB,EAAEI,SAAS,MAAM,EAAE;wBACxD;wBACAF,eAAe;wBACf;gBAER;YACJ;QACJ,EAAE,OAAOI,KAAK;YACV,MAAMT,UAAUS,eAAeC,QAAQD,IAAI,OAAO,GAAGE,OAAOF;YAC5D,IAAI,CAAC,WAAW,CAAC;gBAAE,MAAM;gBAASN;gBAAQ,OAAOH;YAAQ;QAC7D;IACJ;IAEQ,KAAKM,OAAgC,EAAyB;QAClE,OAAO,IAAIM,QAAQ,CAACC,SAASC;YACzB,MAAMC,MAAM,IAAIC,IAAI,IAAI,CAAC,SAAS;YAClC,MAAMC,OAAOT,KAAK,SAAS,CAACF;YAY5B,MAAMY,YAAY,IAAI,CAAC,KAAK,CAAC,wBAAwB;YACrD,IAAIC;YAEJ,MAAMC,OAAO,CAACC;gBACVC,aAAaH;gBACbL,OAAOO;YACX;YAEA,MAAME,MAAMC,UAAAA,OAAY,CACpB;gBACI,UAAUT,IAAI,QAAQ;gBACtB,MAAMA,IAAI,IAAI;gBACd,MAAMA,IAAI,QAAQ;gBAClB,QAAQ;gBACR,SAAS;oBACL,gBAAgB;oBAChB,kBAAkBU,OAAO,UAAU,CAACR;oBACpC,kCAAkC,IAAI,CAAC,aAAa;gBACxD;YACJ,GACAS,CAAAA;gBACI,IAAIC,OAAO;gBACXD,IAAI,EAAE,CAAC,QAAQE,CAAAA;oBACXD,QAAQC;gBACZ;gBAEAF,IAAI,EAAE,CAAC,SAASN;gBAChBM,IAAI,EAAE,CAAC,OAAO;oBACVJ,aAAaH;oBACb,IAAI,CAACO,IAAI,UAAU,IAAIA,IAAI,UAAU,GAAG,OAAOA,IAAI,UAAU,IAAI,KAAK,YAClEZ,OAAO,IAAIJ,MAAM,CAAC,KAAK,EAAEgB,IAAI,UAAU,CAAC,EAAE,EAAEC,MAAM;oBAGtD,IAAI;wBACAd,QAAQL,KAAK,KAAK,CAACmB;oBACvB,EAAE,OAAM;wBACJb,OAAO,IAAIJ,MAAM,CAAC,uBAAuB,EAAEiB,MAAM;oBACrD;gBACJ;YACJ;YAGJR,WAAWU,WAAW;gBAClB,MAAMC,WAAW,IAAIpB,MAAM;gBAC3Ba,IAAI,OAAO,CAACO;gBACZV,KAAKU;YACT,GAAGZ;YAEHK,IAAI,EAAE,CAAC,SAASH;YAChBG,IAAI,KAAK,CAACN;YACVM,IAAI,GAAG;QACX;IACJ;IAEQ,KAAKQ,EAAU,EAAiB;QACpC,OAAO,IAAInB,QAAQC,CAAAA,UAAWgB,WAAWhB,SAASkB;IACtD;AACJ"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface TaskEventPayload {
|
|
2
|
+
readonly webinyTaskId: string;
|
|
3
|
+
readonly webinyTaskDefinitionId: string;
|
|
4
|
+
readonly tenant: string;
|
|
5
|
+
readonly delay: number;
|
|
6
|
+
readonly endpoint: string;
|
|
7
|
+
readonly executionName: string;
|
|
8
|
+
readonly stateMachineId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StartMessage {
|
|
11
|
+
readonly type: "start";
|
|
12
|
+
readonly taskEvent: TaskEventPayload;
|
|
13
|
+
readonly serverUrl: string;
|
|
14
|
+
readonly maxDurationMs: number;
|
|
15
|
+
readonly internalToken: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DoneMessage {
|
|
18
|
+
readonly type: "done";
|
|
19
|
+
readonly taskId: string;
|
|
20
|
+
readonly result: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface ErrorMessage {
|
|
23
|
+
readonly type: "error";
|
|
24
|
+
readonly taskId: string;
|
|
25
|
+
readonly error: string;
|
|
26
|
+
}
|
|
27
|
+
export type WorkerToParentMessage = DoneMessage | ErrorMessage;
|
|
28
|
+
export type ParentToWorkerMessage = StartMessage;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { TaskOrchestrator } from "./TaskOrchestrator.js";
|
|
3
|
+
if (!parentPort) throw new Error("workerEntry must be run inside a worker thread.");
|
|
4
|
+
const port = parentPort;
|
|
5
|
+
port.on("message", async (message)=>{
|
|
6
|
+
if ("start" !== message.type) return;
|
|
7
|
+
const orchestrator = new TaskOrchestrator(message, (msg)=>port.postMessage(msg));
|
|
8
|
+
await orchestrator.run();
|
|
9
|
+
process.exit(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=workerEntry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker/workerEntry.js","sources":["../../src/worker/workerEntry.ts"],"sourcesContent":["import { parentPort } from \"node:worker_threads\";\nimport type { ParentToWorkerMessage } from \"./TaskOrchestratorMessage.js\";\nimport { TaskOrchestrator } from \"./TaskOrchestrator.js\";\n\nif (!parentPort) {\n throw new Error(\"workerEntry must be run inside a worker thread.\");\n}\n\nconst port = parentPort;\n\nport.on(\"message\", async (message: ParentToWorkerMessage) => {\n if (message.type !== \"start\") {\n return;\n }\n\n const orchestrator = new TaskOrchestrator(message, msg => port.postMessage(msg));\n await orchestrator.run();\n\n process.exit(0);\n});\n"],"names":["parentPort","Error","port","message","orchestrator","TaskOrchestrator","msg","process"],"mappings":";;AAIA,IAAI,CAACA,YACD,MAAM,IAAIC,MAAM;AAGpB,MAAMC,OAAOF;AAEbE,KAAK,EAAE,CAAC,WAAW,OAAOC;IACtB,IAAIA,AAAiB,YAAjBA,QAAQ,IAAI,EACZ;IAGJ,MAAMC,eAAe,IAAIC,iBAAiBF,SAASG,CAAAA,MAAOJ,KAAK,WAAW,CAACI;IAC3E,MAAMF,aAAa,GAAG;IAEtBG,QAAQ,IAAI,CAAC;AACjB"}
|
package/domain/TaskLoop.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { IBackgroundTaskEvent } from "@webiny/event-handler-core";
|
|
2
|
-
/**
|
|
3
|
-
* Drives a triggered task to completion inside this process: runs one iteration, waits as long as
|
|
4
|
-
* the iteration asks, and runs the next until the task stops asking to continue. The standalone
|
|
5
|
-
* counterpart of the Step Functions state machine that drives a task on AWS.
|
|
6
|
-
*
|
|
7
|
-
* `start` returns at once. The task runs after the request that triggered it has finished.
|
|
8
|
-
*/
|
|
9
|
-
export interface ITaskLoop {
|
|
10
|
-
start(event: IBackgroundTaskEvent): void;
|
|
11
|
-
}
|
|
12
|
-
export declare const TaskLoop: import("@webiny/di").Abstraction<ITaskLoop>;
|
|
13
|
-
export declare namespace TaskLoop {
|
|
14
|
-
type Interface = ITaskLoop;
|
|
15
|
-
}
|
package/domain/TaskLoop.js
DELETED
package/domain/TaskLoop.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domain/TaskLoop.js","sources":["../../src/domain/TaskLoop.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\nimport type { IBackgroundTaskEvent } from \"@webiny/event-handler-core\";\n\n/**\n * Drives a triggered task to completion inside this process: runs one iteration, waits as long as\n * the iteration asks, and runs the next until the task stops asking to continue. The standalone\n * counterpart of the Step Functions state machine that drives a task on AWS.\n *\n * `start` returns at once. The task runs after the request that triggered it has finished.\n */\nexport interface ITaskLoop {\n start(event: IBackgroundTaskEvent): void;\n}\n\nexport const TaskLoop = createAbstraction<ITaskLoop>(\"BackgroundTasks/TaskLoop\");\n\nexport namespace TaskLoop {\n export type Interface = ITaskLoop;\n}\n"],"names":["TaskLoop","createAbstraction"],"mappings":";AAcO,MAAMA,WAAWC,kBAA6B"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Container } from "@webiny/feature/api";
|
|
2
|
-
import { BackgroundTaskEventHandler } from "@webiny/event-handler-core";
|
|
3
|
-
import type { EventContext } from "@webiny/event-handler-core";
|
|
4
|
-
import type { IBackgroundTaskEvent } from "@webiny/event-handler-core";
|
|
5
|
-
import { RawTenantId } from "@webiny/api-core/features/requestContext/index.js";
|
|
6
|
-
import { RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
7
|
-
/**
|
|
8
|
-
* Runs one iteration of a background task in the request container the dispatcher built for it.
|
|
9
|
-
* The standalone counterpart of `BackgroundTaskLambdaHandler`: same tenant set-up and runner, with
|
|
10
|
-
* a process timer in place of the Lambda countdown.
|
|
11
|
-
*/
|
|
12
|
-
declare class InProcessBackgroundTaskHandlerImpl implements BackgroundTaskEventHandler.Interface {
|
|
13
|
-
private readonly container;
|
|
14
|
-
private readonly rawTenantId;
|
|
15
|
-
private readonly tenantLoader;
|
|
16
|
-
constructor(container: Container, rawTenantId: RawTenantId.Interface, tenantLoader: RequestTenantLoader.Interface);
|
|
17
|
-
execute(eventCtx: EventContext<IBackgroundTaskEvent>): Promise<unknown>;
|
|
18
|
-
}
|
|
19
|
-
export declare const InProcessBackgroundTaskHandler: typeof InProcessBackgroundTaskHandlerImpl & {
|
|
20
|
-
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core/features/backgroundTask/BackgroundTaskEventHandler").IBackgroundTaskEventHandler>;
|
|
21
|
-
};
|
|
22
|
-
export {};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { BackgroundTaskEventHandler, RequestContainer } from "@webiny/event-handler-core";
|
|
2
|
-
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
3
|
-
import { TaskRunner } from "@webiny/background-tasks/api/runner/index.js";
|
|
4
|
-
import { TaskEventValidation } from "@webiny/background-tasks/api/runner/TaskEventValidation.js";
|
|
5
|
-
import { ProcessTimer } from "../timer/ProcessTimer.js";
|
|
6
|
-
class InProcessBackgroundTaskHandlerImpl {
|
|
7
|
-
constructor(container, rawTenantId, tenantLoader){
|
|
8
|
-
this.container = container;
|
|
9
|
-
this.rawTenantId = rawTenantId;
|
|
10
|
-
this.tenantLoader = tenantLoader;
|
|
11
|
-
}
|
|
12
|
-
async execute(eventCtx) {
|
|
13
|
-
const taskEvent = eventCtx.event;
|
|
14
|
-
this.rawTenantId.set(taskEvent.tenant);
|
|
15
|
-
await this.tenantLoader.establish();
|
|
16
|
-
const ctx = {
|
|
17
|
-
container: this.container
|
|
18
|
-
};
|
|
19
|
-
const timer = new ProcessTimer();
|
|
20
|
-
const runner = new TaskRunner(ctx, timer, new TaskEventValidation());
|
|
21
|
-
return runner.run(taskEvent);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
const InProcessBackgroundTaskHandler = BackgroundTaskEventHandler.createImplementation({
|
|
25
|
-
implementation: InProcessBackgroundTaskHandlerImpl,
|
|
26
|
-
dependencies: [
|
|
27
|
-
RequestContainer,
|
|
28
|
-
RawTenantId,
|
|
29
|
-
RequestTenantLoader
|
|
30
|
-
]
|
|
31
|
-
});
|
|
32
|
-
export { InProcessBackgroundTaskHandler };
|
|
33
|
-
|
|
34
|
-
//# sourceMappingURL=InProcessBackgroundTaskHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handlers/InProcessBackgroundTaskHandler.js","sources":["../../src/handlers/InProcessBackgroundTaskHandler.ts"],"sourcesContent":["import type { Container } from \"@webiny/feature/api\";\nimport { BackgroundTaskEventHandler } from \"@webiny/event-handler-core\";\nimport { RequestContainer } from \"@webiny/event-handler-core\";\nimport type { EventContext } from \"@webiny/event-handler-core\";\nimport type { IBackgroundTaskEvent } from \"@webiny/event-handler-core\";\nimport { RawTenantId } from \"@webiny/api-core/features/requestContext/index.js\";\nimport { RequestTenantLoader } from \"@webiny/api-core/features/requestContext/index.js\";\nimport { TaskRunner } from \"@webiny/background-tasks/api/runner/index.js\";\nimport { TaskEventValidation } from \"@webiny/background-tasks/api/runner/TaskEventValidation.js\";\nimport type { Context } from \"@webiny/background-tasks/api/types.js\";\nimport { ProcessTimer } from \"~/timer/ProcessTimer.js\";\n\n/**\n * Runs one iteration of a background task in the request container the dispatcher built for it.\n * The standalone counterpart of `BackgroundTaskLambdaHandler`: same tenant set-up and runner, with\n * a process timer in place of the Lambda countdown.\n */\nclass InProcessBackgroundTaskHandlerImpl implements BackgroundTaskEventHandler.Interface {\n public constructor(\n private readonly container: Container,\n private readonly rawTenantId: RawTenantId.Interface,\n private readonly tenantLoader: RequestTenantLoader.Interface\n ) {}\n\n public async execute(eventCtx: EventContext<IBackgroundTaskEvent>): Promise<unknown> {\n const taskEvent = eventCtx.event;\n\n this.rawTenantId.set(taskEvent.tenant);\n await this.tenantLoader.establish();\n\n // TaskRunner still takes the legacy context object, which now only carries the container.\n const ctx: Record<string, any> = { container: this.container };\n\n const timer = new ProcessTimer();\n const runner = new TaskRunner(ctx as Context, timer, new TaskEventValidation());\n\n // The loop reads `status`, `wait` and `delay` from this to decide what happens next.\n return runner.run(taskEvent);\n }\n}\n\nexport const InProcessBackgroundTaskHandler = BackgroundTaskEventHandler.createImplementation({\n implementation: InProcessBackgroundTaskHandlerImpl,\n dependencies: [RequestContainer, RawTenantId, RequestTenantLoader]\n});\n"],"names":["InProcessBackgroundTaskHandlerImpl","container","rawTenantId","tenantLoader","eventCtx","taskEvent","ctx","timer","ProcessTimer","runner","TaskRunner","TaskEventValidation","InProcessBackgroundTaskHandler","BackgroundTaskEventHandler","RequestContainer","RawTenantId","RequestTenantLoader"],"mappings":";;;;;AAiBA,MAAMA;IACF,YACqBC,SAAoB,EACpBC,WAAkC,EAClCC,YAA2C,CAC9D;aAHmBF,SAAS,GAATA;aACAC,WAAW,GAAXA;aACAC,YAAY,GAAZA;IAClB;IAEH,MAAa,QAAQC,QAA4C,EAAoB;QACjF,MAAMC,YAAYD,SAAS,KAAK;QAEhC,IAAI,CAAC,WAAW,CAAC,GAAG,CAACC,UAAU,MAAM;QACrC,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS;QAGjC,MAAMC,MAA2B;YAAE,WAAW,IAAI,CAAC,SAAS;QAAC;QAE7D,MAAMC,QAAQ,IAAIC;QAClB,MAAMC,SAAS,IAAIC,WAAWJ,KAAgBC,OAAO,IAAII;QAGzD,OAAOF,OAAO,GAAG,CAACJ;IACtB;AACJ;AAEO,MAAMO,iCAAiCC,2BAA2B,oBAAoB,CAAC;IAC1F,gBAAgBb;IAChB,cAAc;QAACc;QAAkBC;QAAaC;KAAoB;AACtE"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { EventDispatcher } from "@webiny/event-handler-core";
|
|
2
|
-
import type { IBackgroundTaskEvent } from "@webiny/event-handler-core";
|
|
3
|
-
import { TaskLoop } from "../domain/TaskLoop.js";
|
|
4
|
-
/**
|
|
5
|
-
* Each iteration is one dispatched `BackgroundTaskEvent`, handled in a fresh request container like
|
|
6
|
-
* an HTTP request would be. Root singleton: one loop drives every task in the process.
|
|
7
|
-
*/
|
|
8
|
-
declare class DispatchingTaskLoopImpl implements TaskLoop.Interface {
|
|
9
|
-
private readonly dispatcher;
|
|
10
|
-
constructor(dispatcher: EventDispatcher.Interface);
|
|
11
|
-
start(event: IBackgroundTaskEvent): void;
|
|
12
|
-
private run;
|
|
13
|
-
}
|
|
14
|
-
export declare const DispatchingTaskLoop: typeof DispatchingTaskLoopImpl & {
|
|
15
|
-
__abstraction: import("@webiny/di").Abstraction<import("~/domain/TaskLoop.js").ITaskLoop>;
|
|
16
|
-
};
|
|
17
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { EventDispatcher } from "@webiny/event-handler-core";
|
|
2
|
-
import { TaskLoop } from "../domain/TaskLoop.js";
|
|
3
|
-
import { ProcessTimer } from "../timer/ProcessTimer.js";
|
|
4
|
-
const MAX_TASK_DURATION_MS = 86400000;
|
|
5
|
-
function sleep(ms) {
|
|
6
|
-
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
7
|
-
}
|
|
8
|
-
class DispatchingTaskLoopImpl {
|
|
9
|
-
constructor(dispatcher){
|
|
10
|
-
this.dispatcher = dispatcher;
|
|
11
|
-
}
|
|
12
|
-
start(event) {
|
|
13
|
-
this.run(event);
|
|
14
|
-
}
|
|
15
|
-
async run(firstEvent) {
|
|
16
|
-
const taskId = firstEvent.webinyTaskId;
|
|
17
|
-
const timer = new ProcessTimer(MAX_TASK_DURATION_MS);
|
|
18
|
-
let event = firstEvent;
|
|
19
|
-
while(timer.getRemainingMilliseconds() > 0){
|
|
20
|
-
let result;
|
|
21
|
-
try {
|
|
22
|
-
result = await this.dispatcher.dispatch(event);
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.error(`Background task "${taskId}" failed:`, error);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const status = result?.status;
|
|
28
|
-
if ("done" === status || "aborted" === status) return;
|
|
29
|
-
if ("continue" !== status) return void console.error(`Background task "${taskId}" stopped with status "${status}":`, result);
|
|
30
|
-
event = {
|
|
31
|
-
...event,
|
|
32
|
-
delay: result?.delay ?? -1
|
|
33
|
-
};
|
|
34
|
-
const wait = result?.wait ?? 0;
|
|
35
|
-
if (wait > 0) await sleep(1000 * wait);
|
|
36
|
-
}
|
|
37
|
-
console.error(`Background task "${taskId}" exceeded the maximum duration.`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const DispatchingTaskLoop = TaskLoop.createImplementation({
|
|
41
|
-
implementation: DispatchingTaskLoopImpl,
|
|
42
|
-
dependencies: [
|
|
43
|
-
EventDispatcher
|
|
44
|
-
]
|
|
45
|
-
});
|
|
46
|
-
export { DispatchingTaskLoop };
|
|
47
|
-
|
|
48
|
-
//# sourceMappingURL=DispatchingTaskLoop.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loop/DispatchingTaskLoop.js","sources":["../../src/loop/DispatchingTaskLoop.ts"],"sourcesContent":["import { EventDispatcher } from \"@webiny/event-handler-core\";\nimport type { IBackgroundTaskEvent } from \"@webiny/event-handler-core\";\nimport { TaskLoop } from \"~/domain/TaskLoop.js\";\nimport { ProcessTimer } from \"~/timer/ProcessTimer.js\";\n\n// 24 hours, the most one task may run in total.\nconst MAX_TASK_DURATION_MS = 86_400_000;\n\n// What the runner answers for one iteration; only the fields the loop reads.\ninterface IIterationResult {\n status?: string;\n wait?: number;\n delay?: number;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Each iteration is one dispatched `BackgroundTaskEvent`, handled in a fresh request container like\n * an HTTP request would be. Root singleton: one loop drives every task in the process.\n */\nclass DispatchingTaskLoopImpl implements TaskLoop.Interface {\n public constructor(private readonly dispatcher: EventDispatcher.Interface) {}\n\n public start(event: IBackgroundTaskEvent): void {\n void this.run(event);\n }\n\n private async run(firstEvent: IBackgroundTaskEvent): Promise<void> {\n const taskId = firstEvent.webinyTaskId;\n const timer = new ProcessTimer(MAX_TASK_DURATION_MS);\n let event = firstEvent;\n\n while (timer.getRemainingMilliseconds() > 0) {\n let result: IIterationResult | undefined;\n try {\n result = await this.dispatcher.dispatch<IIterationResult>(event);\n } catch (error) {\n console.error(`Background task \"${taskId}\" failed:`, error);\n return;\n }\n\n const status = result?.status;\n if (status === \"done\" || status === \"aborted\") {\n return;\n }\n if (status !== \"continue\") {\n console.error(\n `Background task \"${taskId}\" stopped with status \"${status}\":`,\n result\n );\n return;\n }\n\n /*\n * The first event carries the trigger's delay, and the runner answers it with `continue`\n * and `wait: delay` without running anything. Every later event must carry the delay the\n * runner sent back (-1), as the Step Functions state does on AWS. Resending the original\n * delay would make the runner answer `continue` forever.\n */\n event = { ...event, delay: result?.delay ?? -1 };\n\n const wait = result?.wait ?? 0;\n if (wait > 0) {\n await sleep(wait * 1000);\n }\n }\n\n console.error(`Background task \"${taskId}\" exceeded the maximum duration.`);\n }\n}\n\nexport const DispatchingTaskLoop = TaskLoop.createImplementation({\n implementation: DispatchingTaskLoopImpl,\n dependencies: [EventDispatcher]\n});\n"],"names":["MAX_TASK_DURATION_MS","sleep","ms","Promise","resolve","setTimeout","DispatchingTaskLoopImpl","dispatcher","event","firstEvent","taskId","timer","ProcessTimer","result","error","console","status","wait","DispatchingTaskLoop","TaskLoop","EventDispatcher"],"mappings":";;;AAMA,MAAMA,uBAAuB;AAS7B,SAASC,MAAMC,EAAU;IACrB,OAAO,IAAIC,QAAQC,CAAAA,UAAWC,WAAWD,SAASF;AACtD;AAMA,MAAMI;IACF,YAAoCC,UAAqC,CAAE;aAAvCA,UAAU,GAAVA;IAAwC;IAErE,MAAMC,KAA2B,EAAQ;QACvC,IAAI,CAAC,GAAG,CAACA;IAClB;IAEA,MAAc,IAAIC,UAAgC,EAAiB;QAC/D,MAAMC,SAASD,WAAW,YAAY;QACtC,MAAME,QAAQ,IAAIC,aAAaZ;QAC/B,IAAIQ,QAAQC;QAEZ,MAAOE,MAAM,wBAAwB,KAAK,EAAG;YACzC,IAAIE;YACJ,IAAI;gBACAA,SAAS,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAmBL;YAC9D,EAAE,OAAOM,OAAO;gBACZC,QAAQ,KAAK,CAAC,CAAC,iBAAiB,EAAEL,OAAO,SAAS,CAAC,EAAEI;gBACrD;YACJ;YAEA,MAAME,SAASH,QAAQ;YACvB,IAAIG,AAAW,WAAXA,UAAqBA,AAAW,cAAXA,QACrB;YAEJ,IAAIA,AAAW,eAAXA,QAAuB,YACvBD,QAAQ,KAAK,CACT,CAAC,iBAAiB,EAAEL,OAAO,uBAAuB,EAAEM,OAAO,EAAE,CAAC,EAC9DH;YAWRL,QAAQ;gBAAE,GAAGA,KAAK;gBAAE,OAAOK,QAAQ,SAAS;YAAG;YAE/C,MAAMI,OAAOJ,QAAQ,QAAQ;YAC7B,IAAII,OAAO,GACP,MAAMhB,MAAMgB,AAAO,OAAPA;QAEpB;QAEAF,QAAQ,KAAK,CAAC,CAAC,iBAAiB,EAAEL,OAAO,gCAAgC,CAAC;IAC9E;AACJ;AAEO,MAAMQ,sBAAsBC,SAAS,oBAAoB,CAAC;IAC7D,gBAAgBb;IAChB,cAAc;QAACc;KAAgB;AACnC"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
|
|
2
|
-
import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
|
|
3
|
-
import { TaskLoop } from "../domain/TaskLoop.js";
|
|
4
|
-
/**
|
|
5
|
-
* Starts a triggered task in this process. Builds the same event Step Functions would deliver on
|
|
6
|
-
* AWS and hands it to the task loop. `endpoint`, `executionName` and `stateMachineId` only satisfy
|
|
7
|
-
* the runner's event validation; nothing reads them off AWS.
|
|
8
|
-
*/
|
|
9
|
-
declare class InProcessTaskServiceImpl implements TaskService.Interface {
|
|
10
|
-
private readonly tenantContext;
|
|
11
|
-
private readonly taskLoop;
|
|
12
|
-
constructor(tenantContext: TenantContext.Interface, taskLoop: TaskLoop.Interface);
|
|
13
|
-
send(task: TaskService.SendTaskParams, delay: number): Promise<unknown>;
|
|
14
|
-
fetch(): Promise<null>;
|
|
15
|
-
}
|
|
16
|
-
export declare const InProcessTaskService: typeof InProcessTaskServiceImpl & {
|
|
17
|
-
__abstraction: import("@webiny/di").Abstraction<import("@webiny/background-tasks/api/domain/TaskService.js").ITaskService>;
|
|
18
|
-
};
|
|
19
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
|
|
2
|
-
import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
|
|
3
|
-
import { TaskLoop } from "../domain/TaskLoop.js";
|
|
4
|
-
class InProcessTaskServiceImpl {
|
|
5
|
-
constructor(tenantContext, taskLoop){
|
|
6
|
-
this.tenantContext = tenantContext;
|
|
7
|
-
this.taskLoop = taskLoop;
|
|
8
|
-
}
|
|
9
|
-
async send(task, delay) {
|
|
10
|
-
const tenant = this.tenantContext.getTenant();
|
|
11
|
-
if (!tenant) return null;
|
|
12
|
-
const event = {
|
|
13
|
-
webinyTaskId: task.id,
|
|
14
|
-
webinyTaskDefinitionId: task.definitionId,
|
|
15
|
-
tenant: tenant.id,
|
|
16
|
-
delay,
|
|
17
|
-
endpoint: "in-process",
|
|
18
|
-
executionName: task.id,
|
|
19
|
-
stateMachineId: ""
|
|
20
|
-
};
|
|
21
|
-
this.taskLoop.start(event);
|
|
22
|
-
return {
|
|
23
|
-
taskId: task.id
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
async fetch() {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const InProcessTaskService = TaskService.createImplementation({
|
|
31
|
-
implementation: InProcessTaskServiceImpl,
|
|
32
|
-
dependencies: [
|
|
33
|
-
TenantContext,
|
|
34
|
-
TaskLoop
|
|
35
|
-
]
|
|
36
|
-
});
|
|
37
|
-
export { InProcessTaskService };
|
|
38
|
-
|
|
39
|
-
//# sourceMappingURL=InProcessTaskService.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service/InProcessTaskService.js","sources":["../../src/service/InProcessTaskService.ts"],"sourcesContent":["import type { IBackgroundTaskEvent } from \"@webiny/event-handler-core\";\nimport { TaskService } from \"@webiny/background-tasks/api/domain/TaskService.js\";\nimport { TenantContext } from \"@webiny/api-core/exports/api/tenancy.js\";\nimport { TaskLoop } from \"~/domain/TaskLoop.js\";\n\n/**\n * Starts a triggered task in this process. Builds the same event Step Functions would deliver on\n * AWS and hands it to the task loop. `endpoint`, `executionName` and `stateMachineId` only satisfy\n * the runner's event validation; nothing reads them off AWS.\n */\nclass InProcessTaskServiceImpl implements TaskService.Interface {\n public constructor(\n private readonly tenantContext: TenantContext.Interface,\n private readonly taskLoop: TaskLoop.Interface\n ) {}\n\n public async send(task: TaskService.SendTaskParams, delay: number): Promise<unknown> {\n const tenant = this.tenantContext.getTenant();\n if (!tenant) {\n return null;\n }\n\n const event: IBackgroundTaskEvent = {\n webinyTaskId: task.id,\n webinyTaskDefinitionId: task.definitionId,\n tenant: tenant.id,\n delay,\n endpoint: \"in-process\",\n executionName: task.id,\n stateMachineId: \"\"\n };\n\n this.taskLoop.start(event);\n\n return { taskId: task.id };\n }\n\n public async fetch(): Promise<null> {\n // A running task has no execution record outside the task itself.\n return null;\n }\n}\n\nexport const InProcessTaskService = TaskService.createImplementation({\n implementation: InProcessTaskServiceImpl,\n dependencies: [TenantContext, TaskLoop]\n});\n"],"names":["InProcessTaskServiceImpl","tenantContext","taskLoop","task","delay","tenant","event","InProcessTaskService","TaskService","TenantContext","TaskLoop"],"mappings":";;;AAUA,MAAMA;IACF,YACqBC,aAAsC,EACtCC,QAA4B,CAC/C;aAFmBD,aAAa,GAAbA;aACAC,QAAQ,GAARA;IAClB;IAEH,MAAa,KAAKC,IAAgC,EAAEC,KAAa,EAAoB;QACjF,MAAMC,SAAS,IAAI,CAAC,aAAa,CAAC,SAAS;QAC3C,IAAI,CAACA,QACD,OAAO;QAGX,MAAMC,QAA8B;YAChC,cAAcH,KAAK,EAAE;YACrB,wBAAwBA,KAAK,YAAY;YACzC,QAAQE,OAAO,EAAE;YACjBD;YACA,UAAU;YACV,eAAeD,KAAK,EAAE;YACtB,gBAAgB;QACpB;QAEA,IAAI,CAAC,QAAQ,CAAC,KAAK,CAACG;QAEpB,OAAO;YAAE,QAAQH,KAAK,EAAE;QAAC;IAC7B;IAEA,MAAa,QAAuB;QAEhC,OAAO;IACX;AACJ;AAEO,MAAMI,uBAAuBC,YAAY,oBAAoB,CAAC;IACjE,gBAAgBR;IAChB,cAAc;QAACS;QAAeC;KAAS;AAC3C"}
|