@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.
Files changed (70) hide show
  1. package/dbos-config.schema.json +28 -19
  2. package/dist/dbos-config.schema.json +28 -19
  3. package/dist/schemas/system_db_schema.d.ts +0 -4
  4. package/dist/schemas/system_db_schema.d.ts.map +1 -1
  5. package/dist/src/client.d.ts.map +1 -1
  6. package/dist/src/client.js +5 -7
  7. package/dist/src/client.js.map +1 -1
  8. package/dist/src/conductor/conductor.d.ts.map +1 -1
  9. package/dist/src/conductor/conductor.js +6 -12
  10. package/dist/src/conductor/conductor.js.map +1 -1
  11. package/dist/src/conductor/protocol.d.ts +2 -2
  12. package/dist/src/conductor/protocol.d.ts.map +1 -1
  13. package/dist/src/conductor/protocol.js +2 -2
  14. package/dist/src/conductor/protocol.js.map +1 -1
  15. package/dist/src/dbos-executor.d.ts +22 -7
  16. package/dist/src/dbos-executor.d.ts.map +1 -1
  17. package/dist/src/dbos-executor.js +154 -36
  18. package/dist/src/dbos-executor.js.map +1 -1
  19. package/dist/src/dbos-runtime/cli.d.ts.map +1 -1
  20. package/dist/src/dbos-runtime/cli.js +2 -0
  21. package/dist/src/dbos-runtime/cli.js.map +1 -1
  22. package/dist/src/dbos-runtime/config.js +3 -3
  23. package/dist/src/dbos-runtime/config.js.map +1 -1
  24. package/dist/src/dbos-runtime/workflow_management.d.ts +6 -13
  25. package/dist/src/dbos-runtime/workflow_management.d.ts.map +1 -1
  26. package/dist/src/dbos-runtime/workflow_management.js +23 -36
  27. package/dist/src/dbos-runtime/workflow_management.js.map +1 -1
  28. package/dist/src/dbos.d.ts +17 -11
  29. package/dist/src/dbos.d.ts.map +1 -1
  30. package/dist/src/dbos.js +47 -33
  31. package/dist/src/dbos.js.map +1 -1
  32. package/dist/src/error.d.ts +11 -9
  33. package/dist/src/error.d.ts.map +1 -1
  34. package/dist/src/error.js +26 -23
  35. package/dist/src/error.js.map +1 -1
  36. package/dist/src/eventreceiver.d.ts +7 -3
  37. package/dist/src/eventreceiver.d.ts.map +1 -1
  38. package/dist/src/httpServer/handler.d.ts.map +1 -1
  39. package/dist/src/httpServer/handler.js +2 -1
  40. package/dist/src/httpServer/handler.js.map +1 -1
  41. package/dist/src/httpServer/middleware.js +2 -2
  42. package/dist/src/httpServer/middleware.js.map +1 -1
  43. package/dist/src/httpServer/server.d.ts +6 -0
  44. package/dist/src/httpServer/server.d.ts.map +1 -1
  45. package/dist/src/httpServer/server.js +40 -2
  46. package/dist/src/httpServer/server.js.map +1 -1
  47. package/dist/src/scheduler/scheduler.js +1 -1
  48. package/dist/src/scheduler/scheduler.js.map +1 -1
  49. package/dist/src/system_database.d.ts +28 -52
  50. package/dist/src/system_database.d.ts.map +1 -1
  51. package/dist/src/system_database.js +261 -390
  52. package/dist/src/system_database.js.map +1 -1
  53. package/dist/src/testing/testing_runtime.d.ts.map +1 -1
  54. package/dist/src/testing/testing_runtime.js +2 -1
  55. package/dist/src/testing/testing_runtime.js.map +1 -1
  56. package/dist/src/utils.d.ts +1 -0
  57. package/dist/src/utils.d.ts.map +1 -1
  58. package/dist/src/utils.js +3 -1
  59. package/dist/src/utils.js.map +1 -1
  60. package/dist/src/wfqueue.d.ts.map +1 -1
  61. package/dist/src/wfqueue.js +1 -2
  62. package/dist/src/wfqueue.js.map +1 -1
  63. package/dist/src/workflow.d.ts +16 -6
  64. package/dist/src/workflow.d.ts.map +1 -1
  65. package/dist/src/workflow.js +38 -7
  66. package/dist/src/workflow.js.map +1 -1
  67. package/dist/tsconfig.tsbuildinfo +1 -1
  68. package/package.json +1 -3
  69. package/migrations/20250421000000_workflowcancel.js +0 -15
  70. 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
- dbPollingIntervalMs = 1000;
101
- shouldUseDBNotifications = true;
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
- if (this.shouldUseDBNotifications) {
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, serializedInputs) {
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, $17)
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
- JSON.stringify(initStatus.authenticatedRoles),
216
- JSON.stringify(initStatus.request),
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 > initStatus.maxRetries + 1) {
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, initStatus.maxRetries);
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 { serializedInputs: rows[0].inputs, status };
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.DBOSWorkflowConflictError(`Attempt to record transition of nonexistent workflow ${workflowID}`);
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.DBOSWorkflowConflictError(workflowID);
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
- const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
444
- let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
445
- while (true) {
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
- const cbr = this.notificationsMap.registerCallback(payload, resolveNotification);
453
- const crh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
454
- resolveNotification();
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.checkIfCanceled(workflowID);
458
- // Check if the key is already in the DB, then wait for the notification if it isn't.
459
- 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;
460
- if (initRecvRows.length !== 0)
461
- break;
462
- const ct = Date.now();
463
- if (finishTime && ct > finishTime)
464
- break; // Time's up
465
- let timeoutPromise = Promise.resolve();
466
- let timeoutCancel = () => { };
467
- if (timeoutms) {
468
- const { promise, cancel, endTime } = await this.durableSleepmsInternal(workflowID, timeoutFunctionID, timeoutms, this.dbPollingIntervalMs);
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
- this.notificationsMap.deregisterCallback(cbr);
489
- this.cancelWakeupMap.deregisterCallback(crh);
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
- while (true) {
571
- let resolveNotification;
572
- const valuePromise = new Promise((resolve) => {
573
- resolveNotification = resolve;
574
- });
575
- const cbr = this.workflowEventsMap.registerCallback(payloadKey, resolveNotification);
576
- const crh = callerWorkflow?.workflowID
577
- ? this.cancelWakeupMap.registerCallback(callerWorkflow.workflowID, (_res) => {
578
- resolveNotification();
579
- })
580
- : undefined;
581
- try {
582
- if (callerWorkflow?.workflowID)
583
- await this.checkIfCanceled(callerWorkflow?.workflowID);
584
- // Check if the key is already in the DB, then wait for the notification if it isn't.
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 && timeoutms) {
600
- const { promise, cancel, endTime } = await this.durableSleepmsInternal(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms, this.dbPollingIntervalMs);
601
- timeoutPromise = promise;
602
- timeoutCancel = cancel;
603
- finishTime = endTime;
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
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
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
- finally {
620
- this.workflowEventsMap.deregisterCallback(cbr);
621
- if (crh)
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(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_cancel
694
- WHERE workflow_id = $1`, [workflowID]);
695
- // Update status to pending and reset recovery attempts
696
- await this.recordWorkflowStatusChange(workflowID, workflow_1.StatusString.PENDING, { resetRecoveryAttempts: true }, client);
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 { rows } = await this.pool.query(`SELECT workflow_uuid, status, name, class_name, config_name, authenticated_user, assumed_role, authenticated_roles, request, queue_name, executor_id, created_at, updated_at, application_version, application_id, recovery_attempts FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status WHERE workflow_uuid=$1`, [workflowID]);
758
- let value = null;
759
- if (rows.length > 0) {
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, timeoutSeconds, callerID, timerFuncID) {
787
- const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
788
- let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
691
+ async awaitWorkflowResult(workflowID, timeoutms) {
692
+ const pollingIntervalMs = 1000;
693
+ const et = timeoutms !== undefined ? new Date().getTime() + timeoutms : undefined;
789
694
  while (true) {
790
- let resolveNotification;
791
- const statusPromise = new Promise((resolve) => {
792
- resolveNotification = resolve;
793
- });
794
- const irh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
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
- catch (e) {
824
- const err = e;
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
- const ct = Date.now();
829
- if (finishTime && ct > finishTime)
830
- return undefined; // Time's up
831
- let timeoutPromise = Promise.resolve();
832
- let timeoutCancel = () => { };
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
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
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
- finally {
854
- this.cancelWakeupMap.deregisterCallback(irh);
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.callCallbacks(msg.payload);
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 if (msg.channel === 'dbos_workflow_cancel_channel') {
884
- if (msg.payload) {
885
- const notif = JSON.parse(msg.payload);
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 getWorkflows(input) {
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(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status`).orderBy('created_at', input.sortDesc ? 'desc' : 'asc');
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('name', input.workflowName);
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('workflow_uuid', input.workflowIDs);
803
+ query = query.whereIn(`${schemaName}.workflow_status.workflow_uuid`, input.workflowIDs);
952
804
  }
953
805
  if (input.authenticatedUser) {
954
- query = query.where('authenticated_user', input.authenticatedUser);
806
+ query = query.where(`${schemaName}.workflow_status.authenticated_user`, input.authenticatedUser);
955
807
  }
956
808
  if (input.startTime) {
957
- query = query.where('created_at', '>=', new Date(input.startTime).getTime());
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('created_at', '<=', new Date(input.endTime).getTime());
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('status', input.status);
815
+ query = query.where(`${schemaName}.workflow_status.status`, input.status);
964
816
  }
965
817
  if (input.applicationVersion) {
966
- query = query.where('application_version', input.applicationVersion);
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.select('workflow_uuid');
975
- const workflowUUIDs = rows.map((row) => row.workflow_uuid);
976
- return {
977
- workflowUUIDs: workflowUUIDs,
978
- };
826
+ const rows = await query;
827
+ return rows.map(PostgresSystemDatabase.#mapWorkflowStatus);
979
828
  }
980
- async getQueuedWorkflows(input) {
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(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue`)
983
- .join(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status`, `${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue.workflow_uuid`, '=', `${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.workflow_uuid`)
984
- .orderBy(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.created_at`, sortDesc ? 'desc' : 'asc');
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(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.name = ?`, [input.workflowName]);
837
+ query = query.whereRaw(`${schemaName}.workflow_status.name = ?`, [input.workflowName]);
987
838
  }
988
839
  if (input.queueName) {
989
- query = query.whereRaw(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.queue_name = ?`, [input.queueName]);
840
+ query = query.whereRaw(`${schemaName}.workflow_status.queue_name = ?`, [input.queueName]);
990
841
  }
991
842
  if (input.startTime) {
992
- query = query.where(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.created_at`, '>=', new Date(input.startTime).getTime());
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(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.created_at`, '<=', new Date(input.endTime).getTime());
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(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.status = ?`, [input.status]);
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.select(`${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status.workflow_uuid`);
1007
- const workflowUUIDs = rows.map((row) => row.workflow_uuid);
857
+ const rows = await query;
858
+ return rows.map(PostgresSystemDatabase.#mapWorkflowStatus);
859
+ }
860
+ static #mapWorkflowStatus(row) {
1008
861
  return {
1009
- workflowUUIDs: workflowUUIDs,
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.now();
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.now();
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) => {