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