@balena/pinejs 16.0.0 → 16.1.0-build-joshbwlng-tasks-82a48d2f7c281892020e59c514b2775690dcabed-1

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.
@@ -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",
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 };