@balena/pinejs 16.0.0-build-fisehara-update-sbvr-types-f5e8e6ebfd0d3f590ef6ed3fa6b46d1b0861d409-1 → 16.1.0-build-joshbwlng-tasks-82a48d2f7c281892020e59c514b2775690dcabed-1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/sbvr-api/tasks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ Vocabulary: tasks
2
+
3
+ Term: actor
4
+ Concept Type: Integer (Type)
5
+ Term: attempt count
6
+ Concept Type: Integer (Type)
7
+ Term: attempt limit
8
+ Concept Type: Integer (Type)
9
+ Term: cron expression
10
+ Concept Type: Short Text (Type)
11
+ Term: error message
12
+ Concept Type: Short Text (Type)
13
+ Term: handler
14
+ Concept Type: Short Text (Type)
15
+ Term: key
16
+ Concept Type: Short Text (Type)
17
+ Term: parameter set
18
+ Concept Type: JSON (Type)
19
+ Term: priority
20
+ Concept Type: Integer (Type)
21
+ Term: status
22
+ Concept Type: Short Text (Type)
23
+ Term: time
24
+ Concept Type: Date Time (Type)
25
+
26
+ Term: task
27
+ Fact type: task has key
28
+ Necessity: each task has at most one key
29
+ Fact type: task is created by actor
30
+ Necessity: each task is created by exactly one actor
31
+ Fact type: task is executed by handler
32
+ Necessity: each task is executed by exactly one handler
33
+ Fact type: task is executed with parameter set
34
+ Necessity: each task is executed with at most one parameter set
35
+ Fact type: task has priority
36
+ Necessity: each task has exactly one priority
37
+ Necessity: each task has a priority that is greater than or equal to 0
38
+ Fact type: task is scheduled with cron expression
39
+ Necessity: each task is scheduled with at most one cron expression
40
+ Fact type: task is scheduled to execute on time
41
+ Necessity: each task is scheduled to execute on at most one time
42
+ Fact type: task has status
43
+ Necessity: each task has exactly one status
44
+ Definition: "pending" or "cancelled" or "success" or "failed"
45
+ Fact type: task started on time
46
+ Necessity: each task started on at most one time
47
+ Fact type: task ended on time
48
+ Necessity: each task ended on at most one time
49
+ Fact type: task has error message
50
+ Necessity: each task has at most one error message
51
+ Fact type: task has attempt count
52
+ Necessity: each task has exactly one attempt count
53
+ Fact type: task has attempt limit
54
+ Necessity: each task has exactly one attempt limit
55
+ Necessity: each task has an attempt limit that is greater than or equal to 1
56
+
@@ -13,6 +13,7 @@ export * as errors from '../sbvr-api/errors';
13
13
  export * as env from '../config-loader/env';
14
14
  export * as types from '../sbvr-api/common-types';
15
15
  export * as hooks from '../sbvr-api/hooks';
16
+ export * as tasks from '../sbvr-api/tasks';
16
17
  export * as webResourceHandler from '../webresource-handler';
17
18
  export type { configLoader as ConfigLoader };
18
19
  export type { migratorUtils as Migrator };
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.init = exports.webResourceHandler = exports.hooks = exports.types = exports.env = exports.errors = exports.permissions = exports.sbvrUtils = exports.mountLoginRouter = exports.PinejsSessionStore = exports.dbModule = void 0;
26
+ exports.init = exports.webResourceHandler = exports.tasks = exports.hooks = exports.types = exports.env = exports.errors = exports.permissions = exports.sbvrUtils = exports.mountLoginRouter = exports.PinejsSessionStore = exports.dbModule = void 0;
27
27
  require("./sbvr-loader");
28
28
  const dbModule = __importStar(require("../database-layer/db"));
29
29
  const configLoader = __importStar(require("../config-loader/config-loader"));
@@ -41,6 +41,7 @@ exports.errors = __importStar(require("../sbvr-api/errors"));
41
41
  exports.env = __importStar(require("../config-loader/env"));
42
42
  exports.types = __importStar(require("../sbvr-api/common-types"));
43
43
  exports.hooks = __importStar(require("../sbvr-api/hooks"));
44
+ exports.tasks = __importStar(require("../sbvr-api/tasks"));
44
45
  exports.webResourceHandler = __importStar(require("../webresource-handler"));
45
46
  let envDatabaseOptions;
46
47
  if (dbModule.engines.websql != null) {
@@ -1 +1 @@
1
- {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/server-glue/module.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/server-glue/module.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/pinejs",
3
- "version": "16.0.0-build-fisehara-update-sbvr-types-f5e8e6ebfd0d3f590ef6ed3fa6b46d1b0861d409-1",
3
+ "version": "16.1.0-build-joshbwlng-tasks-82a48d2f7c281892020e59c514b2775690dcabed-1",
4
4
  "main": "out/server-glue/module",
5
5
  "type": "commonjs",
6
6
  "repository": "git@github.com:balena-io/pinejs.git",
@@ -20,7 +20,7 @@
20
20
  "webpack-build": "npm run webpack-browser && npm run webpack-module && npm run webpack-server",
21
21
  "lint": "balena-lint -t tsconfig.dev.json -e js -e ts src build typings Gruntfile.ts && npx tsc --project tsconfig.dev.json --noEmit",
22
22
  "test": "npm run lint && npm run build && npm run webpack-build && npm run test:compose",
23
- "test:compose": "trap 'docker-compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' SIGINT; docker-compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 npm run mocha",
23
+ "test:compose": "trap 'docker-compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' SIGINT; docker-compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 npm run mocha",
24
24
  "mocha": "TS_NODE_FILES=true mocha",
25
25
  "prettify": "balena-lint -t tsconfig.dev.json -e js -e ts --fix src test build typings Gruntfile.ts"
26
26
  },
@@ -51,8 +51,10 @@
51
51
  "@types/pg": "^8.10.9",
52
52
  "@types/randomstring": "^1.1.11",
53
53
  "@types/websql": "^0.0.30",
54
+ "ajv": "^8.12.0",
54
55
  "busboy": "^1.6.0",
55
56
  "commander": "^11.1.0",
57
+ "cron-parser": "^4.9.0",
56
58
  "deep-freeze": "^0.0.1",
57
59
  "eventemitter3": "^5.0.1",
58
60
  "express-session": "^1.17.3",
@@ -89,6 +91,7 @@
89
91
  "grunt-ts": "^6.0.0-beta.22",
90
92
  "grunt-webpack": "^6.0.0",
91
93
  "husky": "^8.0.3",
94
+ "json-schema-to-ts": "^3.0.0",
92
95
  "lint-staged": "^15.2.0",
93
96
  "load-grunt-tasks": "^5.1.0",
94
97
  "mocha": "^10.2.0",
@@ -144,6 +147,6 @@
144
147
  "recursive": true
145
148
  },
146
149
  "versionist": {
147
- "publishedAt": "2024-03-08T13:37:28.583Z"
150
+ "publishedAt": "2024-03-12T00:43:56.639Z"
148
151
  }
149
152
  }
@@ -49,7 +49,7 @@ export const cache = {
49
49
  apiKeyActorId: false as CacheOpts,
50
50
  };
51
51
 
52
- import { boolVar } from '@balena/env-parsing';
52
+ import { boolVar, intVar } from '@balena/env-parsing';
53
53
  import memoize from 'memoizee';
54
54
  import memoizeWeak = require('memoizee/weak');
55
55
  export const createCache = <T extends (...args: any[]) => any>(
@@ -146,3 +146,8 @@ export const migrator = {
146
146
  */
147
147
  asyncMigrationIsEnabled: boolVar('PINEJS_ASYNC_MIGRATION_ENABLED', true),
148
148
  };
149
+
150
+ export const tasks = {
151
+ queueConcurrency: intVar('PINEJS_QUEUE_CONCURRENCY', 0),
152
+ queueIntervalMS: intVar('PINEJS_QUEUE_INTERVAL_MS', 1000),
153
+ };
@@ -42,6 +42,7 @@ import { generateODataMetadata } from '../odata-metadata/odata-metadata-generato
42
42
 
43
43
  // eslint-disable-next-line @typescript-eslint/no-var-requires
44
44
  const devModel = require('./dev.sbvr');
45
+ import * as tasks from './tasks';
45
46
  import * as permissions from './permissions';
46
47
  import {
47
48
  BadRequestError,
@@ -77,6 +78,7 @@ export {
77
78
  addPureHook,
78
79
  addSideEffectHook,
79
80
  } from './hooks';
81
+ export { addTaskHandler } from './tasks';
80
82
 
81
83
  import memoizeWeak = require('memoizee/weak');
82
84
  import * as controlFlow from './control-flow';
@@ -1953,6 +1955,7 @@ export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
1953
1955
  },
1954
1956
  });
1955
1957
  await executeModels(tx, permissions.config.models);
1958
+ await executeModels(tx, tasks.config.models);
1956
1959
  console.info('Successfully executed standard models.');
1957
1960
  } catch (err: any) {
1958
1961
  console.error('Failed to execute standard models.', err);
@@ -1969,6 +1972,7 @@ export const setup = async (
1969
1972
  await db.transaction(async (tx) => {
1970
1973
  await executeStandardModels(tx);
1971
1974
  await permissions.setup();
1975
+ await tasks.setup();
1972
1976
  });
1973
1977
  } catch (err: any) {
1974
1978
  console.error('Could not execute standard models', err);
@@ -0,0 +1,56 @@
1
+ Vocabulary: tasks
2
+
3
+ Term: actor
4
+ Concept Type: Integer (Type)
5
+ Term: attempt count
6
+ Concept Type: Integer (Type)
7
+ Term: attempt limit
8
+ Concept Type: Integer (Type)
9
+ Term: cron expression
10
+ Concept Type: Short Text (Type)
11
+ Term: error message
12
+ Concept Type: Short Text (Type)
13
+ Term: handler
14
+ Concept Type: Short Text (Type)
15
+ Term: key
16
+ Concept Type: Short Text (Type)
17
+ Term: parameter set
18
+ Concept Type: JSON (Type)
19
+ Term: priority
20
+ Concept Type: Integer (Type)
21
+ Term: status
22
+ Concept Type: Short Text (Type)
23
+ Term: time
24
+ Concept Type: Date Time (Type)
25
+
26
+ Term: task
27
+ Fact type: task has key
28
+ Necessity: each task has at most one key
29
+ Fact type: task is created by actor
30
+ Necessity: each task is created by exactly one actor
31
+ Fact type: task is executed by handler
32
+ Necessity: each task is executed by exactly one handler
33
+ Fact type: task is executed with parameter set
34
+ Necessity: each task is executed with at most one parameter set
35
+ Fact type: task has priority
36
+ Necessity: each task has exactly one priority
37
+ Necessity: each task has a priority that is greater than or equal to 0
38
+ Fact type: task is scheduled with cron expression
39
+ Necessity: each task is scheduled with at most one cron expression
40
+ Fact type: task is scheduled to execute on time
41
+ Necessity: each task is scheduled to execute on at most one time
42
+ Fact type: task has status
43
+ Necessity: each task has exactly one status
44
+ Definition: "pending" or "cancelled" or "success" or "failed"
45
+ Fact type: task started on time
46
+ Necessity: each task started on at most one time
47
+ Fact type: task ended on time
48
+ Necessity: each task ended on at most one time
49
+ Fact type: task has error message
50
+ Necessity: each task has at most one error message
51
+ Fact type: task has attempt count
52
+ Necessity: each task has exactly one attempt count
53
+ Fact type: task has attempt limit
54
+ Necessity: each task has exactly one attempt limit
55
+ Necessity: each task has an attempt limit that is greater than or equal to 1
56
+
@@ -0,0 +1,378 @@
1
+ import Ajv from 'ajv';
2
+ import type { Schema, ValidateFunction } from 'ajv';
3
+ import * as cronParser from 'cron-parser';
4
+ import type { AnyObject } from 'pinejs-client-core';
5
+
6
+ import { tasks as tasksEnv } from '../config-loader/env';
7
+ import type { Tx } from '../database-layer/db';
8
+ import { BadRequestError } from './errors';
9
+ import { addPureHook } from './hooks';
10
+ import * as permissions from './permissions';
11
+ import type { ExecutableModel } from './sbvr-utils';
12
+ import { PinejsClient } from './sbvr-utils';
13
+ import { sbvrUtils } from '../server-glue/module';
14
+
15
+ export const apiRoot = 'tasks';
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ const modelText: string = require(`./${apiRoot}.sbvr`);
19
+
20
+ const handlers: {
21
+ [name: string]: TaskHandler;
22
+ } = {};
23
+
24
+ export const taskStatuses = ['pending', 'cancelled', 'success', 'failed'];
25
+ export interface Task {
26
+ id: number;
27
+ created_at: Date;
28
+ modified_at: Date;
29
+ is_created_by__actor: number;
30
+ is_executed_by__handler: string;
31
+ is_executed_with__parameter_set: object | null;
32
+ is_scheduled_with__cron_expression: string | null;
33
+ is_scheduled_to_execute_on__time: Date | null;
34
+ priority: number;
35
+ status: (typeof taskStatuses)[number];
36
+ started_on__time: Date | null;
37
+ ended_on__time: Date | null;
38
+ error_message: string | null;
39
+ attempt_count: number;
40
+ attempt_limit: number;
41
+ }
42
+
43
+ type PartialTask = Pick<
44
+ Task,
45
+ | 'id'
46
+ | 'is_created_by__actor'
47
+ | 'is_executed_by__handler'
48
+ | 'is_executed_with__parameter_set'
49
+ | 'is_scheduled_with__cron_expression'
50
+ | 'priority'
51
+ | 'attempt_count'
52
+ | 'attempt_limit'
53
+ >;
54
+
55
+ interface TaskArgs {
56
+ api: PinejsClient;
57
+ params: AnyObject;
58
+ tx: Tx;
59
+ }
60
+
61
+ type TaskResponse = Promise<{
62
+ status: (typeof taskStatuses)[number];
63
+ error?: string;
64
+ }>;
65
+
66
+ export interface TaskHandler {
67
+ name: string;
68
+ fn: (options: TaskArgs) => TaskResponse;
69
+ validate?: ValidateFunction;
70
+ }
71
+
72
+ // Parse a cron expression
73
+ function parseCron(cron: string): Date {
74
+ return cronParser.parseExpression(cron).next().toDate();
75
+ }
76
+
77
+ export const config = {
78
+ models: [
79
+ {
80
+ apiRoot,
81
+ modelText,
82
+ customServerCode: exports,
83
+ migrations: {},
84
+ },
85
+ ] as ExecutableModel[],
86
+ };
87
+
88
+ // Setting inlineRefs=false as without it we run into a
89
+ // "Maximum call stack size exceeded" error apprarently caused
90
+ // by String.prototype._uncountable_words being set in sbvr-parser?
91
+ const ajv = new Ajv({
92
+ inlineRefs: false,
93
+ });
94
+
95
+ export const setup = async () => {
96
+ addPureHook('POST', apiRoot, 'task', {
97
+ POSTPARSE: async ({ req, request }) => {
98
+ // Set the actor
99
+ request.values.is_created_by__actor =
100
+ req.user?.actor ?? req.apiKey?.actor;
101
+ if (request.values.is_created_by__actor == null) {
102
+ throw new BadRequestError(
103
+ 'Creating tasks with missing actor on req is not allowed',
104
+ );
105
+ }
106
+
107
+ // Set defaults
108
+ request.values.status = 'pending';
109
+ request.values.attempt_count = 0;
110
+ request.values.priority ??= 1;
111
+ request.values.attempt_limit ??= 1;
112
+
113
+ // Set scheduled start time using cron expression if provided
114
+ if (
115
+ request.values.is_scheduled_with__cron_expression != null &&
116
+ request.values.is_scheduled_to_execute_on__time == null
117
+ ) {
118
+ try {
119
+ request.values.is_scheduled_to_execute_on__time = parseCron(
120
+ request.values.is_scheduled_with__cron_expression,
121
+ ).toISOString();
122
+ } catch (_) {
123
+ throw new BadRequestError(
124
+ `Invalid cron expression: ${request.values.is_scheduled_with__cron_expression}`,
125
+ );
126
+ }
127
+ }
128
+
129
+ // Assert that the provided start time is far enough in the future
130
+ if (request.values.is_scheduled_to_execute_on__time != null) {
131
+ const now = new Date(new Date().getTime() + tasksEnv.queueIntervalMS);
132
+ const startTime = new Date(
133
+ request.values.is_scheduled_to_execute_on__time,
134
+ );
135
+ if (startTime < now) {
136
+ throw new BadRequestError(
137
+ `Task scheduled start time must be greater than ${tasksEnv.queueIntervalMS} milliseconds in the future`,
138
+ );
139
+ }
140
+ }
141
+
142
+ // Assert that the requested handler exists
143
+ const handlerName = request.values.is_executed_by__handler;
144
+ if (handlerName == null) {
145
+ throw new BadRequestError(`Must specify a task handler to execute`);
146
+ }
147
+ const handler = handlers[handlerName];
148
+ if (handler == null) {
149
+ throw new BadRequestError(
150
+ `No task handler with name '${handlerName}' registered`,
151
+ );
152
+ }
153
+
154
+ // Assert that the provided parameter set is valid
155
+ if (handler.validate != null) {
156
+ if (!handler.validate(request.values.is_executed_with__parameter_set)) {
157
+ throw new BadRequestError(
158
+ `Invalid parameter set: ${ajv.errorsText(handler.validate.errors)}`,
159
+ );
160
+ }
161
+ }
162
+ },
163
+ });
164
+
165
+ // Start the worker if possible
166
+ if (tasksEnv.queueConcurrency > 0 && tasksEnv.queueIntervalMS >= 1000) {
167
+ watch();
168
+ }
169
+ };
170
+
171
+ // Register a task handler
172
+ export function addTaskHandler(
173
+ name: string,
174
+ fn: TaskHandler['fn'],
175
+ schema?: Schema,
176
+ ): void {
177
+ if (handlers[name] != null) {
178
+ throw new Error(`Task handler with name '${name}' already registered`);
179
+ }
180
+ handlers[name] = {
181
+ name,
182
+ fn,
183
+ validate: schema != null ? ajv.compile(schema) : undefined,
184
+ };
185
+ }
186
+
187
+ // Calculate next attempt datetime for a task that has failed using exponential backoff
188
+ function getNextAttemptTime(attempt: number): Date | null {
189
+ const millisecondsInFuture =
190
+ Math.ceil(Math.exp(Math.min(10, attempt))) * 1000;
191
+ return new Date(Date.now() + millisecondsInFuture);
192
+ }
193
+
194
+ // Watch for new tasks to execute
195
+ let executing = 0;
196
+ function watch(): void {
197
+ const client = new PinejsClient({
198
+ apiPrefix: `/${apiRoot}/`,
199
+ });
200
+
201
+ setInterval(async () => {
202
+ // Do nothing if there are no handlers or if we are already at the concurrency limit
203
+ if (
204
+ Object.keys(handlers).length === 0 ||
205
+ executing >= tasksEnv.queueConcurrency
206
+ ) {
207
+ return;
208
+ }
209
+
210
+ try {
211
+ await sbvrUtils.db.transaction(async (tx) => {
212
+ const names = Object.keys(handlers);
213
+ const binds = names.map((_, index) => `$${index + 1}`).join(', ');
214
+ const result = await tx.executeSql(
215
+ `
216
+ SELECT
217
+ t."id",
218
+ t."is executed by-handler" AS is_executed_by__handler,
219
+ t."is executed with-parameter set" AS is_executed_with__parameter_set,
220
+ t."is scheduled with-cron expression" AS is_scheduled_with__cron_expression,
221
+ t."attempt count" AS attempt_count,
222
+ t."attempt limit" AS attempt_limit,
223
+ t."priority" AS priority,
224
+ t."is created by-actor" AS is_created_by__actor
225
+ FROM
226
+ task AS t
227
+ WHERE
228
+ t."is executed by-handler" IN (${binds}) AND
229
+ t."status" = 'pending' AND
230
+ t."attempt count" <= t."attempt limit" AND
231
+ (
232
+ t."is scheduled to execute on-time" IS NULL OR
233
+ t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(tasksEnv.queueIntervalMS / 1000)} second'
234
+ )
235
+ ORDER BY
236
+ t."is scheduled to execute on-time" ASC,
237
+ t."priority" DESC,
238
+ t."id" ASC
239
+ LIMIT 1
240
+ FOR UPDATE
241
+ SKIP LOCKED
242
+ `,
243
+ names,
244
+ );
245
+ if (result.rows.length > 0) {
246
+ executing++;
247
+ await execute(client, result.rows[0] as PartialTask, tx);
248
+ executing--;
249
+ }
250
+ });
251
+ } catch (err: unknown) {
252
+ console.error('Failed polling for tasks:', err);
253
+ }
254
+ }, tasksEnv.queueIntervalMS);
255
+ }
256
+
257
+ // Execute a task
258
+ async function execute(
259
+ client: PinejsClient,
260
+ task: PartialTask,
261
+ tx: Tx,
262
+ ): Promise<void> {
263
+ try {
264
+ // Get the handler
265
+ const handler = handlers[task.is_executed_by__handler];
266
+ const startedOnTime = new Date();
267
+ if (handler == null) {
268
+ await update(
269
+ client,
270
+ tx,
271
+ task,
272
+ startedOnTime,
273
+ 'failed',
274
+ 'Matching task handler not found',
275
+ );
276
+ return;
277
+ }
278
+
279
+ // Validate parameters before execution so we can fail early if
280
+ // the parameter set is invalid. This can happen if the handler
281
+ // definition changes after a task is added to the queue.
282
+ if (
283
+ handler.validate != null &&
284
+ !handler.validate(task.is_executed_with__parameter_set)
285
+ ) {
286
+ await update(
287
+ client,
288
+ tx,
289
+ task,
290
+ startedOnTime,
291
+ 'failed',
292
+ `Invalid parameter set: ${ajv.errorsText(handler.validate.errors)}`,
293
+ );
294
+ return;
295
+ }
296
+
297
+ // Execute the handler
298
+ const result = await handler.fn({
299
+ api: new PinejsClient({
300
+ passthrough: {
301
+ tx,
302
+ },
303
+ }),
304
+ params: task.is_executed_with__parameter_set ?? {},
305
+ tx,
306
+ });
307
+
308
+ // Update the task with the results
309
+ await update(client, tx, task, startedOnTime, result.status, result.error);
310
+ } catch (err: unknown) {
311
+ // This shouldn't normally happen, but if it does, we want to log it and kill the process
312
+ console.error('Task execution failed:', err);
313
+ process.exit(1);
314
+ }
315
+ }
316
+
317
+ // Update a task
318
+ async function update(
319
+ client: PinejsClient,
320
+ tx: Tx,
321
+ task: PartialTask,
322
+ startedOnTime: Date,
323
+ status: string,
324
+ errorMessage?: string,
325
+ ): Promise<void> {
326
+ const attemptCount = task.attempt_count + 1;
327
+ const body: AnyObject = {
328
+ started_on__time: startedOnTime,
329
+ ended_on__time: new Date(),
330
+ status,
331
+ attempt_count: attemptCount,
332
+ ...(errorMessage != null && { error_message: errorMessage }),
333
+ };
334
+
335
+ // Re-enqueue if the task failed but has retries left, remember that
336
+ // executionCount includes the initial attempt while retryLimit does not
337
+ if (status === 'failed' && attemptCount < task.attempt_limit) {
338
+ body.status = 'pending';
339
+
340
+ // Schedule next attempt using exponential backoff
341
+ body.is_scheduled_to_execute_on__time = getNextAttemptTime(attemptCount);
342
+ }
343
+
344
+ // Patch current task
345
+ await client.patch({
346
+ resource: 'task',
347
+ passthrough: {
348
+ tx,
349
+ req: permissions.root,
350
+ },
351
+ id: task.id,
352
+ body,
353
+ });
354
+
355
+ // Create new task with same configuration if previous
356
+ // iteration completed and has a cron expression
357
+ if (
358
+ ['failed', 'success'].includes(body.status) &&
359
+ task.is_scheduled_with__cron_expression != null
360
+ ) {
361
+ await client.post({
362
+ resource: 'task',
363
+ passthrough: {
364
+ tx,
365
+ req: permissions.root,
366
+ },
367
+ body: {
368
+ attempt_limit: task.attempt_limit,
369
+ is_created_by__actor: task.is_created_by__actor,
370
+ is_executed_by__handler: task.is_executed_by__handler,
371
+ is_executed_with__parameter_set: task.is_executed_with__parameter_set,
372
+ is_scheduled_with__cron_expression:
373
+ task.is_scheduled_with__cron_expression,
374
+ priority: task.priority,
375
+ },
376
+ });
377
+ }
378
+ }
@@ -19,6 +19,7 @@ export * as errors from '../sbvr-api/errors';
19
19
  export * as env from '../config-loader/env';
20
20
  export * as types from '../sbvr-api/common-types';
21
21
  export * as hooks from '../sbvr-api/hooks';
22
+ export * as tasks from '../sbvr-api/tasks';
22
23
  export * as webResourceHandler from '../webresource-handler';
23
24
  export type { configLoader as ConfigLoader };
24
25
  export type { migratorUtils as Migrator };