@dbos-inc/dbos-sdk 2.8.46-preview.g30b0e27ed1 → 2.9.9-preview
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/dbos-config.schema.json +28 -19
- package/dist/dbos-config.schema.json +28 -19
- package/dist/schemas/system_db_schema.d.ts +0 -4
- package/dist/schemas/system_db_schema.d.ts.map +1 -1
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +5 -7
- package/dist/src/client.js.map +1 -1
- package/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +6 -12
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/conductor/protocol.d.ts +2 -2
- package/dist/src/conductor/protocol.d.ts.map +1 -1
- package/dist/src/conductor/protocol.js +2 -2
- package/dist/src/conductor/protocol.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +22 -7
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +154 -36
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos-runtime/cli.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cli.js +2 -0
- package/dist/src/dbos-runtime/cli.js.map +1 -1
- package/dist/src/dbos-runtime/config.js +3 -3
- package/dist/src/dbos-runtime/config.js.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.d.ts +6 -13
- package/dist/src/dbos-runtime/workflow_management.d.ts.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.js +23 -36
- package/dist/src/dbos-runtime/workflow_management.js.map +1 -1
- package/dist/src/dbos.d.ts +17 -11
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +47 -33
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/error.d.ts +11 -9
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js +26 -23
- package/dist/src/error.js.map +1 -1
- package/dist/src/eventreceiver.d.ts +7 -3
- package/dist/src/eventreceiver.d.ts.map +1 -1
- package/dist/src/httpServer/handler.d.ts.map +1 -1
- package/dist/src/httpServer/handler.js +2 -1
- package/dist/src/httpServer/handler.js.map +1 -1
- package/dist/src/httpServer/middleware.js +2 -2
- package/dist/src/httpServer/middleware.js.map +1 -1
- package/dist/src/httpServer/server.d.ts +6 -0
- package/dist/src/httpServer/server.d.ts.map +1 -1
- package/dist/src/httpServer/server.js +40 -2
- package/dist/src/httpServer/server.js.map +1 -1
- package/dist/src/scheduler/scheduler.js +1 -1
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/system_database.d.ts +28 -52
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +261 -390
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/testing/testing_runtime.d.ts.map +1 -1
- package/dist/src/testing/testing_runtime.js +2 -1
- package/dist/src/testing/testing_runtime.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +3 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/wfqueue.d.ts.map +1 -1
- package/dist/src/wfqueue.js +1 -2
- package/dist/src/wfqueue.js.map +1 -1
- package/dist/src/workflow.d.ts +16 -6
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +38 -7
- package/dist/src/workflow.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -3
- package/migrations/20250421000000_workflowcancel.js +0 -15
- package/migrations/20250421100000_triggers_wfcancel.js +0 -35
@@ -1,4 +1,5 @@
|
|
1
1
|
"use strict";
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
5
|
};
|
@@ -39,37 +40,6 @@ async function migrateSystemDatabase(systemPoolConfig, logger) {
|
|
39
40
|
}
|
40
41
|
}
|
41
42
|
exports.migrateSystemDatabase = migrateSystemDatabase;
|
42
|
-
class NotificationMap {
|
43
|
-
map = new Map();
|
44
|
-
curCK = 0;
|
45
|
-
registerCallback(key, cb) {
|
46
|
-
if (!this.map.has(key)) {
|
47
|
-
this.map.set(key, new Map());
|
48
|
-
}
|
49
|
-
const ck = this.curCK++;
|
50
|
-
this.map.get(key).set(ck, cb);
|
51
|
-
return { key, ck };
|
52
|
-
}
|
53
|
-
deregisterCallback(k) {
|
54
|
-
if (!this.map.has(k.key))
|
55
|
-
return;
|
56
|
-
const sm = this.map.get(k.key);
|
57
|
-
if (!sm.has(k.ck))
|
58
|
-
return;
|
59
|
-
sm.delete(k.ck);
|
60
|
-
if (sm.size === 0) {
|
61
|
-
this.map.delete(k.key);
|
62
|
-
}
|
63
|
-
}
|
64
|
-
callCallbacks(key, event) {
|
65
|
-
if (!this.map.has(key))
|
66
|
-
return;
|
67
|
-
const sm = this.map.get(key);
|
68
|
-
for (const cb of sm.values()) {
|
69
|
-
cb(event);
|
70
|
-
}
|
71
|
-
}
|
72
|
-
}
|
73
43
|
class PostgresSystemDatabase {
|
74
44
|
pgPoolConfig;
|
75
45
|
systemDatabaseName;
|
@@ -78,32 +48,9 @@ class PostgresSystemDatabase {
|
|
78
48
|
pool;
|
79
49
|
systemPoolConfig;
|
80
50
|
knexDB;
|
81
|
-
/*
|
82
|
-
* Generally, notifications are asynchronous. One should:
|
83
|
-
* Subscribe to updates
|
84
|
-
* Read the database item in question
|
85
|
-
* In response to updates, re-read the database item
|
86
|
-
* Unsubscribe at the end
|
87
|
-
* The notification mechanism is reliable in the sense that it will eventually deliver updates
|
88
|
-
* or the DB connection will get dropped. The right thing to do if you lose connectivity to
|
89
|
-
* the system DB is to exit the process and go through recovery... system DB writes, notifications,
|
90
|
-
* etc may not have completed correctly, and recovery is the way to rebuild in-memory state.
|
91
|
-
*
|
92
|
-
* NOTE:
|
93
|
-
* PG Notifications are not fully reliable.
|
94
|
-
* Dropped connections are recoverable - you just need to restart and scan everything.
|
95
|
-
* (The whole VM being the logical choice, so workflows can recover from any write failures.)
|
96
|
-
* The real problem is, if the pipes out of the server are full... then notifications can be
|
97
|
-
* dropped, and only the PG server log may note it. For those reasons, we do occasional polling
|
98
|
-
*/
|
99
51
|
notificationsClient = null;
|
100
|
-
|
101
|
-
|
102
|
-
notificationsMap = new NotificationMap();
|
103
|
-
workflowEventsMap = new NotificationMap();
|
104
|
-
cancelWakeupMap = new NotificationMap();
|
105
|
-
runningWorkflowMap = new Map(); // Map from workflowID to workflow promise
|
106
|
-
workflowCancellationMap = new Map(); // Map from workflowID to its cancellation status.
|
52
|
+
notificationsMap = {};
|
53
|
+
workflowEventsMap = {};
|
107
54
|
constructor(pgPoolConfig, systemDatabaseName, logger, sysDbPoolSize) {
|
108
55
|
this.pgPoolConfig = pgPoolConfig;
|
109
56
|
this.systemDatabaseName = systemDatabaseName;
|
@@ -153,9 +100,7 @@ class PostgresSystemDatabase {
|
|
153
100
|
finally {
|
154
101
|
await pgSystemClient.end();
|
155
102
|
}
|
156
|
-
|
157
|
-
await this.listenForNotifications();
|
158
|
-
}
|
103
|
+
await this.listenForNotifications();
|
159
104
|
}
|
160
105
|
async destroy() {
|
161
106
|
await this.knexDB.destroy();
|
@@ -178,7 +123,7 @@ class PostgresSystemDatabase {
|
|
178
123
|
await pgSystemClient.query(`DROP DATABASE IF EXISTS ${dbosConfig.system_database};`);
|
179
124
|
await pgSystemClient.end();
|
180
125
|
}
|
181
|
-
async initWorkflowStatus(initStatus,
|
126
|
+
async initWorkflowStatus(initStatus, args, maxRetries) {
|
182
127
|
const result = await this.pool.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status (
|
183
128
|
workflow_uuid,
|
184
129
|
status,
|
@@ -190,14 +135,13 @@ class PostgresSystemDatabase {
|
|
190
135
|
assumed_role,
|
191
136
|
authenticated_roles,
|
192
137
|
request,
|
193
|
-
output,
|
194
138
|
executor_id,
|
195
139
|
application_version,
|
196
140
|
application_id,
|
197
141
|
created_at,
|
198
142
|
recovery_attempts,
|
199
143
|
updated_at
|
200
|
-
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16
|
144
|
+
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
201
145
|
ON CONFLICT (workflow_uuid)
|
202
146
|
DO UPDATE SET
|
203
147
|
recovery_attempts = workflow_status.recovery_attempts + 1,
|
@@ -212,9 +156,8 @@ class PostgresSystemDatabase {
|
|
212
156
|
initStatus.queueName,
|
213
157
|
initStatus.authenticatedUser,
|
214
158
|
initStatus.assumedRole,
|
215
|
-
|
216
|
-
|
217
|
-
null,
|
159
|
+
utils_1.DBOSJSON.stringify(initStatus.authenticatedRoles),
|
160
|
+
utils_1.DBOSJSON.stringify(initStatus.request),
|
218
161
|
initStatus.executorId,
|
219
162
|
initStatus.applicationVersion,
|
220
163
|
initStatus.applicationID,
|
@@ -249,17 +192,18 @@ class PostgresSystemDatabase {
|
|
249
192
|
// Every time we init the status, we increment `recovery_attempts` by 1.
|
250
193
|
// Thus, when this number becomes equal to `maxRetries + 1`, we should mark the workflow as `RETRIES_EXCEEDED`.
|
251
194
|
const attempts = resRow.recovery_attempts;
|
252
|
-
if (attempts >
|
195
|
+
if (maxRetries && attempts > maxRetries + 1) {
|
253
196
|
await this.pool.query(`UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status SET status=$1 WHERE workflow_uuid=$2 AND status=$3`, [workflow_1.StatusString.RETRIES_EXCEEDED, initStatus.workflowUUID, workflow_1.StatusString.PENDING]);
|
254
|
-
throw new error_1.DBOSDeadLetterQueueError(initStatus.workflowUUID,
|
197
|
+
throw new error_1.DBOSDeadLetterQueueError(initStatus.workflowUUID, maxRetries);
|
255
198
|
}
|
256
199
|
this.logger.debug(`Workflow ${initStatus.workflowUUID} attempt number: ${attempts}.`);
|
257
200
|
const status = resRow.status;
|
201
|
+
const serializedInputs = utils_1.DBOSJSON.stringify(args);
|
258
202
|
const { rows } = await this.pool.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_inputs (workflow_uuid, inputs) VALUES($1, $2) ON CONFLICT (workflow_uuid) DO UPDATE SET workflow_uuid = excluded.workflow_uuid RETURNING inputs`, [initStatus.workflowUUID, serializedInputs]);
|
259
203
|
if (serializedInputs !== rows[0].inputs) {
|
260
204
|
this.logger.warn(`Workflow inputs for ${initStatus.workflowUUID} changed since the first call! Use the original inputs.`);
|
261
205
|
}
|
262
|
-
return {
|
206
|
+
return { args: utils_1.DBOSJSON.parse(rows[0].inputs), status };
|
263
207
|
}
|
264
208
|
async recordWorkflowStatusChange(workflowID, status, update, client) {
|
265
209
|
let rec = '';
|
@@ -272,7 +216,7 @@ class PostgresSystemDatabase {
|
|
272
216
|
const wRes = await (client ?? this.pool).query(`UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status
|
273
217
|
SET ${rec} status=$2, output=$3, error=$4, updated_at=$5 WHERE workflow_uuid=$1`, [workflowID, status, update.output, update.error, Date.now()]);
|
274
218
|
if (wRes.rowCount !== 1) {
|
275
|
-
throw new error_1.
|
219
|
+
throw new error_1.DBOSWorkflowConflictUUIDError(`Attempt to record transition of nonexistent workflow ${workflowID}`);
|
276
220
|
}
|
277
221
|
}
|
278
222
|
async recordWorkflowOutput(workflowID, status) {
|
@@ -293,10 +237,9 @@ class PostgresSystemDatabase {
|
|
293
237
|
if (rows.length === 0) {
|
294
238
|
return null;
|
295
239
|
}
|
296
|
-
return rows[0].inputs;
|
240
|
+
return utils_1.DBOSJSON.parse(rows[0].inputs);
|
297
241
|
}
|
298
242
|
async getOperationResult(workflowID, functionID, client) {
|
299
|
-
await this.checkIfCanceled(workflowID);
|
300
243
|
const { rows } = await (client ?? this.pool).query(`SELECT output, error, child_workflow_id, function_name
|
301
244
|
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.operation_outputs
|
302
245
|
WHERE workflow_uuid=$1 AND function_id=$2`, [workflowID, functionID]);
|
@@ -337,13 +280,110 @@ class PostgresSystemDatabase {
|
|
337
280
|
const err = error;
|
338
281
|
if (err.code === '40001' || err.code === '23505') {
|
339
282
|
// Serialization and primary key conflict (Postgres).
|
340
|
-
throw new error_1.
|
283
|
+
throw new error_1.DBOSWorkflowConflictUUIDError(workflowID);
|
341
284
|
}
|
342
285
|
else {
|
343
286
|
throw err;
|
344
287
|
}
|
345
288
|
}
|
346
289
|
}
|
290
|
+
async getMaxFunctionID(workflowID) {
|
291
|
+
const { rows } = await this.pool.query(`SELECT max(function_id) as max_function_id FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.operation_outputs WHERE workflow_uuid=$1`, [workflowID]);
|
292
|
+
return rows.length === 0 ? 0 : rows[0].max_function_id;
|
293
|
+
}
|
294
|
+
async forkWorkflow(originalWorkflowID, forkedWorkflowId, startStep = 0) {
|
295
|
+
const workflowStatus = await this.getWorkflowStatus(originalWorkflowID);
|
296
|
+
if (workflowStatus === null) {
|
297
|
+
throw new error_1.DBOSNonExistentWorkflowError(`Workflow ${originalWorkflowID} does not exist`);
|
298
|
+
}
|
299
|
+
const client = await this.pool.connect();
|
300
|
+
try {
|
301
|
+
await client.query('BEGIN');
|
302
|
+
const query = `
|
303
|
+
INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status (
|
304
|
+
workflow_uuid,
|
305
|
+
status,
|
306
|
+
name,
|
307
|
+
class_name,
|
308
|
+
config_name,
|
309
|
+
queue_name,
|
310
|
+
authenticated_user,
|
311
|
+
assumed_role,
|
312
|
+
authenticated_roles,
|
313
|
+
request,
|
314
|
+
output,
|
315
|
+
executor_id,
|
316
|
+
application_version,
|
317
|
+
application_id,
|
318
|
+
created_at,
|
319
|
+
recovery_attempts,
|
320
|
+
updated_at
|
321
|
+
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
322
|
+
`;
|
323
|
+
await client.query(query, [
|
324
|
+
forkedWorkflowId,
|
325
|
+
workflow_1.StatusString.ENQUEUED,
|
326
|
+
workflowStatus.workflowName,
|
327
|
+
workflowStatus.workflowClassName,
|
328
|
+
workflowStatus.workflowConfigName,
|
329
|
+
utils_1.INTERNAL_QUEUE_NAME,
|
330
|
+
workflowStatus.authenticatedUser,
|
331
|
+
workflowStatus.assumedRole,
|
332
|
+
utils_1.DBOSJSON.stringify(workflowStatus.authenticatedRoles),
|
333
|
+
utils_1.DBOSJSON.stringify(workflowStatus.request),
|
334
|
+
null,
|
335
|
+
null,
|
336
|
+
workflowStatus.applicationVersion,
|
337
|
+
workflowStatus.applicationID,
|
338
|
+
Date.now(),
|
339
|
+
0,
|
340
|
+
Date.now(),
|
341
|
+
]);
|
342
|
+
// Copy the inputs to the new workflow
|
343
|
+
const inputQuery = `INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_inputs (workflow_uuid, inputs)
|
344
|
+
SELECT $2, inputs
|
345
|
+
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_inputs
|
346
|
+
WHERE workflow_uuid = $1;`;
|
347
|
+
await client.query(inputQuery, [originalWorkflowID, forkedWorkflowId]);
|
348
|
+
if (startStep > 0) {
|
349
|
+
const query = `
|
350
|
+
INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.operation_outputs (
|
351
|
+
workflow_uuid,
|
352
|
+
function_id,
|
353
|
+
output,
|
354
|
+
error,
|
355
|
+
function_name,
|
356
|
+
child_workflow_id
|
357
|
+
)
|
358
|
+
SELECT
|
359
|
+
$1 AS workflow_uuid,
|
360
|
+
function_id,
|
361
|
+
output,
|
362
|
+
error,
|
363
|
+
function_name,
|
364
|
+
child_workflow_id
|
365
|
+
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.operation_outputs
|
366
|
+
WHERE workflow_uuid = $2 AND function_id < $3
|
367
|
+
`;
|
368
|
+
await client.query(query, [forkedWorkflowId, originalWorkflowID, startStep]);
|
369
|
+
}
|
370
|
+
await client.query(`
|
371
|
+
INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue (workflow_uuid, queue_name)
|
372
|
+
VALUES ($1, $2)
|
373
|
+
ON CONFLICT (workflow_uuid)
|
374
|
+
DO NOTHING;
|
375
|
+
`, [forkedWorkflowId, utils_1.INTERNAL_QUEUE_NAME]);
|
376
|
+
await client.query('COMMIT');
|
377
|
+
return forkedWorkflowId;
|
378
|
+
}
|
379
|
+
catch (error) {
|
380
|
+
await client.query('ROLLBACK');
|
381
|
+
throw error;
|
382
|
+
}
|
383
|
+
finally {
|
384
|
+
client.release();
|
385
|
+
}
|
386
|
+
}
|
347
387
|
async runAsStep(callback, functionName, workflowID, functionID, client) {
|
348
388
|
if (workflowID !== undefined && functionID !== undefined) {
|
349
389
|
const res = await this.getOperationResult(workflowID, functionID, client);
|
@@ -362,30 +402,6 @@ class PostgresSystemDatabase {
|
|
362
402
|
return serialOutput;
|
363
403
|
}
|
364
404
|
async durableSleepms(workflowID, functionID, durationMS) {
|
365
|
-
let resolveNotification;
|
366
|
-
const cancelPromise = new Promise((resolve) => {
|
367
|
-
resolveNotification = resolve;
|
368
|
-
});
|
369
|
-
const cbr = this.cancelWakeupMap.registerCallback(workflowID, resolveNotification);
|
370
|
-
try {
|
371
|
-
let timeoutPromise = Promise.resolve();
|
372
|
-
const { promise, cancel: timeoutCancel } = await this.durableSleepmsInternal(workflowID, functionID, durationMS);
|
373
|
-
timeoutPromise = promise;
|
374
|
-
try {
|
375
|
-
await Promise.race([cancelPromise, timeoutPromise]);
|
376
|
-
}
|
377
|
-
finally {
|
378
|
-
timeoutCancel();
|
379
|
-
}
|
380
|
-
}
|
381
|
-
finally {
|
382
|
-
this.cancelWakeupMap.deregisterCallback(cbr);
|
383
|
-
}
|
384
|
-
await this.checkIfCanceled(workflowID);
|
385
|
-
}
|
386
|
-
async durableSleepmsInternal(workflowID, functionID, durationMS, maxSleepPerIteration) {
|
387
|
-
if (maxSleepPerIteration === undefined)
|
388
|
-
maxSleepPerIteration = durationMS;
|
389
405
|
const curTime = Date.now();
|
390
406
|
let endTimeMs = curTime + durationMS;
|
391
407
|
const res = await this.getOperationResult(workflowID, functionID);
|
@@ -398,10 +414,7 @@ class PostgresSystemDatabase {
|
|
398
414
|
else {
|
399
415
|
await this.recordOperationResult(workflowID, functionID, { serialOutput: JSON.stringify(endTimeMs), functionName: exports.DBOS_FUNCNAME_SLEEP }, false);
|
400
416
|
}
|
401
|
-
return
|
402
|
-
...(0, utils_1.cancellableSleep)(Math.max(Math.min(maxSleepPerIteration, endTimeMs - curTime), 0)),
|
403
|
-
endTime: endTimeMs,
|
404
|
-
};
|
417
|
+
return (0, utils_1.cancellableSleep)(Math.max(endTimeMs - curTime, 0));
|
405
418
|
}
|
406
419
|
nullTopic = '__null__topic__';
|
407
420
|
async send(workflowID, functionID, destinationID, message, topic) {
|
@@ -440,56 +453,37 @@ class PostgresSystemDatabase {
|
|
440
453
|
}
|
441
454
|
return res.res.res;
|
442
455
|
}
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
// register the key with the global notifications listener.
|
456
|
+
// Check if the key is already in the DB, then wait for the notification if it isn't.
|
457
|
+
const initRecvRows = (await this.pool.query(`SELECT topic FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.notifications WHERE destination_uuid=$1 AND topic=$2;`, [workflowID, topic])).rows;
|
458
|
+
if (initRecvRows.length === 0) {
|
459
|
+
// Then, register the key with the global notifications listener.
|
447
460
|
let resolveNotification;
|
448
461
|
const messagePromise = new Promise((resolve) => {
|
449
462
|
resolveNotification = resolve;
|
450
463
|
});
|
451
464
|
const payload = `${workflowID}::${topic}`;
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
});
|
465
|
+
this.notificationsMap[payload] = resolveNotification; // The resolver assignment in the Promise definition runs synchronously.
|
466
|
+
let timeoutPromise = Promise.resolve();
|
467
|
+
let timeoutCancel = () => { };
|
456
468
|
try {
|
457
|
-
await this.
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
timeoutPromise = promise;
|
470
|
-
timeoutCancel = cancel;
|
471
|
-
finishTime = endTime;
|
472
|
-
}
|
473
|
-
else {
|
474
|
-
let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
|
475
|
-
poll = Math.min(this.dbPollingIntervalMs, poll);
|
476
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
477
|
-
timeoutPromise = promise;
|
478
|
-
timeoutCancel = cancel;
|
479
|
-
}
|
480
|
-
try {
|
481
|
-
await Promise.race([messagePromise, timeoutPromise]);
|
482
|
-
}
|
483
|
-
finally {
|
484
|
-
timeoutCancel();
|
485
|
-
}
|
469
|
+
const { promise, cancel } = await this.durableSleepms(workflowID, timeoutFunctionID, timeoutSeconds * 1000);
|
470
|
+
timeoutPromise = promise;
|
471
|
+
timeoutCancel = cancel;
|
472
|
+
}
|
473
|
+
catch (e) {
|
474
|
+
this.logger.error(e);
|
475
|
+
delete this.notificationsMap[payload];
|
476
|
+
timeoutCancel();
|
477
|
+
throw new Error('durable sleepms failed');
|
478
|
+
}
|
479
|
+
try {
|
480
|
+
await Promise.race([messagePromise, timeoutPromise]);
|
486
481
|
}
|
487
482
|
finally {
|
488
|
-
|
489
|
-
this.
|
483
|
+
timeoutCancel();
|
484
|
+
delete this.notificationsMap[payload];
|
490
485
|
}
|
491
486
|
}
|
492
|
-
await this.checkIfCanceled(workflowID);
|
493
487
|
// Transactionally consume and return the message if it's in the DB, otherwise return null.
|
494
488
|
let message = null;
|
495
489
|
const client = await this.pool.connect();
|
@@ -563,49 +557,40 @@ class PostgresSystemDatabase {
|
|
563
557
|
// Get the return the value. if it's in the DB, otherwise return null.
|
564
558
|
let value = null;
|
565
559
|
const payloadKey = `${workflowID}::${key}`;
|
566
|
-
const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
|
567
|
-
let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
|
568
560
|
// Register the key with the global notifications listener first... we do not want to look in the DB first
|
569
561
|
// or that would cause a timing hole.
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
const initRecvRows = (await this.pool.query(`
|
586
|
-
SELECT key, value
|
587
|
-
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
|
588
|
-
WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
|
589
|
-
if (initRecvRows.length > 0) {
|
590
|
-
value = initRecvRows[0].value;
|
591
|
-
break;
|
592
|
-
}
|
593
|
-
const ct = Date.now();
|
594
|
-
if (finishTime && ct > finishTime)
|
595
|
-
break; // Time's up
|
562
|
+
let resolveNotification;
|
563
|
+
const valuePromise = new Promise((resolve) => {
|
564
|
+
resolveNotification = resolve;
|
565
|
+
});
|
566
|
+
this.workflowEventsMap[payloadKey] = resolveNotification; // The resolver assignment in the Promise definition runs synchronously.
|
567
|
+
try {
|
568
|
+
// Check if the key is already in the DB, then wait for the notification if it isn't.
|
569
|
+
const initRecvRows = (await this.pool.query(`
|
570
|
+
SELECT key, value
|
571
|
+
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
|
572
|
+
WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
|
573
|
+
if (initRecvRows.length > 0) {
|
574
|
+
value = initRecvRows[0].value;
|
575
|
+
}
|
576
|
+
else {
|
596
577
|
// If we have a callerWorkflow, we want a durable sleep, otherwise, not
|
597
578
|
let timeoutPromise = Promise.resolve();
|
598
579
|
let timeoutCancel = () => { };
|
599
|
-
if (callerWorkflow
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
580
|
+
if (callerWorkflow) {
|
581
|
+
try {
|
582
|
+
const { promise, cancel } = await this.durableSleepms(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutSeconds * 1000);
|
583
|
+
timeoutPromise = promise;
|
584
|
+
timeoutCancel = cancel;
|
585
|
+
}
|
586
|
+
catch (e) {
|
587
|
+
this.logger.error(e);
|
588
|
+
delete this.workflowEventsMap[payloadKey];
|
589
|
+
throw new Error('durable sleepms failed');
|
590
|
+
}
|
604
591
|
}
|
605
592
|
else {
|
606
|
-
|
607
|
-
poll = Math.min(this.dbPollingIntervalMs, poll);
|
608
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
593
|
+
const { promise, cancel } = (0, utils_1.cancellableSleep)(timeoutSeconds * 1000);
|
609
594
|
timeoutPromise = promise;
|
610
595
|
timeoutCancel = cancel;
|
611
596
|
}
|
@@ -615,12 +600,17 @@ class PostgresSystemDatabase {
|
|
615
600
|
finally {
|
616
601
|
timeoutCancel();
|
617
602
|
}
|
603
|
+
const finalRecvRows = (await this.pool.query(`
|
604
|
+
SELECT value
|
605
|
+
FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
|
606
|
+
WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
|
607
|
+
if (finalRecvRows.length > 0) {
|
608
|
+
value = finalRecvRows[0].value;
|
609
|
+
}
|
618
610
|
}
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
this.cancelWakeupMap.deregisterCallback(crh);
|
623
|
-
}
|
611
|
+
}
|
612
|
+
finally {
|
613
|
+
delete this.workflowEventsMap[payloadKey];
|
624
614
|
}
|
625
615
|
// Record the output if it is inside a workflow.
|
626
616
|
if (callerWorkflow) {
|
@@ -634,23 +624,10 @@ class PostgresSystemDatabase {
|
|
634
624
|
async setWorkflowStatus(workflowID, status, resetRecoveryAttempts) {
|
635
625
|
await this.recordWorkflowStatusChange(workflowID, status, { resetRecoveryAttempts });
|
636
626
|
}
|
637
|
-
setWFCancelMap(workflowID) {
|
638
|
-
if (this.runningWorkflowMap.has(workflowID)) {
|
639
|
-
this.workflowCancellationMap.set(workflowID, true);
|
640
|
-
}
|
641
|
-
this.cancelWakeupMap.callCallbacks(workflowID);
|
642
|
-
}
|
643
|
-
clearWFCancelMap(workflowID) {
|
644
|
-
if (this.workflowCancellationMap.has(workflowID)) {
|
645
|
-
this.workflowCancellationMap.delete(workflowID);
|
646
|
-
}
|
647
|
-
}
|
648
627
|
async cancelWorkflow(workflowID) {
|
649
628
|
const client = await this.pool.connect();
|
650
629
|
try {
|
651
630
|
await client.query('BEGIN');
|
652
|
-
await client.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_cancel(workflow_id)
|
653
|
-
VALUES ($1)`, [workflowID]);
|
654
631
|
// Remove workflow from queues table
|
655
632
|
await client.query(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
|
656
633
|
WHERE workflow_uuid = $1`, [workflowID]);
|
@@ -659,25 +636,18 @@ class PostgresSystemDatabase {
|
|
659
636
|
await client.query('COMMIT');
|
660
637
|
}
|
661
638
|
catch (error) {
|
662
|
-
this.logger.error(error);
|
663
639
|
await client.query('ROLLBACK');
|
664
640
|
throw error;
|
665
641
|
}
|
666
642
|
finally {
|
667
643
|
client.release();
|
668
644
|
}
|
669
|
-
this.setWFCancelMap(workflowID);
|
670
|
-
}
|
671
|
-
async checkIfCanceled(workflowID) {
|
672
|
-
if (this.workflowCancellationMap.get(workflowID) === true) {
|
673
|
-
throw new error_1.DBOSWorkflowCancelledError(workflowID);
|
674
|
-
}
|
675
|
-
return Promise.resolve();
|
676
645
|
}
|
677
646
|
async resumeWorkflow(workflowID) {
|
678
647
|
const client = await this.pool.connect();
|
679
648
|
try {
|
680
649
|
await client.query('BEGIN');
|
650
|
+
await client.query('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
681
651
|
// Check workflow status. If it is complete, do nothing.
|
682
652
|
const statusResult = await client.query(`SELECT status FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status
|
683
653
|
WHERE workflow_uuid = $1`, [workflowID]);
|
@@ -690,172 +660,62 @@ class PostgresSystemDatabase {
|
|
690
660
|
// Remove the workflow from the queues table so resume can safely be called on an ENQUEUED workflow
|
691
661
|
await client.query(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
|
692
662
|
WHERE workflow_uuid = $1`, [workflowID]);
|
693
|
-
await client.query(`
|
694
|
-
|
695
|
-
|
696
|
-
await
|
663
|
+
await client.query(`UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status
|
664
|
+
SET status = $1, queue_name= $2, recovery_attempts = 0
|
665
|
+
WHERE workflow_uuid = $3`, [workflow_1.StatusString.ENQUEUED, utils_1.INTERNAL_QUEUE_NAME, workflowID]);
|
666
|
+
await client.query(`
|
667
|
+
INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue (workflow_uuid, queue_name)
|
668
|
+
VALUES ($1, $2)
|
669
|
+
ON CONFLICT (workflow_uuid)
|
670
|
+
DO NOTHING;
|
671
|
+
`, [workflowID, utils_1.INTERNAL_QUEUE_NAME]);
|
697
672
|
await client.query('COMMIT');
|
698
673
|
}
|
699
674
|
catch (error) {
|
700
|
-
this.logger.error(error);
|
701
675
|
await client.query('ROLLBACK');
|
702
676
|
throw error;
|
703
677
|
}
|
704
678
|
finally {
|
705
679
|
client.release();
|
706
680
|
}
|
707
|
-
this.clearWFCancelMap(workflowID);
|
708
|
-
}
|
709
|
-
registerRunningWorkflow(workflowID, workflowPromise) {
|
710
|
-
// Need to await for the workflow and capture errors.
|
711
|
-
const awaitWorkflowPromise = workflowPromise
|
712
|
-
.catch((error) => {
|
713
|
-
this.logger.debug('Captured error in awaitWorkflowPromise: ' + error);
|
714
|
-
})
|
715
|
-
.finally(() => {
|
716
|
-
// Remove itself from pending workflow map.
|
717
|
-
this.runningWorkflowMap.delete(workflowID);
|
718
|
-
this.workflowCancellationMap.delete(workflowID);
|
719
|
-
});
|
720
|
-
this.runningWorkflowMap.set(workflowID, awaitWorkflowPromise);
|
721
|
-
}
|
722
|
-
async awaitRunningWorkflows() {
|
723
|
-
if (this.runningWorkflowMap.size > 0) {
|
724
|
-
this.logger.info('Waiting for pending workflows to finish.');
|
725
|
-
await Promise.allSettled(this.runningWorkflowMap.values());
|
726
|
-
}
|
727
|
-
if (this.workflowEventsMap.map.size > 0) {
|
728
|
-
this.logger.warn('Workflow events map is not empty - shutdown is not clean.');
|
729
|
-
//throw new Error('Workflow events map is not empty - shutdown is not clean.');
|
730
|
-
}
|
731
|
-
if (this.notificationsMap.map.size > 0) {
|
732
|
-
this.logger.warn('Message notification map is not empty - shutdown is not clean.');
|
733
|
-
//throw new Error('Message notification map is not empty - shutdown is not clean.');
|
734
|
-
}
|
735
681
|
}
|
736
682
|
async getWorkflowStatus(workflowID, callerID, callerFN) {
|
737
|
-
const internalStatus = await this.getWorkflowStatusInternal(workflowID, callerID, callerFN);
|
738
|
-
if (internalStatus === null) {
|
739
|
-
return null;
|
740
|
-
}
|
741
|
-
return {
|
742
|
-
status: internalStatus.status,
|
743
|
-
workflowName: internalStatus.workflowName,
|
744
|
-
workflowClassName: internalStatus.workflowClassName,
|
745
|
-
workflowConfigName: internalStatus.workflowConfigName,
|
746
|
-
queueName: internalStatus.queueName,
|
747
|
-
authenticatedUser: internalStatus.authenticatedUser,
|
748
|
-
assumedRole: internalStatus.assumedRole,
|
749
|
-
authenticatedRoles: internalStatus.authenticatedRoles,
|
750
|
-
request: internalStatus.request,
|
751
|
-
executorId: internalStatus.executorId,
|
752
|
-
};
|
753
|
-
}
|
754
|
-
async getWorkflowStatusInternal(workflowID, callerID, callerFN) {
|
755
683
|
// Check if the operation has been done before for OAOO (only do this inside a workflow).
|
756
684
|
const sv = await this.runAsStep(async () => {
|
757
|
-
const
|
758
|
-
|
759
|
-
|
760
|
-
value = {
|
761
|
-
workflowUUID: rows[0].workflow_uuid,
|
762
|
-
status: rows[0].status,
|
763
|
-
workflowName: rows[0].name,
|
764
|
-
output: null,
|
765
|
-
error: null,
|
766
|
-
workflowClassName: rows[0].class_name || '',
|
767
|
-
workflowConfigName: rows[0].config_name || '',
|
768
|
-
queueName: rows[0].queue_name || undefined,
|
769
|
-
authenticatedUser: rows[0].authenticated_user,
|
770
|
-
assumedRole: rows[0].assumed_role,
|
771
|
-
authenticatedRoles: JSON.parse(rows[0].authenticated_roles),
|
772
|
-
request: JSON.parse(rows[0].request),
|
773
|
-
executorId: rows[0].executor_id,
|
774
|
-
createdAt: Number(rows[0].created_at),
|
775
|
-
updatedAt: Number(rows[0].updated_at),
|
776
|
-
applicationVersion: rows[0].application_version,
|
777
|
-
applicationID: rows[0].application_id,
|
778
|
-
recoveryAttempts: Number(rows[0].recovery_attempts),
|
779
|
-
maxRetries: 0,
|
780
|
-
};
|
781
|
-
}
|
782
|
-
return value ? JSON.stringify(value) : null;
|
685
|
+
const statuses = await this.listWorkflows({ workflowIDs: [workflowID] });
|
686
|
+
const status = statuses.find((s) => s.workflowUUID === workflowID);
|
687
|
+
return status ? JSON.stringify(status) : null;
|
783
688
|
}, exports.DBOS_FUNCNAME_GETSTATUS, callerID, callerFN);
|
784
689
|
return sv ? JSON.parse(sv) : null;
|
785
690
|
}
|
786
|
-
async awaitWorkflowResult(workflowID,
|
787
|
-
const
|
788
|
-
|
691
|
+
async awaitWorkflowResult(workflowID, timeoutms) {
|
692
|
+
const pollingIntervalMs = 1000;
|
693
|
+
const et = timeoutms !== undefined ? new Date().getTime() + timeoutms : undefined;
|
789
694
|
while (true) {
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
resolveNotification();
|
796
|
-
});
|
797
|
-
const crh = callerID
|
798
|
-
? this.cancelWakeupMap.registerCallback(callerID, (_res) => {
|
799
|
-
resolveNotification();
|
800
|
-
})
|
801
|
-
: undefined;
|
802
|
-
try {
|
803
|
-
if (callerID)
|
804
|
-
await this.checkIfCanceled(callerID);
|
805
|
-
try {
|
806
|
-
const { rows } = await this.pool.query(`SELECT status, output, error FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status WHERE workflow_uuid=$1`, [workflowID]);
|
807
|
-
if (rows.length > 0) {
|
808
|
-
const status = rows[0].status;
|
809
|
-
if (status === workflow_1.StatusString.SUCCESS) {
|
810
|
-
return { res: rows[0].output };
|
811
|
-
}
|
812
|
-
else if (status === workflow_1.StatusString.ERROR) {
|
813
|
-
return { err: rows[0].error };
|
814
|
-
}
|
815
|
-
else if (status === workflow_1.StatusString.CANCELLED) {
|
816
|
-
return { cancelled: true };
|
817
|
-
}
|
818
|
-
else {
|
819
|
-
// Status is not actionable
|
820
|
-
}
|
821
|
-
}
|
695
|
+
const { rows } = await this.pool.query(`SELECT status, output, error FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status WHERE workflow_uuid=$1`, [workflowID]);
|
696
|
+
if (rows.length > 0) {
|
697
|
+
const status = rows[0].status;
|
698
|
+
if (status === workflow_1.StatusString.SUCCESS) {
|
699
|
+
return { res: rows[0].output };
|
822
700
|
}
|
823
|
-
|
824
|
-
|
825
|
-
this.logger.error(`Exception from system database: ${err}`);
|
826
|
-
throw err;
|
701
|
+
else if (status === workflow_1.StatusString.ERROR) {
|
702
|
+
return { err: rows[0].error };
|
827
703
|
}
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
|
834
|
-
const { promise, cancel, endTime } = await this.durableSleepmsInternal(callerID, timerFuncID, timeoutms, this.dbPollingIntervalMs);
|
835
|
-
finishTime = endTime;
|
836
|
-
timeoutPromise = promise;
|
837
|
-
timeoutCancel = cancel;
|
704
|
+
}
|
705
|
+
if (et !== undefined) {
|
706
|
+
const ct = new Date().getTime();
|
707
|
+
if (et > ct) {
|
708
|
+
await (0, utils_1.sleepms)(Math.min(pollingIntervalMs, et - ct));
|
838
709
|
}
|
839
710
|
else {
|
840
|
-
|
841
|
-
poll = Math.min(this.dbPollingIntervalMs, poll);
|
842
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
843
|
-
timeoutPromise = promise;
|
844
|
-
timeoutCancel = cancel;
|
845
|
-
}
|
846
|
-
try {
|
847
|
-
await Promise.race([statusPromise, timeoutPromise]);
|
848
|
-
}
|
849
|
-
finally {
|
850
|
-
timeoutCancel();
|
711
|
+
break;
|
851
712
|
}
|
852
713
|
}
|
853
|
-
|
854
|
-
|
855
|
-
if (crh)
|
856
|
-
this.cancelWakeupMap.deregisterCallback(crh);
|
714
|
+
else {
|
715
|
+
await (0, utils_1.sleepms)(pollingIntervalMs);
|
857
716
|
}
|
858
717
|
}
|
718
|
+
return undefined;
|
859
719
|
}
|
860
720
|
/* BACKGROUND PROCESSES */
|
861
721
|
/**
|
@@ -866,29 +726,15 @@ class PostgresSystemDatabase {
|
|
866
726
|
this.notificationsClient = await this.pool.connect();
|
867
727
|
await this.notificationsClient.query('LISTEN dbos_notifications_channel;');
|
868
728
|
await this.notificationsClient.query('LISTEN dbos_workflow_events_channel;');
|
869
|
-
await this.notificationsClient.query('LISTEN dbos_workflow_cancel_channel;');
|
870
729
|
const handler = (msg) => {
|
871
|
-
if (!this.shouldUseDBNotifications)
|
872
|
-
return; // Testing parameter
|
873
730
|
if (msg.channel === 'dbos_notifications_channel') {
|
874
|
-
if (msg.payload) {
|
875
|
-
this.notificationsMap
|
876
|
-
}
|
877
|
-
}
|
878
|
-
else if (msg.channel === 'dbos_workflow_events_channel') {
|
879
|
-
if (msg.payload) {
|
880
|
-
this.workflowEventsMap.callCallbacks(msg.payload);
|
731
|
+
if (msg.payload && msg.payload in this.notificationsMap) {
|
732
|
+
this.notificationsMap[msg.payload]();
|
881
733
|
}
|
882
734
|
}
|
883
|
-
else
|
884
|
-
if (msg.payload) {
|
885
|
-
|
886
|
-
if (notif.cancelled === 't') {
|
887
|
-
this.setWFCancelMap(notif.wfid);
|
888
|
-
}
|
889
|
-
else {
|
890
|
-
this.clearWFCancelMap(notif.wfid);
|
891
|
-
}
|
735
|
+
else {
|
736
|
+
if (msg.payload && msg.payload in this.workflowEventsMap) {
|
737
|
+
this.workflowEventsMap[msg.payload]();
|
892
738
|
}
|
893
739
|
}
|
894
740
|
};
|
@@ -941,29 +787,35 @@ class PostgresSystemDatabase {
|
|
941
787
|
: undefined,
|
942
788
|
};
|
943
789
|
}
|
944
|
-
async
|
790
|
+
async listWorkflows(input) {
|
791
|
+
const schemaName = dbos_executor_1.DBOSExecutor.systemDBSchemaName;
|
945
792
|
input.sortDesc = input.sortDesc ?? false; // By default, sort in ascending order
|
946
|
-
let query = this.knexDB(`${
|
793
|
+
let query = this.knexDB(`${schemaName}.workflow_status`)
|
794
|
+
.join(`${schemaName}.workflow_inputs`, `${schemaName}.workflow_status.workflow_uuid`, `${schemaName}.workflow_inputs.workflow_uuid`)
|
795
|
+
.orderBy(`${schemaName}.workflow_status.created_at`, input.sortDesc ? 'desc' : 'asc');
|
947
796
|
if (input.workflowName) {
|
948
|
-
query = query.where(
|
797
|
+
query = query.where(`${schemaName}.workflow_status.name`, input.workflowName);
|
798
|
+
}
|
799
|
+
if (input.workflow_id_prefix) {
|
800
|
+
query = query.whereLike(`${schemaName}.workflow_status.workflow_uuid`, `${input.workflow_id_prefix}%`);
|
949
801
|
}
|
950
802
|
if (input.workflowIDs) {
|
951
|
-
query = query.whereIn(
|
803
|
+
query = query.whereIn(`${schemaName}.workflow_status.workflow_uuid`, input.workflowIDs);
|
952
804
|
}
|
953
805
|
if (input.authenticatedUser) {
|
954
|
-
query = query.where(
|
806
|
+
query = query.where(`${schemaName}.workflow_status.authenticated_user`, input.authenticatedUser);
|
955
807
|
}
|
956
808
|
if (input.startTime) {
|
957
|
-
query = query.where(
|
809
|
+
query = query.where(`${schemaName}.workflow_status.created_at`, '>=', new Date(input.startTime).getTime());
|
958
810
|
}
|
959
811
|
if (input.endTime) {
|
960
|
-
query = query.where(
|
812
|
+
query = query.where(`${schemaName}.workflow_status.created_at`, '<=', new Date(input.endTime).getTime());
|
961
813
|
}
|
962
814
|
if (input.status) {
|
963
|
-
query = query.where(
|
815
|
+
query = query.where(`${schemaName}.workflow_status.status`, input.status);
|
964
816
|
}
|
965
817
|
if (input.applicationVersion) {
|
966
|
-
query = query.where(
|
818
|
+
query = query.where(`${schemaName}.workflow_status.application_version`, input.applicationVersion);
|
967
819
|
}
|
968
820
|
if (input.limit) {
|
969
821
|
query = query.limit(input.limit);
|
@@ -971,31 +823,30 @@ class PostgresSystemDatabase {
|
|
971
823
|
if (input.offset) {
|
972
824
|
query = query.offset(input.offset);
|
973
825
|
}
|
974
|
-
const rows = await query
|
975
|
-
|
976
|
-
return {
|
977
|
-
workflowUUIDs: workflowUUIDs,
|
978
|
-
};
|
826
|
+
const rows = await query;
|
827
|
+
return rows.map(PostgresSystemDatabase.#mapWorkflowStatus);
|
979
828
|
}
|
980
|
-
async
|
829
|
+
async listQueuedWorkflows(input) {
|
830
|
+
const schemaName = dbos_executor_1.DBOSExecutor.systemDBSchemaName;
|
981
831
|
const sortDesc = input.sortDesc ?? false; // By default, sort in ascending order
|
982
|
-
let query = this.knexDB(`${
|
983
|
-
.join(`${
|
984
|
-
.
|
832
|
+
let query = this.knexDB(`${schemaName}.workflow_queue`)
|
833
|
+
.join(`${schemaName}.workflow_inputs`, `${schemaName}.workflow_queue.workflow_uuid`, `${schemaName}.workflow_inputs.workflow_uuid`)
|
834
|
+
.join(`${schemaName}.workflow_status`, `${schemaName}.workflow_queue.workflow_uuid`, `${schemaName}.workflow_status.workflow_uuid`)
|
835
|
+
.orderBy(`${schemaName}.workflow_status.created_at`, sortDesc ? 'desc' : 'asc');
|
985
836
|
if (input.workflowName) {
|
986
|
-
query = query.whereRaw(`${
|
837
|
+
query = query.whereRaw(`${schemaName}.workflow_status.name = ?`, [input.workflowName]);
|
987
838
|
}
|
988
839
|
if (input.queueName) {
|
989
|
-
query = query.whereRaw(`${
|
840
|
+
query = query.whereRaw(`${schemaName}.workflow_status.queue_name = ?`, [input.queueName]);
|
990
841
|
}
|
991
842
|
if (input.startTime) {
|
992
|
-
query = query.where(`${
|
843
|
+
query = query.where(`${schemaName}.workflow_status.created_at`, '>=', new Date(input.startTime).getTime());
|
993
844
|
}
|
994
845
|
if (input.endTime) {
|
995
|
-
query = query.where(`${
|
846
|
+
query = query.where(`${schemaName}.workflow_status.created_at`, '<=', new Date(input.endTime).getTime());
|
996
847
|
}
|
997
848
|
if (input.status) {
|
998
|
-
query = query.whereRaw(`${
|
849
|
+
query = query.whereRaw(`${schemaName}.workflow_status.status = ?`, [input.status]);
|
999
850
|
}
|
1000
851
|
if (input.limit) {
|
1001
852
|
query = query.limit(input.limit);
|
@@ -1003,10 +854,30 @@ class PostgresSystemDatabase {
|
|
1003
854
|
if (input.offset) {
|
1004
855
|
query = query.offset(input.offset);
|
1005
856
|
}
|
1006
|
-
const rows = await query
|
1007
|
-
|
857
|
+
const rows = await query;
|
858
|
+
return rows.map(PostgresSystemDatabase.#mapWorkflowStatus);
|
859
|
+
}
|
860
|
+
static #mapWorkflowStatus(row) {
|
1008
861
|
return {
|
1009
|
-
|
862
|
+
workflowUUID: row.workflow_uuid,
|
863
|
+
status: row.status,
|
864
|
+
workflowName: row.name,
|
865
|
+
output: row.output ? row.output : null,
|
866
|
+
error: row.error ? row.error : null,
|
867
|
+
workflowClassName: row.class_name ?? '',
|
868
|
+
workflowConfigName: row.config_name ?? '',
|
869
|
+
queueName: row.queue_name,
|
870
|
+
authenticatedUser: row.authenticated_user,
|
871
|
+
assumedRole: row.assumed_role,
|
872
|
+
authenticatedRoles: JSON.parse(row.authenticated_roles),
|
873
|
+
request: JSON.parse(row.request),
|
874
|
+
executorId: row.executor_id,
|
875
|
+
createdAt: Number(row.created_at),
|
876
|
+
updatedAt: Number(row.updated_at),
|
877
|
+
applicationVersion: row.application_version,
|
878
|
+
applicationID: row.application_id,
|
879
|
+
recoveryAttempts: Number(row.recovery_attempts),
|
880
|
+
input: row.inputs,
|
1010
881
|
};
|
1011
882
|
}
|
1012
883
|
async getWorkflowQueue(input) {
|
@@ -1085,7 +956,7 @@ class PostgresSystemDatabase {
|
|
1085
956
|
}
|
1086
957
|
async dequeueWorkflow(workflowId, queue) {
|
1087
958
|
if (queue.rateLimit) {
|
1088
|
-
const time = Date.
|
959
|
+
const time = new Date().getTime();
|
1089
960
|
await this.pool.query(`
|
1090
961
|
UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
|
1091
962
|
SET completed_at_epoch_ms = $2
|
@@ -1100,7 +971,7 @@ class PostgresSystemDatabase {
|
|
1100
971
|
}
|
1101
972
|
}
|
1102
973
|
async findAndMarkStartableWorkflows(queue, executorID, appVersion) {
|
1103
|
-
const startTimeMs = Date.
|
974
|
+
const startTimeMs = new Date().getTime();
|
1104
975
|
const limiterPeriodMS = queue.rateLimit ? queue.rateLimit.periodSec * 1000 : 0;
|
1105
976
|
const claimedIDs = [];
|
1106
977
|
await this.knexDB.transaction(async (trx) => {
|