@dbos-inc/dbos-sdk 5.3.6-test.0a362f9 → 5.3.7-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.
@@ -1,944 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.Conductor = void 0;
30
- const decorators_1 = require("../decorators");
31
- const utils_1 = require("../utils");
32
- const ws_1 = __importDefault(require("ws"));
33
- const protocol = __importStar(require("./protocol"));
34
- const node_os_1 = require("node:os");
35
- const workflow_management_1 = require("../workflow_management");
36
- const node_assert_1 = __importDefault(require("node:assert"));
37
- const zlib = __importStar(require("node:zlib"));
38
- const node_util_1 = require("node:util");
39
- const scheduler_1 = require("../scheduler/scheduler");
40
- const gzip = (0, node_util_1.promisify)(zlib.gzip);
41
- const gunzip = (0, node_util_1.promisify)(zlib.gunzip);
42
- // Commands that exist only to move workflow data, refused in metadata-only mode.
43
- const DATA_MESSAGE_TYPES = new Set([
44
- protocol.MessageType.GET_WORKFLOW_EVENTS,
45
- protocol.MessageType.GET_WORKFLOW_NOTIFICATIONS,
46
- protocol.MessageType.GET_WORKFLOW_STREAMS,
47
- protocol.MessageType.EXPORT_WORKFLOW,
48
- protocol.MessageType.IMPORT_WORKFLOW,
49
- ]);
50
- class Conductor {
51
- dbosExec;
52
- appName;
53
- conductorKey;
54
- conductorURL;
55
- executorMetadata;
56
- metadataOnlyMode;
57
- url;
58
- websocket = undefined;
59
- isShuttingDown = false; // Is in the process of shutting down the connection
60
- pingPeriodMs = 20000; // Time in milliseconds to wait before sending a ping message to the conductor
61
- pingTimeoutMs = 15000; // Time in milliseconds to wait for a response to a ping message before considering the connection dead
62
- pingIntervalTimeout = undefined; // Combined interval and timeout for pinging Conductor
63
- reconnectDelayMs = 1000;
64
- reconnectTimeout = undefined;
65
- // The retention round running off the message loop, if there is one.
66
- retentionRound = undefined;
67
- constructor(dbosExec, appName, conductorKey, conductorURL, executorMetadata, metadataOnlyMode = false) {
68
- this.dbosExec = dbosExec;
69
- this.appName = appName;
70
- this.conductorKey = conductorKey;
71
- this.conductorURL = conductorURL;
72
- this.executorMetadata = executorMetadata;
73
- this.metadataOnlyMode = metadataOnlyMode;
74
- (0, node_assert_1.default)(appName, 'Application name must be set in configuration in order to use DBOS Conductor');
75
- const cleanConductorURL = conductorURL.replace(/\/+$/, '');
76
- this.url = `${cleanConductorURL}/websocket/${appName}/${conductorKey}`;
77
- }
78
- /** Logs a command's exception in full and returns the error message Conductor may see. */
79
- reportException(context, e) {
80
- const errorMsg = `${context}: ${e.message}`;
81
- this.dbosExec.logger.error(errorMsg);
82
- if (this.metadataOnlyMode) {
83
- const exceptionName = e instanceof Error ? e.constructor.name : typeof e;
84
- return `${context}: ${exceptionName} (details withheld in metadata-only mode)`;
85
- }
86
- return errorMsg;
87
- }
88
- resetWebsocket(currWebsocket, currPing) {
89
- clearInterval(currPing?.interval);
90
- clearTimeout(currPing?.timeout);
91
- if (currWebsocket) {
92
- currWebsocket.terminate(); // Terminate the existing connection
93
- if (this.websocket === currWebsocket) {
94
- // Only clear if it's the same websocket
95
- this.websocket = undefined;
96
- }
97
- }
98
- if (this.reconnectTimeout || this.isShuttingDown) {
99
- return;
100
- }
101
- this.dbosExec.logger.debug(`Reconnecting in ${this.reconnectDelayMs / 1000} second`);
102
- this.reconnectTimeout = setTimeout(() => {
103
- this.dispatchLoop();
104
- }, this.reconnectDelayMs);
105
- }
106
- setPingInterval(currWebsocket, currPing) {
107
- // Clear any existing ping interval to avoid multiple intervals being set
108
- clearInterval(currPing.interval);
109
- clearTimeout(currPing.timeout);
110
- currPing.interval = setInterval(() => {
111
- // Set ping timeout before sending ping. This prevents the socket from hanging at a non-open state indefinitely
112
- currPing.timeout = setTimeout(() => {
113
- if (this.isShuttingDown) {
114
- return;
115
- }
116
- else if (this.reconnectTimeout === undefined) {
117
- // Otherwise, try to reconnect if we haven't already
118
- this.dbosExec.logger.warn('Connection to conductor lost. Reconnecting...');
119
- this.resetWebsocket(currWebsocket, currPing);
120
- }
121
- }, this.pingTimeoutMs);
122
- // Only ping if the connection is open
123
- if (currWebsocket.readyState === ws_1.default.OPEN) {
124
- this.dbosExec.logger.debug('Sending ping to conductor');
125
- currWebsocket.ping();
126
- }
127
- }, this.pingPeriodMs);
128
- }
129
- dispatchLoop() {
130
- if (this.websocket &&
131
- (this.websocket.readyState === ws_1.default.OPEN || this.websocket.readyState === ws_1.default.CONNECTING)) {
132
- this.dbosExec.logger.warn('Conductor websocket already exists');
133
- return;
134
- }
135
- clearTimeout(this.pingIntervalTimeout?.timeout);
136
- clearInterval(this.pingIntervalTimeout?.interval);
137
- clearTimeout(this.reconnectTimeout);
138
- this.reconnectTimeout = undefined;
139
- if (this.isShuttingDown) {
140
- this.dbosExec.logger.debug('Not starting dispatch loop as conductor is shutting down');
141
- return;
142
- }
143
- try {
144
- this.dbosExec.logger.debug(`Connecting to conductor at ${this.url}`);
145
- // Start a new websocket connection
146
- const currWebsocket = new ws_1.default(this.url, { handshakeTimeout: 5000 });
147
- this.websocket = currWebsocket;
148
- // Start ping interval
149
- const currPing = { interval: undefined, timeout: undefined };
150
- this.setPingInterval(currWebsocket, currPing);
151
- this.pingIntervalTimeout = currPing;
152
- currWebsocket.on('open', () => {
153
- this.dbosExec.logger.debug('Opened connection to DBOS conductor');
154
- clearTimeout(currPing.timeout);
155
- });
156
- currWebsocket.on('pong', () => {
157
- this.dbosExec.logger.debug('Received pong from conductor');
158
- clearTimeout(currPing.timeout);
159
- });
160
- currWebsocket.on('message', async (data) => {
161
- this.dbosExec.logger.debug(`Received message from conductor: ${data}`);
162
- const baseMsg = JSON.parse(data);
163
- const msgType = baseMsg.type;
164
- let errorMsg = undefined;
165
- clearTimeout(currPing.timeout);
166
- if (this.metadataOnlyMode && DATA_MESSAGE_TYPES.has(msgType)) {
167
- const refusedResp = new protocol.BaseResponse(msgType, baseMsg.request_id, `${msgType} is not allowed in conductor metadata-only mode`);
168
- currWebsocket.send(JSON.stringify(refusedResp));
169
- return;
170
- }
171
- switch (msgType) {
172
- case protocol.MessageType.EXECUTOR_INFO:
173
- const infoResp = new protocol.ExecutorInfoResponse(baseMsg.request_id, utils_1.globalParams.executorID, utils_1.globalParams.appVersion, (0, node_os_1.hostname)(), 'typescript', utils_1.globalParams.dbosVersion, undefined, this.executorMetadata);
174
- currWebsocket.send(JSON.stringify(infoResp));
175
- this.dbosExec.logger.info('Connected to DBOS conductor');
176
- break;
177
- case protocol.MessageType.RECOVERY:
178
- const recoveryMsg = baseMsg;
179
- let success = true;
180
- try {
181
- await this.dbosExec.recoverPendingWorkflows(recoveryMsg.executor_ids);
182
- }
183
- catch (e) {
184
- errorMsg = this.reportException('Exception encountered when recovering workflows', e);
185
- success = false;
186
- }
187
- const recoveryResp = new protocol.RecoveryResponse(baseMsg.request_id, success, errorMsg);
188
- currWebsocket.send(JSON.stringify(recoveryResp));
189
- break;
190
- case protocol.MessageType.CANCEL:
191
- const cancelMsg = baseMsg;
192
- const cancelIds = cancelMsg.workflow_ids ?? [cancelMsg.workflow_id];
193
- let cancelSuccess = true;
194
- try {
195
- await this.dbosExec.systemDatabase.cancelWorkflows(cancelIds, cancelMsg.cancel_children);
196
- }
197
- catch (e) {
198
- errorMsg = this.reportException(`Exception encountered when cancelling workflow(s) ${String(cancelIds)}`, e);
199
- cancelSuccess = false;
200
- }
201
- const cancelResp = new protocol.CancelResponse(baseMsg.request_id, cancelSuccess, errorMsg);
202
- currWebsocket.send(JSON.stringify(cancelResp));
203
- break;
204
- case protocol.MessageType.DELETE:
205
- const deleteMsg = baseMsg;
206
- const deleteIds = deleteMsg.workflow_ids ?? [deleteMsg.workflow_id];
207
- let deleteSuccess = true;
208
- try {
209
- await this.dbosExec.systemDatabase.deleteWorkflows(deleteIds, deleteMsg.delete_children ?? false);
210
- }
211
- catch (e) {
212
- errorMsg = this.reportException(`Exception encountered when deleting workflow(s) ${String(deleteIds)}`, e);
213
- deleteSuccess = false;
214
- }
215
- const deleteResp = new protocol.DeleteResponse(baseMsg.request_id, deleteSuccess, errorMsg);
216
- currWebsocket.send(JSON.stringify(deleteResp));
217
- break;
218
- case protocol.MessageType.RESUME:
219
- const resumeMsg = baseMsg;
220
- const resumeIds = resumeMsg.workflow_ids ?? [resumeMsg.workflow_id];
221
- let resumeSuccess = true;
222
- try {
223
- await this.dbosExec.systemDatabase.resumeWorkflows(resumeIds, resumeMsg.queue_name);
224
- }
225
- catch (e) {
226
- errorMsg = this.reportException(`Exception encountered when resuming workflow(s) ${String(resumeIds)}`, e);
227
- resumeSuccess = false;
228
- }
229
- const resumeResp = new protocol.ResumeResponse(baseMsg.request_id, resumeSuccess, errorMsg);
230
- currWebsocket.send(JSON.stringify(resumeResp));
231
- break;
232
- case protocol.MessageType.REWIND_WORKFLOW:
233
- const rewindMsg = baseMsg;
234
- const rewindBody = rewindMsg.body;
235
- let rewindSuccess = true;
236
- try {
237
- await this.dbosExec.rewindWorkflow(rewindBody.workflow_id, rewindBody.start_step ?? 0, {
238
- applicationVersion: rewindBody.application_version,
239
- queueName: rewindBody.queue_name,
240
- queuePartitionKey: rewindBody.queue_partition_key,
241
- });
242
- }
243
- catch (e) {
244
- errorMsg = this.reportException(`Exception encountered when rewinding workflow ${rewindBody.workflow_id} to step ${rewindBody.start_step ?? 0}`, e);
245
- rewindSuccess = false;
246
- }
247
- const rewindResp = new protocol.RewindWorkflowResponse(baseMsg.request_id, rewindSuccess, errorMsg);
248
- currWebsocket.send(JSON.stringify(rewindResp));
249
- break;
250
- case protocol.MessageType.FORK_WORKFLOW:
251
- const forkMsg = baseMsg;
252
- let newWorkflowID = forkMsg.body.new_workflow_id;
253
- try {
254
- newWorkflowID = await this.dbosExec.forkWorkflow(forkMsg.body.workflow_id, forkMsg.body.start_step, {
255
- newWorkflowID: newWorkflowID,
256
- applicationVersion: forkMsg.body.application_version,
257
- queueName: forkMsg.body.queue_name,
258
- queuePartitionKey: forkMsg.body.queue_partition_key,
259
- });
260
- }
261
- catch (e) {
262
- errorMsg = this.reportException(`Exception encountered when forking workflow ${forkMsg.body.workflow_id} to new workflow ${newWorkflowID} on step ${forkMsg.body.start_step}, app version ${forkMsg.body.application_version}`, e);
263
- newWorkflowID = undefined;
264
- }
265
- const forkResp = new protocol.ForkWorkflowResponse(baseMsg.request_id, newWorkflowID, errorMsg);
266
- currWebsocket.send(JSON.stringify(forkResp));
267
- break;
268
- case protocol.MessageType.FORK_FROM_FAILURE:
269
- const forkFromFailureMsg = baseMsg;
270
- const forkFromFailureBody = forkFromFailureMsg.body;
271
- let forkedWorkflowIDs = undefined;
272
- try {
273
- forkedWorkflowIDs = await this.dbosExec.systemDatabase.forkFromFailure(forkFromFailureBody.workflow_ids, {
274
- applicationVersion: forkFromFailureBody.application_version,
275
- queueName: forkFromFailureBody.queue_name,
276
- queuePartitionKey: forkFromFailureBody.queue_partition_key,
277
- fromLastFailure: forkFromFailureBody.from_last_failure ?? false,
278
- fromLastStep: forkFromFailureBody.from_last_step ?? false,
279
- fromStep: forkFromFailureBody.from_step,
280
- fromStepName: forkFromFailureBody.from_step_name,
281
- });
282
- }
283
- catch (e) {
284
- errorMsg = this.reportException('Exception encountered when bulk forking workflows', e);
285
- }
286
- const forkFromFailureResp = new protocol.ForkFromFailureResponse(baseMsg.request_id, forkedWorkflowIDs, errorMsg);
287
- currWebsocket.send(JSON.stringify(forkFromFailureResp));
288
- break;
289
- case protocol.MessageType.LIST_WORKFLOWS:
290
- const listWFMsg = baseMsg;
291
- const body = listWFMsg.body;
292
- const listWFReq = {
293
- workflowIDs: body.workflow_uuids,
294
- workflowName: body.workflow_name,
295
- authenticatedUser: body.authenticated_user,
296
- startTime: body.start_time,
297
- endTime: body.end_time,
298
- completedAfter: body.completed_after,
299
- completedBefore: body.completed_before,
300
- dequeuedAfter: body.dequeued_after,
301
- dequeuedBefore: body.dequeued_before,
302
- status: body.status,
303
- applicationVersion: body.application_version,
304
- forkedFrom: body.forked_from,
305
- parentWorkflowID: body.parent_workflow_id,
306
- queueName: body.queue_name,
307
- limit: body.limit,
308
- offset: body.offset,
309
- sortDesc: body.sort_desc,
310
- workflow_id_prefix: body.workflow_id_prefix,
311
- loadInput: (body.load_input ?? false) && !this.metadataOnlyMode, // Default to false if not provided
312
- loadOutput: (body.load_output ?? false) && !this.metadataOnlyMode, // Default to false if not provided
313
- executorId: body.executor_id,
314
- queuesOnly: body.queues_only,
315
- wasForkedFrom: body.was_forked_from,
316
- isFork: body.is_fork,
317
- hasParent: body.has_parent,
318
- attributes: body.attributes,
319
- scheduleName: body.schedule_name,
320
- applicationName: body.application_name,
321
- };
322
- let workflowsOutput = [];
323
- try {
324
- const workflows = await this.dbosExec.listWorkflows(listWFReq);
325
- workflowsOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
326
- }
327
- catch (e) {
328
- errorMsg = this.reportException('Exception encountered when listing workflows', e);
329
- }
330
- const wfsResp = new protocol.ListWorkflowsResponse(listWFMsg.request_id, workflowsOutput, errorMsg);
331
- currWebsocket.send(JSON.stringify(wfsResp));
332
- break;
333
- case protocol.MessageType.LIST_QUEUED_WORKFLOWS:
334
- const listQueuedWFMsg = baseMsg;
335
- const bodyQueued = listQueuedWFMsg.body;
336
- const listQueuedWFReq = {
337
- workflowIDs: bodyQueued.workflow_uuids,
338
- workflowName: bodyQueued.workflow_name,
339
- authenticatedUser: bodyQueued.authenticated_user,
340
- startTime: bodyQueued.start_time,
341
- endTime: bodyQueued.end_time,
342
- completedAfter: bodyQueued.completed_after,
343
- completedBefore: bodyQueued.completed_before,
344
- dequeuedAfter: bodyQueued.dequeued_after,
345
- dequeuedBefore: bodyQueued.dequeued_before,
346
- status: bodyQueued.status,
347
- applicationVersion: bodyQueued.application_version,
348
- forkedFrom: bodyQueued.forked_from,
349
- parentWorkflowID: bodyQueued.parent_workflow_id,
350
- queueName: bodyQueued.queue_name,
351
- limit: bodyQueued.limit,
352
- offset: bodyQueued.offset,
353
- sortDesc: bodyQueued.sort_desc,
354
- workflow_id_prefix: bodyQueued.workflow_id_prefix,
355
- loadInput: (bodyQueued.load_input ?? false) && !this.metadataOnlyMode, // Default to false if not provided
356
- loadOutput: (bodyQueued.load_output ?? false) && !this.metadataOnlyMode, // Default to false if not provided
357
- executorId: bodyQueued.executor_id,
358
- wasForkedFrom: bodyQueued.was_forked_from,
359
- isFork: bodyQueued.is_fork,
360
- hasParent: bodyQueued.has_parent,
361
- attributes: bodyQueued.attributes,
362
- scheduleName: bodyQueued.schedule_name,
363
- applicationName: bodyQueued.application_name,
364
- };
365
- let queuedWFOutput = [];
366
- try {
367
- const workflows = await this.dbosExec.listQueuedWorkflows(listQueuedWFReq);
368
- queuedWFOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
369
- }
370
- catch (e) {
371
- errorMsg = this.reportException('Exception encountered when listing queued workflows', e);
372
- }
373
- const queuedWfsResp = new protocol.ListQueuedWorkflowsResponse(listQueuedWFMsg.request_id, queuedWFOutput, errorMsg);
374
- currWebsocket.send(JSON.stringify(queuedWfsResp));
375
- break;
376
- case protocol.MessageType.GET_WORKFLOW:
377
- const getWFMsg = baseMsg;
378
- let wfOutput = undefined;
379
- try {
380
- const workflows = await this.dbosExec.listWorkflows({
381
- workflowIDs: [getWFMsg.workflow_id],
382
- loadInput: (getWFMsg.load_input ?? true) && !this.metadataOnlyMode,
383
- loadOutput: (getWFMsg.load_output ?? true) && !this.metadataOnlyMode,
384
- });
385
- if (workflows.length > 0) {
386
- wfOutput = new protocol.WorkflowsOutput(workflows[0]);
387
- }
388
- }
389
- catch (e) {
390
- errorMsg = this.reportException(`Exception encountered when getting workflow ${getWFMsg.workflow_id}`, e);
391
- }
392
- const getWFResp = new protocol.GetWorkflowResponse(getWFMsg.request_id, wfOutput, errorMsg);
393
- currWebsocket.send(JSON.stringify(getWFResp));
394
- break;
395
- case protocol.MessageType.EXIST_PENDING_WORKFLOWS:
396
- const existPendingMsg = baseMsg;
397
- let hasPendingWFs = false;
398
- try {
399
- const pendingWFs = await this.dbosExec.systemDatabase.getPendingWorkflows(existPendingMsg.executor_id, existPendingMsg.application_version);
400
- hasPendingWFs = pendingWFs.length > 0;
401
- }
402
- catch (e) {
403
- errorMsg = this.reportException('Exception encountered when checking for pending workflows', e);
404
- }
405
- const existPendingResp = new protocol.ExistPendingWorkflowsResponse(baseMsg.request_id, hasPendingWFs, errorMsg);
406
- currWebsocket.send(JSON.stringify(existPendingResp));
407
- break;
408
- case protocol.MessageType.LIST_STEPS:
409
- const listStepsMessage = baseMsg;
410
- let workflowSteps = undefined;
411
- try {
412
- const stepsInfo = await this.dbosExec.listWorkflowSteps(listStepsMessage.workflow_id, (listStepsMessage.load_output ?? true) && !this.metadataOnlyMode, { limit: listStepsMessage.limit, offset: listStepsMessage.offset });
413
- workflowSteps = stepsInfo?.map((i) => new protocol.WorkflowSteps(i));
414
- }
415
- catch (e) {
416
- errorMsg = this.reportException(`Exception encountered when listing steps ${listStepsMessage.workflow_id}`, e);
417
- }
418
- const listStepsResponse = new protocol.ListStepsResponse(listStepsMessage.request_id, workflowSteps, errorMsg);
419
- currWebsocket.send(JSON.stringify(listStepsResponse));
420
- break;
421
- case protocol.MessageType.RETENTION:
422
- const retentionMessage = baseMsg;
423
- const retentionBody = retentionMessage.body;
424
- // Off the message loop: a round takes minutes, and awaiting it here stops every
425
- // other command and lets the dispatch time out into a second round on another executor.
426
- if (this.retentionRound !== undefined) {
427
- this.dbosExec.logger.warn('Skipping retention: the previous round on this executor is still running.');
428
- }
429
- else {
430
- this.retentionRound = (async () => {
431
- try {
432
- await (0, workflow_management_1.garbageCollect)(this.dbosExec.systemDatabase, retentionBody.gc_cutoff_epoch_ms, retentionBody.gc_rows_threshold,
433
- // An omitted, null, or zero gc_batch_size takes the default.
434
- { batchSize: retentionBody.gc_batch_size || undefined });
435
- if (retentionBody.timeout_cutoff_epoch_ms) {
436
- await (0, workflow_management_1.globalTimeout)(this.dbosExec.systemDatabase, retentionBody.timeout_cutoff_epoch_ms);
437
- }
438
- }
439
- catch (e) {
440
- if (this.isShuttingDown) {
441
- // Shutdown took the system database away mid-round; the next round resumes the work.
442
- this.dbosExec.logger.debug('Retention interrupted by shutdown');
443
- return;
444
- }
445
- this.dbosExec.logger.error(`Exception encountered during enforcing retention policy: ${e.message}`);
446
- }
447
- })().finally(() => {
448
- this.retentionRound = undefined;
449
- });
450
- }
451
- currWebsocket.send(JSON.stringify(new protocol.RetentionResponse(retentionMessage.request_id, true)));
452
- break;
453
- case protocol.MessageType.GET_METRICS:
454
- const getMetricsMessage = baseMsg;
455
- this.dbosExec.logger.debug(`Received metrics request for time range ${getMetricsMessage.start_time} to ${getMetricsMessage.end_time}`);
456
- let metricsData = [];
457
- if (getMetricsMessage.metric_class === 'workflow_step_count') {
458
- try {
459
- const sysMetrics = await this.dbosExec.systemDatabase.getMetrics(getMetricsMessage.start_time, getMetricsMessage.end_time, getMetricsMessage.application_name);
460
- metricsData = sysMetrics.map((m) => new protocol.MetricDataOutput(m.metricType, m.metricName, m.value));
461
- }
462
- catch (e) {
463
- errorMsg = this.reportException('Exception encountered when getting metrics', e);
464
- }
465
- }
466
- else {
467
- errorMsg = `Unexpected metric class: ${getMetricsMessage.metric_class}`;
468
- this.dbosExec.logger.warn(errorMsg);
469
- }
470
- const getMetricsResponse = new protocol.GetMetricsResponse(getMetricsMessage.request_id, metricsData, errorMsg);
471
- currWebsocket.send(JSON.stringify(getMetricsResponse));
472
- break;
473
- case protocol.MessageType.EXPORT_WORKFLOW:
474
- const exportMsg = baseMsg;
475
- let serializedWorkflow = null;
476
- try {
477
- const exported = await this.dbosExec.systemDatabase.exportWorkflow(exportMsg.workflow_id, exportMsg.export_children ?? false);
478
- if (exported.length > 0) {
479
- const jsonStr = JSON.stringify(exported);
480
- const compressed = await gzip(jsonStr);
481
- serializedWorkflow = compressed.toString('base64');
482
- }
483
- }
484
- catch (e) {
485
- errorMsg = this.reportException(`Exception encountered when exporting workflow ${exportMsg.workflow_id}`, e);
486
- }
487
- const exportResp = new protocol.ExportWorkflowResponse(baseMsg.request_id, serializedWorkflow, errorMsg);
488
- currWebsocket.send(JSON.stringify(exportResp));
489
- break;
490
- case protocol.MessageType.IMPORT_WORKFLOW:
491
- const importMsg = baseMsg;
492
- let importSuccess = true;
493
- try {
494
- const compressedData = Buffer.from(importMsg.serialized_workflow, 'base64');
495
- const decompressed = await gunzip(compressedData);
496
- const workflows = JSON.parse(decompressed.toString());
497
- await this.dbosExec.systemDatabase.importWorkflow(workflows);
498
- }
499
- catch (e) {
500
- errorMsg = this.reportException('Exception encountered when importing workflow', e);
501
- importSuccess = false;
502
- }
503
- const importResp = new protocol.ImportWorkflowResponse(baseMsg.request_id, importSuccess, errorMsg);
504
- currWebsocket.send(JSON.stringify(importResp));
505
- break;
506
- case protocol.MessageType.ALERT:
507
- const alertMsg = baseMsg;
508
- let alertSuccess = true;
509
- try {
510
- const handler = (0, decorators_1.getAlertHandler)();
511
- if (handler) {
512
- await Promise.resolve(handler(alertMsg.name, alertMsg.message, alertMsg.metadata));
513
- }
514
- else {
515
- this.dbosExec.logger.warn(`Alert received (no handler registered): [${alertMsg.name}] ${alertMsg.message} | Metadata: ${JSON.stringify(alertMsg.metadata)}`);
516
- }
517
- }
518
- catch (e) {
519
- errorMsg = this.reportException('Exception in alert handler', e);
520
- alertSuccess = false;
521
- }
522
- const alertResp = new protocol.AlertResponse(baseMsg.request_id, alertSuccess, errorMsg);
523
- currWebsocket.send(JSON.stringify(alertResp));
524
- break;
525
- case protocol.MessageType.LIST_SCHEDULES:
526
- const listSchedMsg = baseMsg;
527
- const loadContextList = (listSchedMsg.body.load_context ?? true) && !this.metadataOnlyMode;
528
- let schedOutput = [];
529
- try {
530
- const scheds = await this.dbosExec.systemDatabase.listSchedules({
531
- status: listSchedMsg.body.status,
532
- workflowName: listSchedMsg.body.workflow_name,
533
- scheduleNamePrefix: listSchedMsg.body.schedule_name_prefix,
534
- applicationName: listSchedMsg.body.application_name,
535
- });
536
- schedOutput = await Promise.all(scheds.map(async (s) => ({
537
- schedule_id: s.scheduleId,
538
- schedule_name: s.scheduleName,
539
- workflow_name: s.workflowName,
540
- workflow_class_name: s.workflowClassName || undefined,
541
- schedule: s.schedule,
542
- status: s.status,
543
- context: loadContextList
544
- ? protocol.inspectForConductor(await this.dbosExec.serializer.parse(s.context))
545
- : undefined,
546
- last_fired_at: s.lastFiredAt,
547
- automatic_backfill: s.automaticBackfill,
548
- cron_timezone: s.cronTimezone,
549
- queue_name: s.queueName,
550
- application_name: s.applicationName ?? null,
551
- })));
552
- }
553
- catch (e) {
554
- errorMsg = this.reportException('Exception encountered when listing schedules', e);
555
- }
556
- const listSchedResp = new protocol.ListSchedulesResponse(listSchedMsg.request_id, schedOutput, errorMsg);
557
- currWebsocket.send(JSON.stringify(listSchedResp));
558
- break;
559
- case protocol.MessageType.GET_SCHEDULE:
560
- const getSchedMsg = baseMsg;
561
- const loadContextGet = (getSchedMsg.load_context ?? true) && !this.metadataOnlyMode;
562
- let getSchedOutput = undefined;
563
- try {
564
- const sched = await this.dbosExec.systemDatabase.getSchedule(getSchedMsg.schedule_name);
565
- if (sched) {
566
- getSchedOutput = {
567
- schedule_id: sched.scheduleId,
568
- schedule_name: sched.scheduleName,
569
- workflow_name: sched.workflowName,
570
- workflow_class_name: sched.workflowClassName || undefined,
571
- schedule: sched.schedule,
572
- status: sched.status,
573
- context: loadContextGet
574
- ? protocol.inspectForConductor(await this.dbosExec.serializer.parse(sched.context))
575
- : undefined,
576
- last_fired_at: sched.lastFiredAt,
577
- automatic_backfill: sched.automaticBackfill,
578
- cron_timezone: sched.cronTimezone,
579
- queue_name: sched.queueName,
580
- application_name: sched.applicationName ?? null,
581
- };
582
- }
583
- }
584
- catch (e) {
585
- errorMsg = this.reportException(`Exception encountered when getting schedule ${getSchedMsg.schedule_name}`, e);
586
- }
587
- const getSchedResp = new protocol.GetScheduleResponse(getSchedMsg.request_id, getSchedOutput, errorMsg);
588
- currWebsocket.send(JSON.stringify(getSchedResp));
589
- break;
590
- case protocol.MessageType.PAUSE_SCHEDULE:
591
- const pauseSchedMsg = baseMsg;
592
- let pauseSuccess = true;
593
- try {
594
- await this.dbosExec.systemDatabase.setScheduleStatus(pauseSchedMsg.schedule_name, 'PAUSED');
595
- }
596
- catch (e) {
597
- errorMsg = this.reportException(`Exception encountered when pausing schedule '${pauseSchedMsg.schedule_name}'`, e);
598
- pauseSuccess = false;
599
- }
600
- const pauseSchedResp = new protocol.PauseScheduleResponse(baseMsg.request_id, pauseSuccess, errorMsg);
601
- currWebsocket.send(JSON.stringify(pauseSchedResp));
602
- break;
603
- case protocol.MessageType.RESUME_SCHEDULE:
604
- const resumeSchedMsg = baseMsg;
605
- let resumeSchedSuccess = true;
606
- try {
607
- await this.dbosExec.systemDatabase.setScheduleStatus(resumeSchedMsg.schedule_name, 'ACTIVE');
608
- }
609
- catch (e) {
610
- errorMsg = this.reportException(`Exception encountered when resuming schedule '${resumeSchedMsg.schedule_name}'`, e);
611
- resumeSchedSuccess = false;
612
- }
613
- const resumeSchedResp = new protocol.ResumeScheduleResponse(baseMsg.request_id, resumeSchedSuccess, errorMsg);
614
- currWebsocket.send(JSON.stringify(resumeSchedResp));
615
- break;
616
- case protocol.MessageType.BACKFILL_SCHEDULE:
617
- const backfillSchedMsg = baseMsg;
618
- let backfillWorkflowIds = [];
619
- try {
620
- backfillWorkflowIds = await (0, scheduler_1.backfillSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, backfillSchedMsg.schedule_name, new Date(backfillSchedMsg.start), new Date(backfillSchedMsg.end));
621
- }
622
- catch (e) {
623
- errorMsg = this.reportException(`Exception encountered when backfilling schedule '${backfillSchedMsg.schedule_name}'`, e);
624
- }
625
- const backfillSchedResp = new protocol.BackfillScheduleResponse(baseMsg.request_id, backfillWorkflowIds, errorMsg);
626
- currWebsocket.send(JSON.stringify(backfillSchedResp));
627
- break;
628
- case protocol.MessageType.TRIGGER_SCHEDULE:
629
- const triggerSchedMsg = baseMsg;
630
- let triggerWorkflowId = undefined;
631
- try {
632
- triggerWorkflowId = await (0, scheduler_1.triggerSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, triggerSchedMsg.schedule_name);
633
- }
634
- catch (e) {
635
- errorMsg = this.reportException(`Exception encountered when triggering schedule '${triggerSchedMsg.schedule_name}'`, e);
636
- }
637
- const triggerSchedResp = new protocol.TriggerScheduleResponse(baseMsg.request_id, triggerWorkflowId, errorMsg);
638
- currWebsocket.send(JSON.stringify(triggerSchedResp));
639
- break;
640
- case protocol.MessageType.LIST_APPLICATION_VERSIONS:
641
- let versionsOutput = [];
642
- try {
643
- const versions = await this.dbosExec.systemDatabase.listApplicationVersions();
644
- versionsOutput = versions.map((v) => ({
645
- version_id: v.versionId,
646
- version_name: v.versionName,
647
- version_timestamp: v.versionTimestamp,
648
- created_at: v.createdAt,
649
- }));
650
- }
651
- catch (e) {
652
- errorMsg = this.reportException('Exception encountered when listing application versions', e);
653
- }
654
- const listVersionsResp = new protocol.ListApplicationVersionsResponse(baseMsg.request_id, versionsOutput, errorMsg);
655
- currWebsocket.send(JSON.stringify(listVersionsResp));
656
- break;
657
- case protocol.MessageType.SET_LATEST_APPLICATION_VERSION:
658
- const setVersionMsg = baseMsg;
659
- let setVersionSuccess = true;
660
- try {
661
- await this.dbosExec.systemDatabase.updateApplicationVersionTimestamp(setVersionMsg.version_name, Date.now());
662
- }
663
- catch (e) {
664
- errorMsg = this.reportException(`Exception encountered when setting latest application version '${setVersionMsg.version_name}'`, e);
665
- setVersionSuccess = false;
666
- }
667
- const setVersionResp = new protocol.SetLatestApplicationVersionResponse(baseMsg.request_id, setVersionSuccess, errorMsg);
668
- currWebsocket.send(JSON.stringify(setVersionResp));
669
- break;
670
- case protocol.MessageType.GET_WORKFLOW_EVENTS:
671
- const eventsMsg = baseMsg;
672
- let eventOutputs = undefined;
673
- try {
674
- const rawEvents = await this.dbosExec.systemDatabase.getAllEvents(eventsMsg.workflow_id);
675
- eventOutputs = Object.entries(rawEvents).map(([key, value]) => ({
676
- key,
677
- value: protocol.inspectForConductor(value),
678
- }));
679
- }
680
- catch (e) {
681
- errorMsg = this.reportException(`Exception encountered when getting events for workflow ${eventsMsg.workflow_id}`, e);
682
- }
683
- const eventsResp = new protocol.GetWorkflowEventsResponse(baseMsg.request_id, eventOutputs, errorMsg);
684
- currWebsocket.send(JSON.stringify(eventsResp));
685
- break;
686
- case protocol.MessageType.GET_WORKFLOW_NOTIFICATIONS:
687
- const notifsMsg = baseMsg;
688
- let notifOutputs = undefined;
689
- try {
690
- const rawNotifs = await this.dbosExec.systemDatabase.getAllNotifications(notifsMsg.workflow_id);
691
- notifOutputs = rawNotifs.map((n) => ({
692
- topic: n.topic,
693
- message: protocol.inspectForConductor(n.message),
694
- created_at_epoch_ms: n.createdAtEpochMs,
695
- consumed: n.consumed,
696
- }));
697
- }
698
- catch (e) {
699
- errorMsg = this.reportException(`Exception encountered when getting notifications for workflow ${notifsMsg.workflow_id}`, e);
700
- }
701
- const notifsResp = new protocol.GetWorkflowNotificationsResponse(baseMsg.request_id, notifOutputs, errorMsg);
702
- currWebsocket.send(JSON.stringify(notifsResp));
703
- break;
704
- case protocol.MessageType.GET_WORKFLOW_STREAMS:
705
- const streamsMsg = baseMsg;
706
- let streamOutputs = undefined;
707
- try {
708
- const rawStreams = await this.dbosExec.systemDatabase.getAllStreamEntries(streamsMsg.workflow_id);
709
- streamOutputs = Object.entries(rawStreams).map(([key, values]) => ({
710
- key,
711
- values: values.map((v) => protocol.inspectForConductor(v)),
712
- }));
713
- }
714
- catch (e) {
715
- errorMsg = this.reportException(`Exception encountered when getting streams for workflow ${streamsMsg.workflow_id}`, e);
716
- }
717
- const streamsResp = new protocol.GetWorkflowStreamsResponse(baseMsg.request_id, streamOutputs, errorMsg);
718
- currWebsocket.send(JSON.stringify(streamsResp));
719
- break;
720
- case protocol.MessageType.GET_WORKFLOW_AGGREGATES:
721
- const aggMsg = baseMsg;
722
- const aggBody = aggMsg.body;
723
- let aggOutput = [];
724
- // Backwards compat: older clients send no select_* flags and expect counts.
725
- let selectCount = aggBody.select_count ?? false;
726
- const selectMinCreatedAt = aggBody.select_min_created_at ?? false;
727
- const selectMaxQueueWaitMs = aggBody.select_max_queue_wait_ms ?? false;
728
- const selectMaxTotalLatencyMs = aggBody.select_max_total_latency_ms ?? false;
729
- if (!(selectCount || selectMinCreatedAt || selectMaxQueueWaitMs || selectMaxTotalLatencyMs)) {
730
- selectCount = true;
731
- }
732
- try {
733
- const rows = await this.dbosExec.systemDatabase.getWorkflowAggregates({
734
- groupByStatus: aggBody.group_by_status ?? false,
735
- groupByName: aggBody.group_by_name ?? false,
736
- groupByQueueName: aggBody.group_by_queue_name ?? false,
737
- groupByExecutorId: aggBody.group_by_executor_id ?? false,
738
- groupByApplicationVersion: aggBody.group_by_application_version ?? false,
739
- groupByApplicationName: aggBody.group_by_application_name ?? false,
740
- selectCount,
741
- selectMinCreatedAt,
742
- selectMaxQueueWaitMs,
743
- selectMaxTotalLatencyMs,
744
- timeBucketSizeMs: aggBody.time_bucket_size_ms ?? undefined,
745
- status: aggBody.status,
746
- startTime: aggBody.start_time,
747
- endTime: aggBody.end_time,
748
- completedAfter: aggBody.completed_after,
749
- completedBefore: aggBody.completed_before,
750
- dequeuedAfter: aggBody.dequeued_after,
751
- dequeuedBefore: aggBody.dequeued_before,
752
- name: aggBody.name,
753
- appVersion: aggBody.app_version,
754
- executorId: aggBody.executor_id,
755
- queueName: aggBody.queue_name,
756
- workflowIdPrefix: aggBody.workflow_id_prefix,
757
- workflowIDs: aggBody.workflow_ids,
758
- authenticatedUser: aggBody.user,
759
- forkedFrom: aggBody.forked_from,
760
- parentWorkflowID: aggBody.parent_workflow_id,
761
- scheduleName: aggBody.schedule_name,
762
- applicationName: aggBody.application_name,
763
- wasForkedFrom: aggBody.was_forked_from,
764
- isFork: aggBody.is_fork,
765
- hasParent: aggBody.has_parent,
766
- attributes: aggBody.attributes,
767
- });
768
- aggOutput = rows.map((r) => ({
769
- group: r.group,
770
- count: r.count,
771
- min_created_at: r.minCreatedAt,
772
- max_queue_wait_ms: r.maxQueueWaitMs,
773
- max_total_latency_ms: r.maxTotalLatencyMs,
774
- }));
775
- }
776
- catch (e) {
777
- errorMsg = this.reportException('Exception encountered when getting workflow aggregates', e);
778
- }
779
- const aggResp = new protocol.GetWorkflowAggregatesResponse(baseMsg.request_id, aggOutput, errorMsg);
780
- currWebsocket.send(JSON.stringify(aggResp));
781
- break;
782
- case protocol.MessageType.GET_STEP_AGGREGATES:
783
- const stepAggMsg = baseMsg;
784
- const stepAggBody = stepAggMsg.body;
785
- let stepAggOutput = [];
786
- // Backwards compat: older clients send no select_* flags and expect counts.
787
- let stepSelectCount = stepAggBody.select_count ?? false;
788
- const stepSelectMaxDurationMs = stepAggBody.select_max_duration_ms ?? false;
789
- if (!(stepSelectCount || stepSelectMaxDurationMs)) {
790
- stepSelectCount = true;
791
- }
792
- try {
793
- const rows = await this.dbosExec.systemDatabase.getStepAggregates({
794
- groupByFunctionName: stepAggBody.group_by_function_name ?? false,
795
- groupByStatus: stepAggBody.group_by_status ?? false,
796
- selectCount: stepSelectCount,
797
- selectMaxDurationMs: stepSelectMaxDurationMs,
798
- timeBucketSizeMs: stepAggBody.time_bucket_size_ms ?? undefined,
799
- status: stepAggBody.status,
800
- functionName: stepAggBody.function_name,
801
- workflowIdPrefix: stepAggBody.workflow_id_prefix,
802
- completedAfter: stepAggBody.completed_after,
803
- completedBefore: stepAggBody.completed_before,
804
- applicationName: stepAggBody.application_name,
805
- });
806
- stepAggOutput = rows.map((r) => ({
807
- group: r.group,
808
- count: r.count,
809
- max_duration_ms: r.maxDurationMs,
810
- }));
811
- }
812
- catch (e) {
813
- errorMsg = this.reportException('Exception encountered when getting step aggregates', e);
814
- }
815
- const stepAggResp = new protocol.GetStepAggregatesResponse(baseMsg.request_id, stepAggOutput, errorMsg);
816
- currWebsocket.send(JSON.stringify(stepAggResp));
817
- break;
818
- case protocol.MessageType.LIST_QUEUES:
819
- const listQueuesMsg = baseMsg;
820
- let queueOutputs = [];
821
- try {
822
- const queues = await this.dbosExec.systemDatabase.listQueues(listQueuesMsg.body?.application_name);
823
- queueOutputs = queues.map((q) => ({
824
- name: q.name,
825
- concurrency: q.concurrency,
826
- worker_concurrency: q.workerConcurrency,
827
- rate_limit_max: q.rateLimitMax,
828
- rate_limit_period_sec: q.rateLimitPeriodSec,
829
- priority_enabled: true,
830
- partition_queue: q.partitionQueue,
831
- polling_interval_sec: q.pollingIntervalSec,
832
- application_name: q.applicationName ?? null,
833
- partition_concurrency: q.partitionConcurrency,
834
- partition_worker_concurrency: q.partitionWorkerConcurrency,
835
- partition_rate_limit_max: q.partitionRateLimitMax,
836
- partition_rate_limit_period_sec: q.partitionRateLimitPeriodSec,
837
- }));
838
- }
839
- catch (e) {
840
- errorMsg = this.reportException('Exception encountered when listing queues', e);
841
- }
842
- const listQueuesResp = new protocol.ListQueuesResponse(baseMsg.request_id, queueOutputs, errorMsg);
843
- currWebsocket.send(JSON.stringify(listQueuesResp));
844
- break;
845
- case protocol.MessageType.GET_QUEUE:
846
- const getQueueMsg = baseMsg;
847
- let getQueueOutput = null;
848
- try {
849
- const queue = await this.dbosExec.systemDatabase.getQueue(getQueueMsg.name);
850
- if (queue !== null) {
851
- getQueueOutput = {
852
- name: queue.name,
853
- concurrency: queue.concurrency,
854
- worker_concurrency: queue.workerConcurrency,
855
- rate_limit_max: queue.rateLimitMax,
856
- rate_limit_period_sec: queue.rateLimitPeriodSec,
857
- priority_enabled: true,
858
- partition_queue: queue.partitionQueue,
859
- polling_interval_sec: queue.pollingIntervalSec,
860
- application_name: queue.applicationName ?? null,
861
- partition_concurrency: queue.partitionConcurrency,
862
- partition_worker_concurrency: queue.partitionWorkerConcurrency,
863
- partition_rate_limit_max: queue.partitionRateLimitMax,
864
- partition_rate_limit_period_sec: queue.partitionRateLimitPeriodSec,
865
- };
866
- }
867
- }
868
- catch (e) {
869
- errorMsg = this.reportException(`Exception encountered when getting queue ${getQueueMsg.name}`, e);
870
- }
871
- const getQueueResp = new protocol.GetQueueResponse(baseMsg.request_id, getQueueOutput, errorMsg);
872
- currWebsocket.send(JSON.stringify(getQueueResp));
873
- break;
874
- default:
875
- this.dbosExec.logger.warn(`Unknown message type: ${baseMsg.type}`);
876
- // Still need to send a response to the conductor
877
- const unknownResp = new protocol.BaseResponse(baseMsg.type, baseMsg.request_id, 'Unknown message type');
878
- currWebsocket.send(JSON.stringify(unknownResp));
879
- }
880
- });
881
- currWebsocket.on('close', () => {
882
- if (this.isShuttingDown) {
883
- this.dbosExec.logger.info('Shutdown Conductor connection');
884
- return;
885
- }
886
- else if (this.reconnectTimeout === undefined) {
887
- this.dbosExec.logger.warn('Connection to conductor lost. Reconnecting.');
888
- this.resetWebsocket(currWebsocket, currPing);
889
- }
890
- });
891
- currWebsocket.on('unexpected-response', (_, res) => {
892
- const chunks = [];
893
- res.on('data', (chunk) => chunks.push(chunk));
894
- res.on('end', () => {
895
- const body = Buffer.concat(chunks).toString('utf-8');
896
- this.dbosExec.logger.warn(`Unexpected response from conductor: ${res.statusCode} ${res.statusMessage}. Details: ${body}`);
897
- if (this.reconnectTimeout === undefined) {
898
- this.resetWebsocket(currWebsocket, currPing);
899
- }
900
- });
901
- });
902
- currWebsocket.on('error', (err) => {
903
- this.dbosExec.logger.warn(`Unexpected exception in connection to conductor. Reconnecting: ${err.message}`);
904
- if (this.reconnectTimeout === undefined) {
905
- this.resetWebsocket(currWebsocket, currPing);
906
- }
907
- });
908
- }
909
- catch (e) {
910
- this.dbosExec.logger.warn(`Error in conductor loop. Reconnecting: ${e.message}`);
911
- if (this.reconnectTimeout === undefined) {
912
- this.resetWebsocket(this.websocket, this.pingIntervalTimeout);
913
- }
914
- }
915
- }
916
- /**
917
- * Give a round in flight a bounded chance to finish before shutdown takes the system
918
- * database away from it.
919
- */
920
- async awaitRetention(timeoutMs = 10000) {
921
- const round = this.retentionRound;
922
- if (round === undefined) {
923
- return;
924
- }
925
- let timer = undefined;
926
- try {
927
- await Promise.race([round, new Promise((resolve) => (timer = setTimeout(resolve, timeoutMs)))]);
928
- }
929
- finally {
930
- clearTimeout(timer);
931
- }
932
- }
933
- stop() {
934
- this.isShuttingDown = true;
935
- clearInterval(this.pingIntervalTimeout?.interval);
936
- clearTimeout(this.pingIntervalTimeout?.timeout);
937
- clearTimeout(this.reconnectTimeout);
938
- if (this.websocket) {
939
- this.websocket.close();
940
- }
941
- }
942
- }
943
- exports.Conductor = Conductor;
944
- //# sourceMappingURL=conductor.js.map