@webiny/background-tasks-standalone 0.0.0-unstable.33e4fd9647

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.
Files changed (71) hide show
  1. package/BackgroundTasksStandaloneFeature.d.ts +8 -0
  2. package/BackgroundTasksStandaloneFeature.js +27 -0
  3. package/BackgroundTasksStandaloneFeature.js.map +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +11 -0
  6. package/claim/ClaimRenewal.d.ts +22 -0
  7. package/claim/ClaimRenewal.js +36 -0
  8. package/claim/ClaimRenewal.js.map +1 -0
  9. package/claim/KeyValueTaskClaim.d.ts +19 -0
  10. package/claim/KeyValueTaskClaim.js +64 -0
  11. package/claim/KeyValueTaskClaim.js.map +1 -0
  12. package/claim/KeyValueTaskRecoveries.d.ts +16 -0
  13. package/claim/KeyValueTaskRecoveries.js +40 -0
  14. package/claim/KeyValueTaskRecoveries.js.map +1 -0
  15. package/claim/constants.d.ts +5 -0
  16. package/claim/constants.js +8 -0
  17. package/claim/constants.js.map +1 -0
  18. package/domain/BackgroundTasksLoop.d.ts +31 -0
  19. package/domain/BackgroundTasksLoop.js +7 -0
  20. package/domain/BackgroundTasksLoop.js.map +1 -0
  21. package/domain/ShutdownSignal.d.ts +16 -0
  22. package/domain/ShutdownSignal.js +5 -0
  23. package/domain/ShutdownSignal.js.map +1 -0
  24. package/domain/TaskClaim.d.ts +25 -0
  25. package/domain/TaskClaim.js +5 -0
  26. package/domain/TaskClaim.js.map +1 -0
  27. package/domain/TaskRecoveries.d.ts +18 -0
  28. package/domain/TaskRecoveries.js +5 -0
  29. package/domain/TaskRecoveries.js.map +1 -0
  30. package/handlers/InProcessBackgroundTaskHandler.d.ts +37 -0
  31. package/handlers/InProcessBackgroundTaskHandler.js +120 -0
  32. package/handlers/InProcessBackgroundTaskHandler.js.map +1 -0
  33. package/index.d.ts +3 -0
  34. package/index.js +3 -0
  35. package/loop/InProcessBackgroundTasksLoop.d.ts +22 -0
  36. package/loop/InProcessBackgroundTasksLoop.js +58 -0
  37. package/loop/InProcessBackgroundTasksLoop.js.map +1 -0
  38. package/loop/IterationSlots.d.ts +21 -0
  39. package/loop/IterationSlots.js +37 -0
  40. package/loop/IterationSlots.js.map +1 -0
  41. package/loop/TaskExecution.d.ts +31 -0
  42. package/loop/TaskExecution.js +61 -0
  43. package/loop/TaskExecution.js.map +1 -0
  44. package/loop/nextStep.d.ts +20 -0
  45. package/loop/nextStep.js +26 -0
  46. package/loop/nextStep.js.map +1 -0
  47. package/package.json +37 -0
  48. package/recovery/RecoverTasksHandler.d.ts +47 -0
  49. package/recovery/RecoverTasksHandler.js +151 -0
  50. package/recovery/RecoverTasksHandler.js.map +1 -0
  51. package/recovery/TaskRecoverEventHandler.d.ts +15 -0
  52. package/recovery/TaskRecoverEventHandler.js +5 -0
  53. package/recovery/TaskRecoverEventHandler.js.map +1 -0
  54. package/recovery/TaskRecoverEventType.d.ts +17 -0
  55. package/recovery/TaskRecoverEventType.js +18 -0
  56. package/recovery/TaskRecoverEventType.js.map +1 -0
  57. package/recovery/startTaskRecovery.d.ts +11 -0
  58. package/recovery/startTaskRecovery.js +31 -0
  59. package/recovery/startTaskRecovery.js.map +1 -0
  60. package/service/InProcessTaskService.d.ts +28 -0
  61. package/service/InProcessTaskService.js +62 -0
  62. package/service/InProcessTaskService.js.map +1 -0
  63. package/shutdown/ProcessShutdownSignal.d.ts +12 -0
  64. package/shutdown/ProcessShutdownSignal.js +29 -0
  65. package/shutdown/ProcessShutdownSignal.js.map +1 -0
  66. package/shutdown/stopBackgroundTasks.d.ts +7 -0
  67. package/shutdown/stopBackgroundTasks.js +8 -0
  68. package/shutdown/stopBackgroundTasks.js.map +1 -0
  69. package/timer/ProcessTimer.d.ts +14 -0
  70. package/timer/ProcessTimer.js +24 -0
  71. package/timer/ProcessTimer.js.map +1 -0
@@ -0,0 +1,151 @@
1
+ import { ListTenantsUseCase } from "@webiny/api-core/features/tenancy/ListTenants/index.js";
2
+ import { TenantContext } from "@webiny/api-core/features/tenancy/TenantContext/index.js";
3
+ import { Logger } from "@webiny/api-core/features/logger/index.js";
4
+ import { ListTasksUseCase } from "@webiny/background-tasks/api/features/ListTasks/index.js";
5
+ import { UpdateTaskUseCase } from "@webiny/background-tasks/api/features/UpdateTask/index.js";
6
+ import { TaskDataStatus } from "@webiny/background-tasks/api/types.js";
7
+ import { BackgroundTasksLoop } from "../domain/BackgroundTasksLoop.js";
8
+ import { TaskClaim } from "../domain/TaskClaim.js";
9
+ import { TaskRecoveries } from "../domain/TaskRecoveries.js";
10
+ import { ShutdownSignal } from "../domain/ShutdownSignal.js";
11
+ import { CLAIM_TTL_MS, MAX_RECOVERIES, RECOVER_GRACE_MS } from "../claim/constants.js";
12
+ import { TaskRecoverEventHandler } from "./TaskRecoverEventHandler.js";
13
+ const PAGE_SIZE = 100;
14
+ function canRecover(tenant) {
15
+ return tenant.isInstalled && "disabled" !== tenant.status;
16
+ }
17
+ function isJustTriggered(task) {
18
+ if (task.taskStatus !== TaskDataStatus.PENDING) return false;
19
+ const savedAt = Date.parse(task.savedOn);
20
+ return savedAt > Date.now() - RECOVER_GRACE_MS;
21
+ }
22
+ function toEvent(task, tenant) {
23
+ return {
24
+ webinyTaskId: task.id,
25
+ webinyTaskDefinitionId: task.definitionId,
26
+ tenant,
27
+ delay: 0,
28
+ endpoint: "in-process",
29
+ executionName: task.executionName || task.id,
30
+ stateMachineId: ""
31
+ };
32
+ }
33
+ class RecoverTasksHandlerImpl {
34
+ constructor(listTenants, tenantContext, listTasks, updateTask, claim, recoveries, tasksLoop, shutdown, logger){
35
+ this.listTenants = listTenants;
36
+ this.tenantContext = tenantContext;
37
+ this.listTasks = listTasks;
38
+ this.updateTask = updateTask;
39
+ this.claim = claim;
40
+ this.recoveries = recoveries;
41
+ this.tasksLoop = tasksLoop;
42
+ this.shutdown = shutdown;
43
+ this.logger = logger;
44
+ }
45
+ async execute() {
46
+ const tenantsResult = await this.listTenants.execute();
47
+ if (tenantsResult.isFail()) throw tenantsResult.error;
48
+ const result = {
49
+ resumed: 0,
50
+ failed: 0,
51
+ failedTenants: []
52
+ };
53
+ for (const tenant of tenantsResult.value)if (canRecover(tenant)) try {
54
+ await this.recoverTenant(tenant, result);
55
+ } catch (error) {
56
+ result.failedTenants.push(tenant.id);
57
+ this.logger.error({
58
+ error,
59
+ tenant: tenant.id
60
+ }, "Could not recover the tenant's background tasks.");
61
+ }
62
+ if (result.resumed > 0 || result.failed > 0) this.logger.info({
63
+ resumed: result.resumed,
64
+ failed: result.failed
65
+ }, "Recovered background tasks that no process held.");
66
+ return result;
67
+ }
68
+ recoverTenant(tenant, result) {
69
+ return this.tenantContext.withTenant(tenant, ()=>this.recoverUnfinished(tenant.id, result));
70
+ }
71
+ async recoverUnfinished(tenant, result) {
72
+ let after;
73
+ do {
74
+ const listResult = await this.listTasks.execute({
75
+ where: {
76
+ taskStatus_in: [
77
+ TaskDataStatus.PENDING,
78
+ TaskDataStatus.RUNNING
79
+ ]
80
+ },
81
+ limit: PAGE_SIZE,
82
+ after
83
+ });
84
+ if (listResult.isFail()) throw listResult.error;
85
+ for (const task of listResult.value.items){
86
+ const outcome = await this.recoverTask(task, tenant);
87
+ if ("resumed" === outcome) result.resumed++;
88
+ else if ("failed" === outcome) result.failed++;
89
+ }
90
+ const { meta } = listResult.value;
91
+ after = meta.hasMoreItems && meta.cursor ? meta.cursor : void 0;
92
+ }while (after)
93
+ }
94
+ async recoverTask(task, tenant) {
95
+ if (this.shutdown.isShuttingDown() || isJustTriggered(task)) return "skipped";
96
+ const target = {
97
+ tenant,
98
+ taskId: task.id
99
+ };
100
+ try {
101
+ const taken = await this.claim.take(target, CLAIM_TTL_MS);
102
+ if (!taken) return "skipped";
103
+ const count = await this.recoveries.record(target, task.savedOn);
104
+ if (count > MAX_RECOVERIES) {
105
+ await this.fail(task, count);
106
+ await this.claim.release(target);
107
+ return "failed";
108
+ }
109
+ const event = toEvent(task, tenant);
110
+ this.tasksLoop.start(event);
111
+ return "resumed";
112
+ } catch (error) {
113
+ this.logger.error({
114
+ error,
115
+ ...target
116
+ }, "Could not recover a background task.");
117
+ return "skipped";
118
+ }
119
+ }
120
+ async fail(task, count) {
121
+ const finishedOn = new Date().toISOString();
122
+ const result = await this.updateTask.execute(task.id, {
123
+ taskStatus: TaskDataStatus.FAILED,
124
+ finishedOn,
125
+ output: {
126
+ error: {
127
+ message: `The task stopped with its process ${count} times without saving any progress, so it was not resumed again.`,
128
+ code: "TASK_RECOVERY_LIMIT"
129
+ }
130
+ }
131
+ });
132
+ if (result.isFail()) throw result.error;
133
+ }
134
+ }
135
+ const RecoverTasksHandler = TaskRecoverEventHandler.createImplementation({
136
+ implementation: RecoverTasksHandlerImpl,
137
+ dependencies: [
138
+ ListTenantsUseCase,
139
+ TenantContext,
140
+ ListTasksUseCase,
141
+ UpdateTaskUseCase,
142
+ TaskClaim,
143
+ TaskRecoveries,
144
+ BackgroundTasksLoop,
145
+ ShutdownSignal,
146
+ Logger
147
+ ]
148
+ });
149
+ export { RecoverTasksHandler };
150
+
151
+ //# sourceMappingURL=RecoverTasksHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery/RecoverTasksHandler.js","sources":["../../src/recovery/RecoverTasksHandler.ts"],"sourcesContent":["import type { IBackgroundTaskEvent } from \"@webiny/event-handler-core\";\nimport { ListTenantsUseCase } from \"@webiny/api-core/features/tenancy/ListTenants/index.js\";\nimport { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport type { Tenant } from \"@webiny/api-core/types/tenancy.js\";\nimport { Logger } from \"@webiny/api-core/features/logger/index.js\";\nimport { ListTasksUseCase } from \"@webiny/background-tasks/api/features/ListTasks/index.js\";\nimport { UpdateTaskUseCase } from \"@webiny/background-tasks/api/features/UpdateTask/index.js\";\nimport { TaskDataStatus } from \"@webiny/background-tasks/api/types.js\";\nimport type { ITask } from \"@webiny/background-tasks/api/types.js\";\nimport { BackgroundTasksLoop } from \"~/domain/BackgroundTasksLoop.js\";\nimport { TaskClaim } from \"~/domain/TaskClaim.js\";\nimport { TaskRecoveries } from \"~/domain/TaskRecoveries.js\";\nimport { ShutdownSignal } from \"~/domain/ShutdownSignal.js\";\nimport { CLAIM_TTL_MS } from \"~/claim/constants.js\";\nimport { MAX_RECOVERIES } from \"~/claim/constants.js\";\nimport { RECOVER_GRACE_MS } from \"~/claim/constants.js\";\nimport { TaskRecoverEventHandler } from \"./TaskRecoverEventHandler.js\";\n\n// Unfinished tasks read per page.\nconst PAGE_SIZE = 100;\n\ntype Outcome = \"resumed\" | \"failed\" | \"skipped\";\n\n// Same reasoning as the scheduler's recovery: not installed has no tasks, disabled must not run them.\nfunction canRecover(tenant: Tenant): boolean {\n return tenant.isInstalled && tenant.status !== \"disabled\";\n}\n\n/*\n * Only a pending task gets the grace period: it can be unclaimed for a moment after its trigger\n * created it. A running task always has a claim while its process lives, so one without a claim is\n * free to take, however recently it saved. A task handed back at shutdown saved moments before its\n * claim was released, and would otherwise wait out the grace period.\n */\nfunction isJustTriggered(task: ITask): boolean {\n if (task.taskStatus !== TaskDataStatus.PENDING) {\n return false;\n }\n const savedAt = Date.parse(task.savedOn);\n return savedAt > Date.now() - RECOVER_GRACE_MS;\n}\n\n// The event a trigger would have sent, without its delay: a task found here is due already.\nfunction toEvent(task: ITask, tenant: string): IBackgroundTaskEvent {\n return {\n webinyTaskId: task.id,\n webinyTaskDefinitionId: task.definitionId,\n tenant,\n delay: 0,\n endpoint: \"in-process\",\n executionName: task.executionName || task.id,\n stateMachineId: \"\"\n };\n}\n\n/**\n * Takes over every unfinished task that no process holds: those of a process that died or was\n * replaced, and any that were triggered but never started. Pending and running tasks alike are\n * resumed from their last saved state, the way the next iteration would have continued them.\n *\n * A running task is resumed rather than failed because a redeploy kills healthy work, and the runner\n * picks it up where its last iteration saved it. Work an iteration did after that save runs again,\n * so a task must cope with an iteration being repeated.\n *\n * Each time recovery picks a task up, it counts that, and the count starts over whenever the task\n * has saved since the last pick-up. A task picked up more than `MAX_RECOVERIES` times without saving\n * in between is marked failed instead of resumed. This is for a task that takes the server down\n * itself, say by running out of memory: resumed after every restart, it would crash the server again\n * each time, and the server would never stay up. A long task that keeps saving survives any number\n * of redeploys.\n */\nclass RecoverTasksHandlerImpl implements TaskRecoverEventHandler.Interface {\n public constructor(\n private readonly listTenants: ListTenantsUseCase.Interface,\n private readonly tenantContext: TenantContext.Interface,\n private readonly listTasks: ListTasksUseCase.Interface,\n private readonly updateTask: UpdateTaskUseCase.Interface,\n private readonly claim: TaskClaim.Interface,\n private readonly recoveries: TaskRecoveries.Interface,\n private readonly tasksLoop: BackgroundTasksLoop.Interface,\n private readonly shutdown: ShutdownSignal.Interface,\n private readonly logger: Logger.Interface\n ) {}\n\n public async execute(): Promise<TaskRecoverEventHandler.Result> {\n const tenantsResult = await this.listTenants.execute();\n if (tenantsResult.isFail()) {\n throw tenantsResult.error;\n }\n\n const result: TaskRecoverEventHandler.Result = { resumed: 0, failed: 0, failedTenants: [] };\n\n for (const tenant of tenantsResult.value) {\n if (!canRecover(tenant)) {\n continue;\n }\n try {\n await this.recoverTenant(tenant, result);\n } catch (error) {\n result.failedTenants.push(tenant.id);\n this.logger.error(\n { error, tenant: tenant.id },\n \"Could not recover the tenant's background tasks.\"\n );\n }\n }\n\n if (result.resumed > 0 || result.failed > 0) {\n this.logger.info(\n { resumed: result.resumed, failed: result.failed },\n \"Recovered background tasks that no process held.\"\n );\n }\n return result;\n }\n\n private recoverTenant(tenant: Tenant, result: TaskRecoverEventHandler.Result): Promise<void> {\n // No `withoutAuthorization`: tasks are a private model, which no permission check applies to.\n return this.tenantContext.withTenant(tenant, () => {\n return this.recoverUnfinished(tenant.id, result);\n });\n }\n\n private async recoverUnfinished(\n tenant: string,\n result: TaskRecoverEventHandler.Result\n ): Promise<void> {\n let after: string | undefined;\n\n do {\n const listResult = await this.listTasks.execute({\n where: { taskStatus_in: [TaskDataStatus.PENDING, TaskDataStatus.RUNNING] },\n limit: PAGE_SIZE,\n after\n });\n if (listResult.isFail()) {\n throw listResult.error;\n }\n\n for (const task of listResult.value.items) {\n const outcome = await this.recoverTask(task, tenant);\n if (outcome === \"resumed\") {\n result.resumed++;\n } else if (outcome === \"failed\") {\n result.failed++;\n }\n }\n\n const { meta } = listResult.value;\n after = meta.hasMoreItems && meta.cursor ? meta.cursor : undefined;\n } while (after);\n }\n\n // One task that cannot be recovered is logged, and the rest of the page is still recovered.\n private async recoverTask(task: ITask, tenant: string): Promise<Outcome> {\n // A process that is shutting down would only take a claim it cannot use, and delay the next owner.\n if (this.shutdown.isShuttingDown() || isJustTriggered(task)) {\n return \"skipped\";\n }\n const target: TaskClaim.Target = { tenant, taskId: task.id };\n\n try {\n const taken = await this.claim.take(target, CLAIM_TTL_MS);\n if (!taken) {\n return \"skipped\";\n }\n\n const count = await this.recoveries.record(target, task.savedOn);\n if (count > MAX_RECOVERIES) {\n await this.fail(task, count);\n await this.claim.release(target);\n return \"failed\";\n }\n\n // The loop's first iteration finds the claim already this process's.\n const event = toEvent(task, tenant);\n this.tasksLoop.start(event);\n return \"resumed\";\n } catch (error) {\n this.logger.error({ error, ...target }, \"Could not recover a background task.\");\n return \"skipped\";\n }\n }\n\n private async fail(task: ITask, count: number): Promise<void> {\n const finishedOn = new Date().toISOString();\n const result = await this.updateTask.execute(task.id, {\n taskStatus: TaskDataStatus.FAILED,\n finishedOn,\n output: {\n error: {\n message: `The task stopped with its process ${count} times without saving any progress, so it was not resumed again.`,\n code: \"TASK_RECOVERY_LIMIT\"\n }\n }\n });\n if (result.isFail()) {\n throw result.error;\n }\n }\n}\n\nexport const RecoverTasksHandler = TaskRecoverEventHandler.createImplementation({\n implementation: RecoverTasksHandlerImpl,\n dependencies: [\n ListTenantsUseCase,\n TenantContext,\n ListTasksUseCase,\n UpdateTaskUseCase,\n TaskClaim,\n TaskRecoveries,\n BackgroundTasksLoop,\n ShutdownSignal,\n Logger\n ]\n});\n"],"names":["PAGE_SIZE","canRecover","tenant","isJustTriggered","task","TaskDataStatus","savedAt","Date","RECOVER_GRACE_MS","toEvent","RecoverTasksHandlerImpl","listTenants","tenantContext","listTasks","updateTask","claim","recoveries","tasksLoop","shutdown","logger","tenantsResult","result","error","after","listResult","outcome","meta","undefined","target","taken","CLAIM_TTL_MS","count","MAX_RECOVERIES","event","finishedOn","RecoverTasksHandler","TaskRecoverEventHandler","ListTenantsUseCase","TenantContext","ListTasksUseCase","UpdateTaskUseCase","TaskClaim","TaskRecoveries","BackgroundTasksLoop","ShutdownSignal","Logger"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Abstraction } from "@webiny/di";
2
+ import type { IEventHandler } from "@webiny/event-handler-core";
3
+ import type { ITaskRecoverEvent } from "./TaskRecoverEventType.js";
4
+ export interface ITaskRecoverResult {
5
+ resumed: number;
6
+ failed: number;
7
+ failedTenants: string[];
8
+ }
9
+ export interface ITaskRecoverEventHandler extends IEventHandler<ITaskRecoverEvent, ITaskRecoverResult> {
10
+ }
11
+ export declare const TaskRecoverEventHandler: Abstraction<ITaskRecoverEventHandler>;
12
+ export declare namespace TaskRecoverEventHandler {
13
+ type Interface = ITaskRecoverEventHandler;
14
+ type Result = ITaskRecoverResult;
15
+ }
@@ -0,0 +1,5 @@
1
+ import { Abstraction } from "@webiny/di";
2
+ const TaskRecoverEventHandler = new Abstraction("BackgroundTasks/TaskRecoverEventHandler");
3
+ export { TaskRecoverEventHandler };
4
+
5
+ //# sourceMappingURL=TaskRecoverEventHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery/TaskRecoverEventHandler.js","sources":["../../src/recovery/TaskRecoverEventHandler.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\nimport type { IEventHandler } from \"@webiny/event-handler-core\";\nimport type { ITaskRecoverEvent } from \"./TaskRecoverEventType.js\";\n\nexport interface ITaskRecoverResult {\n // Tasks nobody held, now driven by this process.\n resumed: number;\n // Tasks taken over too often, now failed instead of resumed.\n failed: number;\n // Tenants whose tasks could not be read or recovered; the others were still recovered.\n failedTenants: string[];\n}\n\nexport interface ITaskRecoverEventHandler extends IEventHandler<\n ITaskRecoverEvent,\n ITaskRecoverResult\n> {}\n\nexport const TaskRecoverEventHandler = new Abstraction<ITaskRecoverEventHandler>(\n \"BackgroundTasks/TaskRecoverEventHandler\"\n);\n\nexport namespace TaskRecoverEventHandler {\n export type Interface = ITaskRecoverEventHandler;\n export type Result = ITaskRecoverResult;\n}\n"],"names":["TaskRecoverEventHandler","Abstraction"],"mappings":";AAkBO,MAAMA,0BAA0B,IAAIC,YACvC"}
@@ -0,0 +1,17 @@
1
+ import type { IEventType } from "@webiny/event-handler-core";
2
+ export declare const TASK_RECOVER_EVENT_IDENTIFIER = "WebinyBackgroundTaskRecover";
3
+ /**
4
+ * Asks this process to take over every task that nobody holds. Dispatched at boot and then on a
5
+ * timer: a task whose process died is stranded until some other process looks for it.
6
+ */
7
+ export interface ITaskRecoverEvent {
8
+ [TASK_RECOVER_EVENT_IDENTIFIER]: true;
9
+ }
10
+ declare class TaskRecoverEventTypeImpl implements IEventType<ITaskRecoverEvent> {
11
+ canHandle(event: any): event is ITaskRecoverEvent;
12
+ getHandlerAbstraction(): import("@webiny/di").Abstraction<import("./TaskRecoverEventHandler.js").ITaskRecoverEventHandler>;
13
+ }
14
+ export declare const TaskRecoverEventType: typeof TaskRecoverEventTypeImpl & {
15
+ __abstraction: import("@webiny/di").Abstraction<IEventType<any>>;
16
+ };
17
+ export {};
@@ -0,0 +1,18 @@
1
+ import { EventType } from "@webiny/event-handler-core";
2
+ import { TaskRecoverEventHandler } from "./TaskRecoverEventHandler.js";
3
+ const TASK_RECOVER_EVENT_IDENTIFIER = "WebinyBackgroundTaskRecover";
4
+ class TaskRecoverEventTypeImpl {
5
+ canHandle(event) {
6
+ return event?.[TASK_RECOVER_EVENT_IDENTIFIER] === true;
7
+ }
8
+ getHandlerAbstraction() {
9
+ return TaskRecoverEventHandler;
10
+ }
11
+ }
12
+ const TaskRecoverEventType = EventType.createImplementation({
13
+ implementation: TaskRecoverEventTypeImpl,
14
+ dependencies: []
15
+ });
16
+ export { TASK_RECOVER_EVENT_IDENTIFIER, TaskRecoverEventType };
17
+
18
+ //# sourceMappingURL=TaskRecoverEventType.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery/TaskRecoverEventType.js","sources":["../../src/recovery/TaskRecoverEventType.ts"],"sourcesContent":["import { EventType } from \"@webiny/event-handler-core\";\nimport type { IEventType } from \"@webiny/event-handler-core\";\nimport { TaskRecoverEventHandler } from \"./TaskRecoverEventHandler.js\";\n\nexport const TASK_RECOVER_EVENT_IDENTIFIER = \"WebinyBackgroundTaskRecover\";\n\n/**\n * Asks this process to take over every task that nobody holds. Dispatched at boot and then on a\n * timer: a task whose process died is stranded until some other process looks for it.\n */\nexport interface ITaskRecoverEvent {\n [TASK_RECOVER_EVENT_IDENTIFIER]: true;\n}\n\nclass TaskRecoverEventTypeImpl implements IEventType<ITaskRecoverEvent> {\n canHandle(event: any): event is ITaskRecoverEvent {\n return event?.[TASK_RECOVER_EVENT_IDENTIFIER] === true;\n }\n\n getHandlerAbstraction() {\n return TaskRecoverEventHandler;\n }\n}\n\nexport const TaskRecoverEventType = EventType.createImplementation({\n implementation: TaskRecoverEventTypeImpl,\n dependencies: []\n});\n"],"names":["TASK_RECOVER_EVENT_IDENTIFIER","TaskRecoverEventTypeImpl","event","TaskRecoverEventHandler","TaskRecoverEventType","EventType"],"mappings":";;AAIO,MAAMA,gCAAgC;AAU7C,MAAMC;IACF,UAAUC,KAAU,EAA8B;QAC9C,OAAOA,OAAO,CAACF,8BAA8B,KAAK;IACtD;IAEA,wBAAwB;QACpB,OAAOG;IACX;AACJ;AAEO,MAAMC,uBAAuBC,UAAU,oBAAoB,CAAC;IAC/D,gBAAgBJ;IAChB,cAAc,EAAE;AACpB"}
@@ -0,0 +1,11 @@
1
+ import type { Container } from "@webiny/di";
2
+ /**
3
+ * Looks for tasks nobody holds shortly after boot, then every minute. The first run picks up what a
4
+ * restart or redeploy left behind; the later ones pick up the tasks of another process that died
5
+ * while this one kept running.
6
+ *
7
+ * Runs never overlap: one that finds many tasks can take longer than the interval.
8
+ *
9
+ * Returns a function that stops the timers, for when the process is shutting down.
10
+ */
11
+ export declare function startTaskRecovery(rootContainer: Container): () => void;
@@ -0,0 +1,31 @@
1
+ import { EventDispatcher } from "@webiny/event-handler-core";
2
+ import { RECOVER_EVERY_MS } from "../claim/constants.js";
3
+ import { TASK_RECOVER_EVENT_IDENTIFIER } from "./TaskRecoverEventType.js";
4
+ const FIRST_RUN_DELAY_MS = 5000;
5
+ function startTaskRecovery(rootContainer) {
6
+ const dispatcher = rootContainer.resolve(EventDispatcher);
7
+ const event = {
8
+ [TASK_RECOVER_EVENT_IDENTIFIER]: true
9
+ };
10
+ let running = false;
11
+ const recover = async ()=>{
12
+ if (running) return;
13
+ running = true;
14
+ try {
15
+ await dispatcher.dispatch(event);
16
+ } catch (error) {
17
+ console.error("[background-tasks] recovery failed:", error);
18
+ } finally{
19
+ running = false;
20
+ }
21
+ };
22
+ const firstRun = setTimeout(recover, FIRST_RUN_DELAY_MS);
23
+ const everyMinute = setInterval(recover, RECOVER_EVERY_MS);
24
+ return ()=>{
25
+ clearTimeout(firstRun);
26
+ clearInterval(everyMinute);
27
+ };
28
+ }
29
+ export { startTaskRecovery };
30
+
31
+ //# sourceMappingURL=startTaskRecovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery/startTaskRecovery.js","sources":["../../src/recovery/startTaskRecovery.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { EventDispatcher } from \"@webiny/event-handler-core\";\nimport { RECOVER_EVERY_MS } from \"~/claim/constants.js\";\nimport { TASK_RECOVER_EVENT_IDENTIFIER } from \"./TaskRecoverEventType.js\";\nimport type { ITaskRecoverEvent } from \"./TaskRecoverEventType.js\";\n\n// Shortly after boot, so the first run does not compete with startup.\nconst FIRST_RUN_DELAY_MS = 5000;\n\n/**\n * Looks for tasks nobody holds shortly after boot, then every minute. The first run picks up what a\n * restart or redeploy left behind; the later ones pick up the tasks of another process that died\n * while this one kept running.\n *\n * Runs never overlap: one that finds many tasks can take longer than the interval.\n *\n * Returns a function that stops the timers, for when the process is shutting down.\n */\nexport function startTaskRecovery(rootContainer: Container): () => void {\n const dispatcher = rootContainer.resolve(EventDispatcher);\n const event: ITaskRecoverEvent = { [TASK_RECOVER_EVENT_IDENTIFIER]: true };\n let running = false;\n\n const recover = async () => {\n if (running) {\n return;\n }\n running = true;\n try {\n await dispatcher.dispatch(event);\n } catch (error) {\n /*\n * The handler reports what it recovered and what failed through the Logger. This only\n * catches a run that failed before reaching it, where there is no Logger yet: it is\n * registered per request, not in the root container this runs from.\n */\n console.error(\"[background-tasks] recovery failed:\", error);\n } finally {\n running = false;\n }\n };\n\n const firstRun = setTimeout(recover, FIRST_RUN_DELAY_MS);\n const everyMinute = setInterval(recover, RECOVER_EVERY_MS);\n\n return () => {\n clearTimeout(firstRun);\n clearInterval(everyMinute);\n };\n}\n"],"names":["FIRST_RUN_DELAY_MS","startTaskRecovery","rootContainer","dispatcher","EventDispatcher","event","TASK_RECOVER_EVENT_IDENTIFIER","running","recover","error","console","firstRun","setTimeout","everyMinute","setInterval","RECOVER_EVERY_MS","clearTimeout","clearInterval"],"mappings":";;;AAOA,MAAMA,qBAAqB;AAWpB,SAASC,kBAAkBC,aAAwB;IACtD,MAAMC,aAAaD,cAAc,OAAO,CAACE;IACzC,MAAMC,QAA2B;QAAE,CAACC,8BAA8B,EAAE;IAAK;IACzE,IAAIC,UAAU;IAEd,MAAMC,UAAU;QACZ,IAAID,SACA;QAEJA,UAAU;QACV,IAAI;YACA,MAAMJ,WAAW,QAAQ,CAACE;QAC9B,EAAE,OAAOI,OAAO;YAMZC,QAAQ,KAAK,CAAC,uCAAuCD;QACzD,SAAU;YACNF,UAAU;QACd;IACJ;IAEA,MAAMI,WAAWC,WAAWJ,SAASR;IACrC,MAAMa,cAAcC,YAAYN,SAASO;IAEzC,OAAO;QACHC,aAAaL;QACbM,cAAcJ;IAClB;AACJ"}
@@ -0,0 +1,28 @@
1
+ import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
2
+ import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
3
+ import { Logger } from "@webiny/api-core/features/logger/index.js";
4
+ import { BackgroundTasksLoop } from "../domain/BackgroundTasksLoop.js";
5
+ import { TaskClaim } from "../domain/TaskClaim.js";
6
+ /**
7
+ * Starts a triggered task in this process. Builds the same event Step Functions would deliver on
8
+ * AWS and hands it to the task loop. `endpoint`, `executionName` and `stateMachineId` only satisfy
9
+ * the runner's event validation; nothing reads them off AWS.
10
+ *
11
+ * The task is claimed here, before the loop starts it, with the trigger's delay in the claim's
12
+ * expiry. The delay is not stored on the task, so without this a process that died before the first
13
+ * iteration would take the delay with it, and recovery elsewhere would run the task early.
14
+ */
15
+ declare class InProcessTaskServiceImpl implements TaskService.Interface {
16
+ private readonly tenantContext;
17
+ private readonly tasksLoop;
18
+ private readonly claim;
19
+ private readonly logger;
20
+ constructor(tenantContext: TenantContext.Interface, tasksLoop: BackgroundTasksLoop.Interface, claim: TaskClaim.Interface, logger: Logger.Interface);
21
+ send(task: TaskService.SendTaskParams, delay: number): Promise<unknown>;
22
+ private claimUntilDue;
23
+ fetch(): Promise<null>;
24
+ }
25
+ export declare const InProcessTaskService: typeof InProcessTaskServiceImpl & {
26
+ __abstraction: import("@webiny/di").Abstraction<import("@webiny/background-tasks/api/domain/TaskService.js").ITaskService>;
27
+ };
28
+ export {};
@@ -0,0 +1,62 @@
1
+ import { TaskService } from "@webiny/background-tasks/api/domain/TaskService.js";
2
+ import { TenantContext } from "@webiny/api-core/exports/api/tenancy.js";
3
+ import { Logger } from "@webiny/api-core/features/logger/index.js";
4
+ import { BackgroundTasksLoop } from "../domain/BackgroundTasksLoop.js";
5
+ import { TaskClaim } from "../domain/TaskClaim.js";
6
+ import { CLAIM_TTL_MS } from "../claim/constants.js";
7
+ class InProcessTaskServiceImpl {
8
+ constructor(tenantContext, tasksLoop, claim, logger){
9
+ this.tenantContext = tenantContext;
10
+ this.tasksLoop = tasksLoop;
11
+ this.claim = claim;
12
+ this.logger = logger;
13
+ }
14
+ async send(task, delay) {
15
+ const tenant = this.tenantContext.getTenant();
16
+ if (!tenant) return null;
17
+ const event = {
18
+ webinyTaskId: task.id,
19
+ webinyTaskDefinitionId: task.definitionId,
20
+ tenant: tenant.id,
21
+ delay,
22
+ endpoint: "in-process",
23
+ executionName: task.id,
24
+ stateMachineId: ""
25
+ };
26
+ await this.claimUntilDue(tenant.id, task.id, delay);
27
+ this.tasksLoop.start(event);
28
+ return {
29
+ taskId: task.id
30
+ };
31
+ }
32
+ async claimUntilDue(tenant, taskId, delay) {
33
+ const target = {
34
+ tenant,
35
+ taskId
36
+ };
37
+ const delayMs = 1000 * Math.max(delay, 0);
38
+ try {
39
+ await this.claim.take(target, CLAIM_TTL_MS + delayMs);
40
+ } catch (error) {
41
+ this.logger.warn({
42
+ error,
43
+ ...target
44
+ }, "Could not claim a triggered background task.");
45
+ }
46
+ }
47
+ async fetch() {
48
+ return null;
49
+ }
50
+ }
51
+ const InProcessTaskService = TaskService.createImplementation({
52
+ implementation: InProcessTaskServiceImpl,
53
+ dependencies: [
54
+ TenantContext,
55
+ BackgroundTasksLoop,
56
+ TaskClaim,
57
+ Logger
58
+ ]
59
+ });
60
+ export { InProcessTaskService };
61
+
62
+ //# sourceMappingURL=InProcessTaskService.js.map
@@ -0,0 +1 @@
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 { Logger } from \"@webiny/api-core/features/logger/index.js\";\nimport { BackgroundTasksLoop } from \"~/domain/BackgroundTasksLoop.js\";\nimport { TaskClaim } from \"~/domain/TaskClaim.js\";\nimport { CLAIM_TTL_MS } from \"~/claim/constants.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 *\n * The task is claimed here, before the loop starts it, with the trigger's delay in the claim's\n * expiry. The delay is not stored on the task, so without this a process that died before the first\n * iteration would take the delay with it, and recovery elsewhere would run the task early.\n */\nclass InProcessTaskServiceImpl implements TaskService.Interface {\n public constructor(\n private readonly tenantContext: TenantContext.Interface,\n private readonly tasksLoop: BackgroundTasksLoop.Interface,\n private readonly claim: TaskClaim.Interface,\n private readonly logger: Logger.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 await this.claimUntilDue(tenant.id, task.id, delay);\n this.tasksLoop.start(event);\n\n return { taskId: task.id };\n }\n\n /*\n * A claim that cannot be written is left to the first iteration, which takes it as it did before.\n * The trigger still succeeds: the task is already stored, and failing here would only lose it.\n */\n private async claimUntilDue(tenant: string, taskId: string, delay: number): Promise<void> {\n const target: TaskClaim.Target = { tenant, taskId };\n const delayMs = Math.max(delay, 0) * 1000;\n try {\n await this.claim.take(target, CLAIM_TTL_MS + delayMs);\n } catch (error) {\n this.logger.warn({ error, ...target }, \"Could not claim a triggered background task.\");\n }\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, BackgroundTasksLoop, TaskClaim, Logger]\n});\n"],"names":["InProcessTaskServiceImpl","tenantContext","tasksLoop","claim","logger","task","delay","tenant","event","taskId","target","delayMs","Math","CLAIM_TTL_MS","error","InProcessTaskService","TaskService","TenantContext","BackgroundTasksLoop","TaskClaim","Logger"],"mappings":";;;;;;AAiBA,MAAMA;IACF,YACqBC,aAAsC,EACtCC,SAAwC,EACxCC,KAA0B,EAC1BC,MAAwB,CAC3C;aAJmBH,aAAa,GAAbA;aACAC,SAAS,GAATA;aACAC,KAAK,GAALA;aACAC,MAAM,GAANA;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,MAAM,IAAI,CAAC,aAAa,CAACE,OAAO,EAAE,EAAEF,KAAK,EAAE,EAAEC;QAC7C,IAAI,CAAC,SAAS,CAAC,KAAK,CAACE;QAErB,OAAO;YAAE,QAAQH,KAAK,EAAE;QAAC;IAC7B;IAMA,MAAc,cAAcE,MAAc,EAAEE,MAAc,EAAEH,KAAa,EAAiB;QACtF,MAAMI,SAA2B;YAAEH;YAAQE;QAAO;QAClD,MAAME,UAAUC,AAAqB,OAArBA,KAAK,GAAG,CAACN,OAAO;QAChC,IAAI;YACA,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAACI,QAAQG,eAAeF;QACjD,EAAE,OAAOG,OAAO;YACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAEA;gBAAO,GAAGJ,MAAM;YAAC,GAAG;QAC3C;IACJ;IAEA,MAAa,QAAuB;QAEhC,OAAO;IACX;AACJ;AAEO,MAAMK,uBAAuBC,YAAY,oBAAoB,CAAC;IACjE,gBAAgBhB;IAChB,cAAc;QAACiB;QAAeC;QAAqBC;QAAWC;KAAO;AACzE"}
@@ -0,0 +1,12 @@
1
+ import { ShutdownSignal } from "../domain/ShutdownSignal.js";
2
+ declare class ProcessShutdownSignalImpl implements ShutdownSignal.Interface {
3
+ private shuttingDown;
4
+ private readonly listeners;
5
+ isShuttingDown(): boolean;
6
+ onShutdown(listener: () => void): () => void;
7
+ begin(): void;
8
+ }
9
+ export declare const ProcessShutdownSignal: typeof ProcessShutdownSignalImpl & {
10
+ __abstraction: import("@webiny/di").Abstraction<import("~/domain/ShutdownSignal.js").IShutdownSignal>;
11
+ };
12
+ export {};
@@ -0,0 +1,29 @@
1
+ import { ShutdownSignal } from "../domain/ShutdownSignal.js";
2
+ class ProcessShutdownSignalImpl {
3
+ isShuttingDown() {
4
+ return this.shuttingDown;
5
+ }
6
+ onShutdown(listener) {
7
+ this.listeners.add(listener);
8
+ return ()=>{
9
+ this.listeners.delete(listener);
10
+ };
11
+ }
12
+ begin() {
13
+ if (this.shuttingDown) return;
14
+ this.shuttingDown = true;
15
+ for (const listener of this.listeners)listener();
16
+ this.listeners.clear();
17
+ }
18
+ constructor(){
19
+ this.shuttingDown = false;
20
+ this.listeners = new Set();
21
+ }
22
+ }
23
+ const ProcessShutdownSignal = ShutdownSignal.createImplementation({
24
+ implementation: ProcessShutdownSignalImpl,
25
+ dependencies: []
26
+ });
27
+ export { ProcessShutdownSignal };
28
+
29
+ //# sourceMappingURL=ProcessShutdownSignal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown/ProcessShutdownSignal.js","sources":["../../src/shutdown/ProcessShutdownSignal.ts"],"sourcesContent":["import { ShutdownSignal } from \"~/domain/ShutdownSignal.js\";\n\nclass ProcessShutdownSignalImpl implements ShutdownSignal.Interface {\n private shuttingDown = false;\n private readonly listeners = new Set<() => void>();\n\n public isShuttingDown(): boolean {\n return this.shuttingDown;\n }\n\n public onShutdown(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n public begin(): void {\n if (this.shuttingDown) {\n return;\n }\n this.shuttingDown = true;\n for (const listener of this.listeners) {\n listener();\n }\n this.listeners.clear();\n }\n}\n\nexport const ProcessShutdownSignal = ShutdownSignal.createImplementation({\n implementation: ProcessShutdownSignalImpl,\n dependencies: []\n});\n"],"names":["ProcessShutdownSignalImpl","listener","Set","ProcessShutdownSignal","ShutdownSignal"],"mappings":";AAEA,MAAMA;IAIK,iBAA0B;QAC7B,OAAO,IAAI,CAAC,YAAY;IAC5B;IAEO,WAAWC,QAAoB,EAAc;QAChD,IAAI,CAAC,SAAS,CAAC,GAAG,CAACA;QACnB,OAAO;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAACA;QAC1B;IACJ;IAEO,QAAc;QACjB,IAAI,IAAI,CAAC,YAAY,EACjB;QAEJ,IAAI,CAAC,YAAY,GAAG;QACpB,KAAK,MAAMA,YAAY,IAAI,CAAC,SAAS,CACjCA;QAEJ,IAAI,CAAC,SAAS,CAAC,KAAK;IACxB;;aAvBQ,YAAY,GAAG;aACN,SAAS,GAAG,IAAIC;;AAuBrC;AAEO,MAAMC,wBAAwBC,eAAe,oBAAoB,CAAC;IACrE,gBAAgBJ;IAChB,cAAc,EAAE;AACpB"}
@@ -0,0 +1,7 @@
1
+ import type { Container } from "@webiny/di";
2
+ /**
3
+ * Hands this process's tasks back before it exits. Running iterations save their progress and
4
+ * release their claims, so another process picks them up at its next recovery sweep instead of
5
+ * once the claims run out. Resolves when they have, or after `timeoutMs`.
6
+ */
7
+ export declare function stopBackgroundTasks(rootContainer: Container, timeoutMs: number): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { BackgroundTasksLoop } from "../domain/BackgroundTasksLoop.js";
2
+ function stopBackgroundTasks(rootContainer, timeoutMs) {
3
+ const loop = rootContainer.resolve(BackgroundTasksLoop);
4
+ return loop.stop(timeoutMs);
5
+ }
6
+ export { stopBackgroundTasks };
7
+
8
+ //# sourceMappingURL=stopBackgroundTasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown/stopBackgroundTasks.js","sources":["../../src/shutdown/stopBackgroundTasks.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { BackgroundTasksLoop } from \"~/domain/BackgroundTasksLoop.js\";\n\n/**\n * Hands this process's tasks back before it exits. Running iterations save their progress and\n * release their claims, so another process picks them up at its next recovery sweep instead of\n * once the claims run out. Resolves when they have, or after `timeoutMs`.\n */\nexport function stopBackgroundTasks(rootContainer: Container, timeoutMs: number): Promise<void> {\n const loop = rootContainer.resolve(BackgroundTasksLoop);\n return loop.stop(timeoutMs);\n}\n"],"names":["stopBackgroundTasks","rootContainer","timeoutMs","loop","BackgroundTasksLoop"],"mappings":";AAQO,SAASA,oBAAoBC,aAAwB,EAAEC,SAAiB;IAC3E,MAAMC,OAAOF,cAAc,OAAO,CAACG;IACnC,OAAOD,KAAK,IAAI,CAACD;AACrB"}
@@ -0,0 +1,14 @@
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
+ private expired;
6
+ constructor(maxDurationMs?: number);
7
+ /**
8
+ * Runs the time out now. A task that checks `isCloseToTimeout()` then saves its progress and
9
+ * hands back control, as it would before a Lambda's time runs out.
10
+ */
11
+ expire(): void;
12
+ getRemainingMilliseconds(): number;
13
+ getRemainingSeconds(): number;
14
+ }
@@ -0,0 +1,24 @@
1
+ const DEFAULT_MAX_DURATION_MS = 86400000;
2
+ class ProcessTimer {
3
+ constructor(maxDurationMs = DEFAULT_MAX_DURATION_MS){
4
+ this.expired = false;
5
+ this.startTime = process.hrtime();
6
+ this.maxDurationMs = maxDurationMs;
7
+ }
8
+ expire() {
9
+ this.expired = true;
10
+ }
11
+ getRemainingMilliseconds() {
12
+ if (this.expired) return 0;
13
+ const elapsed = process.hrtime(this.startTime);
14
+ const elapsedMs = 1000 * elapsed[0] + elapsed[1] / 1000000;
15
+ const remaining = this.maxDurationMs - elapsedMs;
16
+ return remaining > 0 ? remaining : 0;
17
+ }
18
+ getRemainingSeconds() {
19
+ return Math.floor(this.getRemainingMilliseconds() / 1000);
20
+ }
21
+ }
22
+ export { ProcessTimer };
23
+
24
+ //# 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 private expired = false;\n\n public constructor(maxDurationMs: number = DEFAULT_MAX_DURATION_MS) {\n this.startTime = process.hrtime();\n this.maxDurationMs = maxDurationMs;\n }\n\n /**\n * Runs the time out now. A task that checks `isCloseToTimeout()` then saves its progress and\n * hands back control, as it would before a Lambda's time runs out.\n */\n public expire(): void {\n this.expired = true;\n }\n\n public getRemainingMilliseconds(): number {\n if (this.expired) {\n return 0;\n }\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;IAKT,YAAmBC,gBAAwBF,uBAAuB,CAAE;aAF5D,OAAO,GAAG;QAGd,IAAI,CAAC,SAAS,GAAGG,QAAQ,MAAM;QAC/B,IAAI,CAAC,aAAa,GAAGD;IACzB;IAMO,SAAe;QAClB,IAAI,CAAC,OAAO,GAAG;IACnB;IAEO,2BAAmC;QACtC,IAAI,IAAI,CAAC,OAAO,EACZ,OAAO;QAEX,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"}