@webiny/background-tasks-standalone 6.6.0-alpha.4
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 +4 -0
- package/BackgroundTasksStandaloneFeature.js +18 -0
- package/BackgroundTasksStandaloneFeature.js.map +1 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/domain/InternalToken.d.ts +8 -0
- package/domain/InternalToken.js +5 -0
- package/domain/InternalToken.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +38 -0
- package/routes/BackgroundTaskRoute.d.ts +25 -0
- package/routes/BackgroundTaskRoute.js +69 -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/timer/ProcessTimer.d.ts +8 -0
- package/timer/ProcessTimer.js +19 -0
- package/timer/ProcessTimer.js.map +1 -0
- package/worker/TaskOrchestrator.d.ts +12 -0
- package/worker/TaskOrchestrator.js +111 -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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/api";
|
|
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
|
+
const BackgroundTasksStandaloneFeature = createFeature({
|
|
7
|
+
name: "BackgroundTasksServer",
|
|
8
|
+
register (container) {
|
|
9
|
+
container.registerInstance(InternalToken, {
|
|
10
|
+
value: uuid()
|
|
11
|
+
});
|
|
12
|
+
container.register(WorkerService);
|
|
13
|
+
container.register(BackgroundTaskRouteDefinition);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
export { BackgroundTasksStandaloneFeature };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=BackgroundTasksStandaloneFeature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Webiny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @webiny/background-tasks-standalone
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This package is part of the [Webiny](https://www.webiny.com) monorepo.
|
|
5
|
+
> It’s **included in every Webiny project by default** and is not meant to be used as a standalone package.
|
|
6
|
+
|
|
7
|
+
📘 **Documentation:** [https://www.webiny.com/docs](https://www.webiny.com/docs)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
_This README file is automatically generated during the publish process._
|
|
@@ -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/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BackgroundTasksStandaloneFeature } from "./BackgroundTasksStandaloneFeature.js";
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BackgroundTasksStandaloneFeature } from "./BackgroundTasksStandaloneFeature.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webiny/background-tasks-standalone",
|
|
3
|
+
"version": "6.6.0-alpha.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./index.js",
|
|
7
|
+
"./*": "./*"
|
|
8
|
+
},
|
|
9
|
+
"description": "Node server transport for Webiny background tasks (worker-thread orchestrator).",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/webiny/webiny-js.git",
|
|
13
|
+
"directory": "packages/background-tasks-standalone"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@webiny/api-core": "6.6.0-alpha.4",
|
|
18
|
+
"@webiny/api-graphql": "6.6.0-alpha.4",
|
|
19
|
+
"@webiny/background-tasks": "6.6.0-alpha.4",
|
|
20
|
+
"@webiny/event-handler-core": "6.6.0-alpha.4",
|
|
21
|
+
"@webiny/feature": "6.6.0-alpha.4",
|
|
22
|
+
"@webiny/stdlib": "0.0.17",
|
|
23
|
+
"@webiny/utils": "6.6.0-alpha.4"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@webiny/build-tools": "6.6.0-alpha.4",
|
|
27
|
+
"@webiny/di": "1.1.0",
|
|
28
|
+
"rimraf": "6.1.3",
|
|
29
|
+
"typescript": "7.0.2",
|
|
30
|
+
"vitest": "4.1.11"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"webiny": {
|
|
36
|
+
"publishFrom": "dist"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -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,69 @@
|
|
|
1
|
+
import { HttpRouteDefinition, HttpRouteHandler, RequestContainer } from "@webiny/event-handler-core";
|
|
2
|
+
import { GraphQLContextEnhancer, GraphQLContextualSchema } from "@webiny/api-graphql";
|
|
3
|
+
import { RawTenantId, RequestTenantLoader } from "@webiny/api-core/features/requestContext/index.js";
|
|
4
|
+
import { TaskRunner } from "@webiny/background-tasks/api/runner/index.js";
|
|
5
|
+
import { TaskEventValidation } from "@webiny/background-tasks/api/runner/TaskEventValidation.js";
|
|
6
|
+
import { ProcessTimer } from "../timer/ProcessTimer.js";
|
|
7
|
+
import { InternalToken } from "../domain/InternalToken.js";
|
|
8
|
+
const INTERNAL_HEADER = "x-webiny-background-task-token";
|
|
9
|
+
class BackgroundTaskRouteImpl {
|
|
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
|
+
const taskEvent = request.body;
|
|
19
|
+
if (!taskEvent || !taskEvent.webinyTaskId) return response.status(400).json({
|
|
20
|
+
error: "Missing webinyTaskId in request body."
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
if (taskEvent.tenant) {
|
|
24
|
+
this.container.resolve(RawTenantId).set(taskEvent.tenant);
|
|
25
|
+
await this.container.resolve(RequestTenantLoader).establish();
|
|
26
|
+
}
|
|
27
|
+
const ctx = {
|
|
28
|
+
container: this.container
|
|
29
|
+
};
|
|
30
|
+
for (const enhancer of this.container.resolveAll(GraphQLContextEnhancer))await enhancer.enhance(ctx);
|
|
31
|
+
for (const schema of this.container.resolveAll(GraphQLContextualSchema))await schema.build(ctx);
|
|
32
|
+
const timer = new ProcessTimer();
|
|
33
|
+
const runner = new TaskRunner(ctx, timer, new TaskEventValidation());
|
|
34
|
+
const result = await runner.run(taskEvent);
|
|
35
|
+
return response.json(result);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(`Background task route error: ${message}`);
|
|
39
|
+
return response.status(500).json({
|
|
40
|
+
status: "error",
|
|
41
|
+
error: {
|
|
42
|
+
message
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const BackgroundTaskRoute = HttpRouteHandler.createImplementation({
|
|
49
|
+
implementation: BackgroundTaskRouteImpl,
|
|
50
|
+
dependencies: [
|
|
51
|
+
RequestContainer,
|
|
52
|
+
InternalToken
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
class BackgroundTaskRouteDefinitionImpl {
|
|
56
|
+
constructor(){
|
|
57
|
+
this.name = "background-task";
|
|
58
|
+
this.method = "POST";
|
|
59
|
+
this.path = "/background-task";
|
|
60
|
+
this.handler = BackgroundTaskRoute;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const BackgroundTaskRouteDefinition = HttpRouteDefinition.createImplementation({
|
|
64
|
+
implementation: BackgroundTaskRouteDefinitionImpl,
|
|
65
|
+
dependencies: []
|
|
66
|
+
});
|
|
67
|
+
export { BackgroundTaskRoute, BackgroundTaskRouteDefinition };
|
|
68
|
+
|
|
69
|
+
//# 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 { GraphQLContextEnhancer, GraphQLContextualSchema } from \"@webiny/api-graphql\";\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 /* TODO: remove once legacy ctx is gone — resolve services directly from the container. */\n const ctx: Record<string, any> = { container: this.container };\n for (const enhancer of this.container.resolveAll(GraphQLContextEnhancer)) {\n await enhancer.enhance(ctx);\n }\n for (const schema of this.container.resolveAll(GraphQLContextualSchema)) {\n await schema.build(ctx);\n }\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","enhancer","GraphQLContextEnhancer","schema","GraphQLContextualSchema","timer","ProcessTimer","runner","TaskRunner","TaskEventValidation","result","err","message","Error","String","console","BackgroundTaskRoute","HttpRouteHandler","RequestContainer","InternalToken","BackgroundTaskRouteDefinitionImpl","BackgroundTaskRouteDefinition","HttpRouteDefinition"],"mappings":";;;;;;;AAkBA,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;YAC7D,KAAK,MAAMC,YAAY,IAAI,CAAC,SAAS,CAAC,UAAU,CAACC,wBAC7C,MAAMD,SAAS,OAAO,CAACD;YAE3B,KAAK,MAAMG,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAACC,yBAC3C,MAAMD,OAAO,KAAK,CAACH;YAGvB,MAAMK,QAAQ,IAAIC;YAClB,MAAMC,SAAS,IAAIC,WAAWR,KAAgBK,OAAO,IAAII;YACzD,MAAMC,SAAS,MAAMH,OAAO,GAAG,CAACV;YAEhC,OAAOD,SAAS,IAAI,CAACc;QACzB,EAAE,OAAOC,KAAK;YACV,MAAMC,UAAUD,eAAeE,QAAQF,IAAI,OAAO,GAAGG,OAAOH;YAC5DI,QAAQ,KAAK,CAAC,CAAC,6BAA6B,EAAEH,SAAS;YACvD,OAAOhB,SAAS,MAAM,CAAC,KAAK,IAAI,CAAC;gBAAE,QAAQ;gBAAS,OAAO;oBAAEgB;gBAAQ;YAAE;QAC3E;IACJ;AACJ;AAEO,MAAMI,sBAAsBC,iBAAiB,oBAAoB,CAAC;IACrE,gBAAgBzB;IAChB,cAAc;QAAC0B;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,8 @@
|
|
|
1
|
+
import type { Timer } from "@webiny/utils/features/Timer/abstraction.js";
|
|
2
|
+
export declare class ProcessTimer implements Timer.Interface {
|
|
3
|
+
private readonly startTime;
|
|
4
|
+
private readonly maxDurationMs;
|
|
5
|
+
constructor(maxDurationMs?: number);
|
|
6
|
+
getRemainingMilliseconds(): number;
|
|
7
|
+
getRemainingSeconds(): number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DEFAULT_MAX_DURATION_MS = 86400000;
|
|
2
|
+
class ProcessTimer {
|
|
3
|
+
constructor(maxDurationMs = DEFAULT_MAX_DURATION_MS){
|
|
4
|
+
this.startTime = process.hrtime();
|
|
5
|
+
this.maxDurationMs = maxDurationMs;
|
|
6
|
+
}
|
|
7
|
+
getRemainingMilliseconds() {
|
|
8
|
+
const elapsed = process.hrtime(this.startTime);
|
|
9
|
+
const elapsedMs = 1000 * elapsed[0] + elapsed[1] / 1000000;
|
|
10
|
+
const remaining = this.maxDurationMs - elapsedMs;
|
|
11
|
+
return remaining > 0 ? remaining : 0;
|
|
12
|
+
}
|
|
13
|
+
getRemainingSeconds() {
|
|
14
|
+
return Math.floor(this.getRemainingMilliseconds() / 1000);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export { ProcessTimer };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=ProcessTimer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timer/ProcessTimer.js","sources":["../../src/timer/ProcessTimer.ts"],"sourcesContent":["import type { Timer } from \"@webiny/utils/features/Timer/abstraction.js\";\n\nconst DEFAULT_MAX_DURATION_MS = 86_400_000; /* 24 hours. */\n\nexport class ProcessTimer implements Timer.Interface {\n private readonly startTime: [number, number];\n private readonly maxDurationMs: number;\n\n public constructor(maxDurationMs: number = DEFAULT_MAX_DURATION_MS) {\n this.startTime = process.hrtime();\n this.maxDurationMs = maxDurationMs;\n }\n\n public getRemainingMilliseconds(): number {\n const elapsed = process.hrtime(this.startTime);\n const elapsedMs = elapsed[0] * 1000 + elapsed[1] / 1_000_000;\n const remaining = this.maxDurationMs - elapsedMs;\n return remaining > 0 ? remaining : 0;\n }\n\n public getRemainingSeconds(): number {\n return Math.floor(this.getRemainingMilliseconds() / 1000);\n }\n}\n"],"names":["DEFAULT_MAX_DURATION_MS","ProcessTimer","maxDurationMs","process","elapsed","elapsedMs","remaining","Math"],"mappings":"AAEA,MAAMA,0BAA0B;AAEzB,MAAMC;IAIT,YAAmBC,gBAAwBF,uBAAuB,CAAE;QAChE,IAAI,CAAC,SAAS,GAAGG,QAAQ,MAAM;QAC/B,IAAI,CAAC,aAAa,GAAGD;IACzB;IAEO,2BAAmC;QACtC,MAAME,UAAUD,QAAQ,MAAM,CAAC,IAAI,CAAC,SAAS;QAC7C,MAAME,YAAYD,AAAa,OAAbA,OAAO,CAAC,EAAE,GAAUA,OAAO,CAAC,EAAE,GAAG;QACnD,MAAME,YAAY,IAAI,CAAC,aAAa,GAAGD;QACvC,OAAOC,YAAY,IAAIA,YAAY;IACvC;IAEO,sBAA8B;QACjC,OAAOC,KAAK,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK;IACxD;AACJ"}
|
|
@@ -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,111 @@
|
|
|
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 req = node_http.request({
|
|
73
|
+
hostname: url.hostname,
|
|
74
|
+
port: url.port,
|
|
75
|
+
path: url.pathname,
|
|
76
|
+
method: "POST",
|
|
77
|
+
timeout: 60000,
|
|
78
|
+
headers: {
|
|
79
|
+
"content-type": "application/json",
|
|
80
|
+
"content-length": Buffer.byteLength(body),
|
|
81
|
+
"x-webiny-background-task-token": this.internalToken
|
|
82
|
+
}
|
|
83
|
+
}, (res)=>{
|
|
84
|
+
let data = "";
|
|
85
|
+
res.on("data", (chunk)=>{
|
|
86
|
+
data += chunk;
|
|
87
|
+
});
|
|
88
|
+
res.on("end", ()=>{
|
|
89
|
+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) return void reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
90
|
+
try {
|
|
91
|
+
resolve(JSON.parse(data));
|
|
92
|
+
} catch {
|
|
93
|
+
reject(new Error(`Invalid JSON response: ${data}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
req.on("timeout", ()=>{
|
|
98
|
+
req.destroy(new Error("Request timed out after 60s."));
|
|
99
|
+
});
|
|
100
|
+
req.on("error", reject);
|
|
101
|
+
req.write(body);
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
wait(ms) {
|
|
106
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export { TaskOrchestrator };
|
|
110
|
+
|
|
111
|
+
//# 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 const req = http.request(\n {\n hostname: url.hostname,\n port: url.port,\n path: url.pathname,\n method: \"POST\",\n timeout: 60_000,\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 res.on(\"end\", () => {\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 req.on(\"timeout\", () => {\n req.destroy(new Error(\"Request timed out after 60s.\"));\n });\n req.on(\"error\", reject);\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","req","http","Buffer","res","data","chunk","ms","setTimeout"],"mappings":"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"}
|
|
@@ -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"}
|