@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.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +13 -1
- package/CHANGELOG.md +5 -0
- package/VERSION +1 -1
- package/out/config-loader/env.d.ts +4 -0
- package/out/config-loader/env.js +5 -1
- package/out/config-loader/env.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +1 -0
- package/out/sbvr-api/sbvr-utils.js +6 -1
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/tasks.d.ts +44 -0
- package/out/sbvr-api/tasks.js +245 -0
- package/out/sbvr-api/tasks.js.map +1 -0
- package/out/sbvr-api/tasks.sbvr +56 -0
- package/out/server-glue/module.d.ts +1 -0
- package/out/server-glue/module.js +2 -1
- package/out/server-glue/module.js.map +1 -1
- package/package.json +6 -3
- package/src/config-loader/env.ts +6 -1
- package/src/sbvr-api/sbvr-utils.ts +4 -0
- package/src/sbvr-api/tasks.sbvr +56 -0
- package/src/sbvr-api/tasks.ts +378 -0
- package/src/server-glue/module.ts +1 -0
@@ -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.
|
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-
|
150
|
+
"publishedAt": "2024-03-12T00:43:56.639Z"
|
148
151
|
}
|
149
152
|
}
|
package/src/config-loader/env.ts
CHANGED
@@ -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 };
|