@dbos-inc/dbos-sdk 2.2.9-preview.g48d872ebd0 → 2.3.7-preview
Sign up to get free protection for your applications and to get access to all the features.
- package/.husky/pre-commit +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +9 -0
- package/CODE_OF_CONDUCT.md +24 -18
- package/CONTRIBUTING.md +12 -10
- package/README.md +2 -2
- package/compose.yaml +17 -17
- package/dbos-config.schema.json +4 -13
- package/dist/schemas/user_db_schema.d.ts.map +1 -1
- package/dist/schemas/user_db_schema.js.map +1 -1
- package/dist/src/context.d.ts +12 -12
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +9 -9
- package/dist/src/context.js.map +1 -1
- package/dist/src/data_validation.d.ts +1 -1
- package/dist/src/data_validation.d.ts.map +1 -1
- package/dist/src/data_validation.js +14 -8
- package/dist/src/data_validation.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +4 -2
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +131 -114
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos-runtime/cli.d.ts +3 -3
- package/dist/src/dbos-runtime/cli.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cli.js +79 -38
- package/dist/src/dbos-runtime/cli.js.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/authentication.d.ts +1 -1
- package/dist/src/dbos-runtime/cloudutils/authentication.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/authentication.js +14 -14
- package/dist/src/dbos-runtime/cloudutils/authentication.js.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/cloudutils.d.ts +2 -2
- package/dist/src/dbos-runtime/cloudutils/cloudutils.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/cloudutils.js +32 -32
- package/dist/src/dbos-runtime/cloudutils/cloudutils.js.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/databases.d.ts +2 -2
- package/dist/src/dbos-runtime/cloudutils/databases.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cloudutils/databases.js +25 -21
- package/dist/src/dbos-runtime/cloudutils/databases.js.map +1 -1
- package/dist/src/dbos-runtime/commands.d.ts +1 -1
- package/dist/src/dbos-runtime/commands.js +9 -9
- package/dist/src/dbos-runtime/config.d.ts +7 -7
- package/dist/src/dbos-runtime/config.d.ts.map +1 -1
- package/dist/src/dbos-runtime/config.js +30 -23
- package/dist/src/dbos-runtime/config.js.map +1 -1
- package/dist/src/dbos-runtime/configure.d.ts.map +1 -1
- package/dist/src/dbos-runtime/configure.js.map +1 -1
- package/dist/src/dbos-runtime/db_connection.d.ts.map +1 -1
- package/dist/src/dbos-runtime/db_connection.js +2 -2
- package/dist/src/dbos-runtime/db_connection.js.map +1 -1
- package/dist/src/dbos-runtime/db_wizard.d.ts +1 -1
- package/dist/src/dbos-runtime/db_wizard.d.ts.map +1 -1
- package/dist/src/dbos-runtime/db_wizard.js +18 -18
- package/dist/src/dbos-runtime/db_wizard.js.map +1 -1
- package/dist/src/dbos-runtime/debug.d.ts +2 -2
- package/dist/src/dbos-runtime/debug.d.ts.map +1 -1
- package/dist/src/dbos-runtime/debug.js +4 -4
- package/dist/src/dbos-runtime/debug.js.map +1 -1
- package/dist/src/dbos-runtime/migrate.d.ts +2 -2
- package/dist/src/dbos-runtime/migrate.d.ts.map +1 -1
- package/dist/src/dbos-runtime/migrate.js +14 -10
- package/dist/src/dbos-runtime/migrate.js.map +1 -1
- package/dist/src/dbos-runtime/reset.d.ts +2 -2
- package/dist/src/dbos-runtime/reset.js +2 -2
- package/dist/src/dbos-runtime/reset.js.map +1 -1
- package/dist/src/dbos-runtime/runtime.d.ts.map +1 -1
- package/dist/src/dbos-runtime/runtime.js +4 -4
- package/dist/src/dbos-runtime/runtime.js.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.d.ts +5 -4
- package/dist/src/dbos-runtime/workflow_management.d.ts.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.js +34 -14
- package/dist/src/dbos-runtime/workflow_management.js.map +1 -1
- package/dist/src/dbos.d.ts +23 -23
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +59 -59
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/debugpoint.d.ts.map +1 -1
- package/dist/src/debugpoint.js +4 -4
- package/dist/src/debugpoint.js.map +1 -1
- package/dist/src/decorators.d.ts +8 -8
- package/dist/src/decorators.d.ts.map +1 -1
- package/dist/src/decorators.js +36 -33
- package/dist/src/decorators.js.map +1 -1
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js +6 -5
- package/dist/src/error.js.map +1 -1
- package/dist/src/eventreceiver.d.ts +1 -1
- package/dist/src/eventreceiver.d.ts.map +1 -1
- package/dist/src/httpServer/handler.d.ts +8 -8
- package/dist/src/httpServer/handler.d.ts.map +1 -1
- package/dist/src/httpServer/handler.js.map +1 -1
- package/dist/src/httpServer/handlerTypes.d.ts.map +1 -1
- package/dist/src/httpServer/handlerTypes.js.map +1 -1
- package/dist/src/httpServer/middleware.d.ts +9 -9
- package/dist/src/httpServer/middleware.d.ts.map +1 -1
- package/dist/src/httpServer/middleware.js +6 -6
- package/dist/src/httpServer/middleware.js.map +1 -1
- package/dist/src/httpServer/server.d.ts +2 -2
- package/dist/src/httpServer/server.d.ts.map +1 -1
- package/dist/src/httpServer/server.js +27 -33
- package/dist/src/httpServer/server.js.map +1 -1
- package/dist/src/index.d.ts +16 -16
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kafka/kafka.d.ts.map +1 -1
- package/dist/src/kafka/kafka.js +2 -2
- package/dist/src/kafka/kafka.js.map +1 -1
- package/dist/src/procedure.d.ts +6 -6
- package/dist/src/procedure.d.ts.map +1 -1
- package/dist/src/procedure.js.map +1 -1
- package/dist/src/scheduler/crontab.d.ts.map +1 -1
- package/dist/src/scheduler/crontab.js +54 -33
- package/dist/src/scheduler/crontab.js.map +1 -1
- package/dist/src/scheduler/scheduler.d.ts +3 -3
- package/dist/src/scheduler/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler/scheduler.js +7 -7
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/step.d.ts +4 -4
- package/dist/src/step.d.ts.map +1 -1
- package/dist/src/step.js.map +1 -1
- package/dist/src/system_database.d.ts +16 -11
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +136 -56
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/telemetry/collector.d.ts +3 -3
- package/dist/src/telemetry/exporters.d.ts +1 -1
- package/dist/src/telemetry/exporters.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +5 -5
- package/dist/src/telemetry/logs.d.ts +4 -4
- package/dist/src/telemetry/logs.d.ts.map +1 -1
- package/dist/src/telemetry/logs.js +18 -18
- package/dist/src/telemetry/logs.js.map +1 -1
- package/dist/src/telemetry/traces.d.ts +3 -3
- package/dist/src/telemetry/traces.js +7 -7
- package/dist/src/testing/testing_runtime.d.ts +11 -11
- package/dist/src/testing/testing_runtime.d.ts.map +1 -1
- package/dist/src/testing/testing_runtime.js +15 -8
- package/dist/src/testing/testing_runtime.js.map +1 -1
- package/dist/src/transaction.d.ts +6 -6
- package/dist/src/transaction.d.ts.map +1 -1
- package/dist/src/transaction.js +4 -4
- package/dist/src/transaction.js.map +1 -1
- package/dist/src/user_database.d.ts +4 -4
- package/dist/src/user_database.d.ts.map +1 -1
- package/dist/src/user_database.js +45 -45
- package/dist/src/user_database.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +6 -12
- package/dist/src/utils.js.map +1 -1
- package/dist/src/wfqueue.d.ts +1 -1
- package/dist/src/wfqueue.d.ts.map +1 -1
- package/dist/src/wfqueue.js +5 -7
- package/dist/src/wfqueue.js.map +1 -1
- package/dist/src/workflow.d.ts +17 -10
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +19 -18
- package/dist/src/workflow.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/eslint.config.js +29 -27
- package/migrations/20240123182943_schema.js +7 -8
- package/migrations/20240123183021_tables.js +52 -48
- package/migrations/20240123183025_indexes.js +11 -14
- package/migrations/20240123183030_triggers.js +7 -8
- package/migrations/20240124015239_status_timestamp.js +12 -18
- package/migrations/20240201213211_replica_identity.js +8 -11
- package/migrations/20240205223925_foreign_keys.js +40 -18
- package/migrations/20240207192338_executor_id_index.js +8 -10
- package/migrations/20240430090000_tables.js +8 -10
- package/migrations/20240516004341_application_version.js +10 -12
- package/migrations/20240517000000_status_class_config.js +10 -14
- package/migrations/20240621000000_workflow_tries.js +8 -12
- package/migrations/20240924000000_workflowqueue.js +32 -23
- package/migrations/20241009150000_event_dispatch_kv.js +12 -14
- package/migrations/20252101000000_workflow_queues_executor_id.js +7 -9
- package/package.json +9 -2
- package/src/dbos-runtime/cloudutils/README.md +1 -1
@@ -29,17 +29,17 @@ const wfqueue_1 = require("./wfqueue");
|
|
29
29
|
const debugpoint_1 = require("./debugpoint");
|
30
30
|
exports.dbosNull = {};
|
31
31
|
exports.OperationType = {
|
32
|
-
HANDLER:
|
33
|
-
WORKFLOW:
|
34
|
-
TRANSACTION:
|
35
|
-
COMMUNICATOR:
|
36
|
-
PROCEDURE:
|
32
|
+
HANDLER: 'handler',
|
33
|
+
WORKFLOW: 'workflow',
|
34
|
+
TRANSACTION: 'transaction',
|
35
|
+
COMMUNICATOR: 'communicator',
|
36
|
+
PROCEDURE: 'procedure',
|
37
37
|
};
|
38
38
|
const TempWorkflowType = {
|
39
|
-
transaction:
|
40
|
-
procedure:
|
41
|
-
external:
|
42
|
-
send:
|
39
|
+
transaction: 'transaction',
|
40
|
+
procedure: 'procedure',
|
41
|
+
external: 'external',
|
42
|
+
send: 'send',
|
43
43
|
};
|
44
44
|
class DBOSExecutor {
|
45
45
|
config;
|
@@ -50,14 +50,14 @@ class DBOSExecutor {
|
|
50
50
|
systemDatabase;
|
51
51
|
procedurePool;
|
52
52
|
// Temporary workflows are created by calling transaction/send/recv directly from the executor class
|
53
|
-
static tempWorkflowName =
|
53
|
+
static tempWorkflowName = 'temp_workflow';
|
54
54
|
workflowInfoMap = new Map([
|
55
55
|
// We initialize the map with an entry for temporary workflows.
|
56
56
|
[
|
57
57
|
DBOSExecutor.tempWorkflowName,
|
58
58
|
{
|
59
59
|
workflow: async () => {
|
60
|
-
this.logger.error(
|
60
|
+
this.logger.error('UNREACHABLE: Indirect invoke of temp workflow');
|
61
61
|
return Promise.resolve();
|
62
62
|
},
|
63
63
|
config: {},
|
@@ -77,7 +77,7 @@ class DBOSExecutor {
|
|
77
77
|
static defaultNotificationTimeoutSec = 60;
|
78
78
|
debugMode;
|
79
79
|
debugProxy;
|
80
|
-
static systemDBSchemaName =
|
80
|
+
static systemDBSchemaName = 'dbos';
|
81
81
|
logger;
|
82
82
|
tracer;
|
83
83
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
@@ -86,7 +86,7 @@ class DBOSExecutor {
|
|
86
86
|
eventReceivers = [];
|
87
87
|
scheduler = undefined;
|
88
88
|
wfqEnded = undefined;
|
89
|
-
executorID = process.env.DBOS__VMID ||
|
89
|
+
executorID = process.env.DBOS__VMID || 'local';
|
90
90
|
static globalInstance = undefined;
|
91
91
|
/* WORKFLOW EXECUTOR LIFE CYCLE MANAGEMENT */
|
92
92
|
constructor(config, systemDatabase) {
|
@@ -96,7 +96,7 @@ class DBOSExecutor {
|
|
96
96
|
// Set configured environment variables
|
97
97
|
if (config.env) {
|
98
98
|
for (const [key, value] of Object.entries(config.env)) {
|
99
|
-
if (typeof value ===
|
99
|
+
if (typeof value === 'string') {
|
100
100
|
process.env[key] = value;
|
101
101
|
}
|
102
102
|
else {
|
@@ -115,7 +115,7 @@ class DBOSExecutor {
|
|
115
115
|
this.logger = new logs_1.GlobalLogger(this.telemetryCollector, this.config.telemetry?.logs);
|
116
116
|
this.tracer = new traces_1.Tracer(this.telemetryCollector);
|
117
117
|
if (this.debugMode) {
|
118
|
-
this.logger.info(
|
118
|
+
this.logger.info('Running in debug mode!');
|
119
119
|
if (this.debugProxy) {
|
120
120
|
try {
|
121
121
|
const url = new URL(this.config.debugProxy);
|
@@ -131,11 +131,11 @@ class DBOSExecutor {
|
|
131
131
|
}
|
132
132
|
this.procedurePool = new pg_1.Pool(this.config.poolConfig);
|
133
133
|
if (systemDatabase) {
|
134
|
-
this.logger.debug(
|
134
|
+
this.logger.debug('Using provided system database'); // XXX print the name or something
|
135
135
|
this.systemDatabase = systemDatabase;
|
136
136
|
}
|
137
137
|
else {
|
138
|
-
this.logger.debug(
|
138
|
+
this.logger.debug('Using Postgres system database');
|
139
139
|
this.systemDatabase = new system_database_1.PostgresSystemDatabase(this.config.poolConfig, this.config.system_database, this.logger);
|
140
140
|
}
|
141
141
|
this.flushBufferID = setInterval(() => {
|
@@ -144,7 +144,7 @@ class DBOSExecutor {
|
|
144
144
|
void this.flushWorkflowBuffers();
|
145
145
|
}
|
146
146
|
}, this.flushBufferIntervalMs);
|
147
|
-
this.logger.debug(
|
147
|
+
this.logger.debug('Started workflow status buffer worker');
|
148
148
|
this.initialized = false;
|
149
149
|
DBOSExecutor.globalInstance = this;
|
150
150
|
}
|
@@ -154,25 +154,26 @@ class DBOSExecutor {
|
|
154
154
|
if (userDbClient === user_database_1.UserDatabaseName.PRISMA) {
|
155
155
|
// TODO: make Prisma work with debugger proxy.
|
156
156
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
157
|
-
const { PrismaClient } = require(node_path_1.default.join(process.cwd(),
|
157
|
+
const { PrismaClient } = require(node_path_1.default.join(process.cwd(), 'node_modules', '@prisma', 'client')); // Find the prisma client in the node_modules of the current project
|
158
|
+
this.userDatabase = new user_database_1.PrismaUserDatabase(
|
158
159
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
159
|
-
|
160
|
+
new PrismaClient({
|
160
161
|
datasources: {
|
161
162
|
db: {
|
162
163
|
url: `postgresql://${userDBConfig.user}:${userDBConfig.password}@${userDBConfig.host}:${userDBConfig.port}/${userDBConfig.database}`,
|
163
164
|
},
|
164
|
-
}
|
165
|
+
},
|
165
166
|
}));
|
166
|
-
this.logger.debug(
|
167
|
+
this.logger.debug('Loaded Prisma user database');
|
167
168
|
}
|
168
169
|
else if (userDbClient === user_database_1.UserDatabaseName.TYPEORM) {
|
169
170
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
170
|
-
const DataSourceExports = require(
|
171
|
+
const DataSourceExports = require('typeorm');
|
171
172
|
try {
|
172
173
|
this.userDatabase = new user_database_1.TypeORMDatabase(
|
173
174
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
174
175
|
new DataSourceExports.DataSource({
|
175
|
-
type:
|
176
|
+
type: 'postgres', // perhaps should move to config file
|
176
177
|
host: userDBConfig.host,
|
177
178
|
port: userDBConfig.port,
|
178
179
|
username: userDBConfig.user,
|
@@ -186,11 +187,11 @@ class DBOSExecutor {
|
|
186
187
|
s.message = `Error loading TypeORM user database: ${s.message}`;
|
187
188
|
this.logger.error(s);
|
188
189
|
}
|
189
|
-
this.logger.debug(
|
190
|
+
this.logger.debug('Loaded TypeORM user database');
|
190
191
|
}
|
191
192
|
else if (userDbClient === user_database_1.UserDatabaseName.KNEX) {
|
192
193
|
const knexConfig = {
|
193
|
-
client:
|
194
|
+
client: 'postgres',
|
194
195
|
connection: {
|
195
196
|
host: userDBConfig.host,
|
196
197
|
port: userDBConfig.port,
|
@@ -201,21 +202,21 @@ class DBOSExecutor {
|
|
201
202
|
},
|
202
203
|
};
|
203
204
|
this.userDatabase = new user_database_1.KnexUserDatabase((0, knex_1.default)(knexConfig));
|
204
|
-
this.logger.debug(
|
205
|
+
this.logger.debug('Loaded Knex user database');
|
205
206
|
}
|
206
207
|
else if (userDbClient === user_database_1.UserDatabaseName.DRIZZLE) {
|
207
208
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
208
|
-
const DrizzleExports = require(
|
209
|
+
const DrizzleExports = require('drizzle-orm/node-postgres');
|
209
210
|
const drizzlePool = new pg_1.Pool(userDBConfig);
|
210
211
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
211
212
|
const drizzle = DrizzleExports.drizzle(drizzlePool, { schema: this.drizzleEntities });
|
212
213
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
213
214
|
this.userDatabase = new user_database_1.DrizzleUserDatabase(drizzlePool, drizzle);
|
214
|
-
this.logger.debug(
|
215
|
+
this.logger.debug('Loaded Drizzle user database');
|
215
216
|
}
|
216
217
|
else {
|
217
218
|
this.userDatabase = new user_database_1.PGNodeUserDatabase(userDBConfig);
|
218
|
-
this.logger.debug(
|
219
|
+
this.logger.debug('Loaded Postgres user database');
|
219
220
|
}
|
220
221
|
}
|
221
222
|
#registerClass(cls) {
|
@@ -253,7 +254,7 @@ class DBOSExecutor {
|
|
253
254
|
}
|
254
255
|
async init(classes) {
|
255
256
|
if (this.initialized) {
|
256
|
-
this.logger.error(
|
257
|
+
this.logger.error('Workflow executor already initialized!');
|
257
258
|
return;
|
258
259
|
}
|
259
260
|
if (!classes || !classes.length) {
|
@@ -267,7 +268,7 @@ class DBOSExecutor {
|
|
267
268
|
* With TSORM, we take an array of entities (Function[]) and add them to this.entities:
|
268
269
|
*/
|
269
270
|
if (Array.isArray(reg.ormEntities)) {
|
270
|
-
this.typeormEntities =
|
271
|
+
this.typeormEntities = this.typeormEntities.concat(reg.ormEntities);
|
271
272
|
length = reg.ormEntities.length;
|
272
273
|
}
|
273
274
|
else {
|
@@ -282,8 +283,8 @@ class DBOSExecutor {
|
|
282
283
|
await (0, user_database_1.createDBIfDoesNotExist)(this.config.poolConfig, this.logger);
|
283
284
|
this.configureDbClient();
|
284
285
|
if (!this.userDatabase) {
|
285
|
-
this.logger.error(
|
286
|
-
throw new error_1.DBOSInitializationError(
|
286
|
+
this.logger.error('No user database configured!');
|
287
|
+
throw new error_1.DBOSInitializationError('No user database configured!');
|
287
288
|
}
|
288
289
|
for (const cls of classes) {
|
289
290
|
this.#registerClass(cls);
|
@@ -324,30 +325,30 @@ class DBOSExecutor {
|
|
324
325
|
for (const v of this.registeredOperations) {
|
325
326
|
const m = v;
|
326
327
|
if (m.init === true) {
|
327
|
-
this.logger.debug(
|
328
|
+
this.logger.debug('Executing init method: ' + m.name);
|
328
329
|
await m.origFunction(new context_1.InitContext(this));
|
329
330
|
}
|
330
331
|
}
|
331
332
|
await this.recoverPendingWorkflows();
|
332
333
|
}
|
333
|
-
this.logger.info(
|
334
|
+
this.logger.info('Workflow executor initialized');
|
334
335
|
}
|
335
336
|
#logNotice(msg) {
|
336
337
|
switch (msg.severity) {
|
337
|
-
case
|
338
|
-
case
|
339
|
-
case
|
338
|
+
case 'INFO':
|
339
|
+
case 'LOG':
|
340
|
+
case 'NOTICE':
|
340
341
|
this.logger.info(msg.message);
|
341
342
|
break;
|
342
|
-
case
|
343
|
+
case 'WARNING':
|
343
344
|
this.logger.warn(msg.message);
|
344
345
|
break;
|
345
|
-
case
|
346
|
+
case 'DEBUG':
|
346
347
|
this.logger.debug(msg.message);
|
347
348
|
break;
|
348
|
-
case
|
349
|
-
case
|
350
|
-
case
|
349
|
+
case 'ERROR':
|
350
|
+
case 'FATAL':
|
351
|
+
case 'PANIC':
|
351
352
|
this.logger.error(msg.message);
|
352
353
|
break;
|
353
354
|
default:
|
@@ -356,7 +357,7 @@ class DBOSExecutor {
|
|
356
357
|
}
|
357
358
|
async destroy() {
|
358
359
|
if (this.pendingWorkflowMap.size > 0) {
|
359
|
-
this.logger.info(
|
360
|
+
this.logger.info('Waiting for pending workflows to finish.');
|
360
361
|
await Promise.allSettled(this.pendingWorkflowMap.values());
|
361
362
|
}
|
362
363
|
clearInterval(this.flushBufferID);
|
@@ -365,7 +366,7 @@ class DBOSExecutor {
|
|
365
366
|
await this.flushWorkflowBuffers();
|
366
367
|
}
|
367
368
|
while (this.isFlushingBuffers) {
|
368
|
-
this.logger.info(
|
369
|
+
this.logger.info('Waiting for result buffers to be exported.');
|
369
370
|
await (0, utils_1.sleepms)(1000);
|
370
371
|
}
|
371
372
|
await this.systemDatabase.destroy();
|
@@ -439,9 +440,7 @@ class DBOSExecutor {
|
|
439
440
|
this.logger.debug(`Registered stored proc ${cfn}`);
|
440
441
|
}
|
441
442
|
getWorkflowInfo(wf) {
|
442
|
-
const wfname =
|
443
|
-
? wf.name
|
444
|
-
: (0, decorators_1.getRegisteredMethodClassName)(wf) + '.' + wf.name;
|
443
|
+
const wfname = wf.name === DBOSExecutor.tempWorkflowName ? wf.name : (0, decorators_1.getRegisteredMethodClassName)(wf) + '.' + wf.name;
|
445
444
|
return this.workflowInfoMap.get(wfname);
|
446
445
|
}
|
447
446
|
getWorkflowInfoByStatus(wf) {
|
@@ -498,14 +497,14 @@ class DBOSExecutor {
|
|
498
497
|
const wCtxt = new workflow_1.WorkflowContextImpl(this, params.parentCtx, workflowUUID, wConfig, wf.name, presetUUID, params.tempWfType, params.tempWfName);
|
499
498
|
const internalStatus = {
|
500
499
|
workflowUUID: workflowUUID,
|
501
|
-
status:
|
500
|
+
status: params.queueName !== undefined ? workflow_1.StatusString.ENQUEUED : workflow_1.StatusString.PENDING,
|
502
501
|
name: wf.name,
|
503
|
-
className: wCtxt.isTempWorkflow ?
|
504
|
-
configName: params.configuredInstance?.name ||
|
502
|
+
className: wCtxt.isTempWorkflow ? '' : (0, decorators_1.getRegisteredMethodClassName)(wf),
|
503
|
+
configName: params.configuredInstance?.name || '',
|
505
504
|
queueName: params.queueName,
|
506
505
|
authenticatedUser: wCtxt.authenticatedUser,
|
507
506
|
output: undefined,
|
508
|
-
error:
|
507
|
+
error: '',
|
509
508
|
assumedRole: wCtxt.assumedRole,
|
510
509
|
authenticatedRoles: wCtxt.authenticatedRoles,
|
511
510
|
request: wCtxt.request,
|
@@ -514,18 +513,17 @@ class DBOSExecutor {
|
|
514
513
|
applicationID: wCtxt.applicationID,
|
515
514
|
createdAt: Date.now(), // Remember the start time of this workflow
|
516
515
|
maxRetries: wCtxt.maxRecoveryAttempts,
|
517
|
-
recovery: params.recovery === true,
|
518
516
|
};
|
519
517
|
if (wCtxt.isTempWorkflow) {
|
520
518
|
internalStatus.name = `${DBOSExecutor.tempWorkflowName}-${wCtxt.tempWfOperationType}-${wCtxt.tempWfOperationName}`;
|
521
|
-
internalStatus.className = params.tempWfClass ??
|
519
|
+
internalStatus.className = params.tempWfClass ?? '';
|
522
520
|
}
|
523
521
|
let status = undefined;
|
524
522
|
// Synchronously set the workflow's status to PENDING and record workflow inputs (for non single-transaction workflows).
|
525
523
|
// We have to do it for all types of workflows because operation_outputs table has a foreign key constraint on workflow status table.
|
526
|
-
if ((wCtxt.tempWfOperationType !== TempWorkflowType.transaction
|
527
|
-
|
528
|
-
|
524
|
+
if ((wCtxt.tempWfOperationType !== TempWorkflowType.transaction &&
|
525
|
+
wCtxt.tempWfOperationType !== TempWorkflowType.procedure) ||
|
526
|
+
params.queueName !== undefined) {
|
529
527
|
if (this.debugMode) {
|
530
528
|
const wfStatus = await this.systemDatabase.getWorkflowStatus(workflowUUID);
|
531
529
|
const wfInputs = await this.systemDatabase.getWorkflowInputs(workflowUUID);
|
@@ -590,7 +588,7 @@ class DBOSExecutor {
|
|
590
588
|
// Retrieve the handle and wait for the result.
|
591
589
|
const retrievedHandle = this.retrieveWorkflow(workflowUUID);
|
592
590
|
result = await retrievedHandle.getResult();
|
593
|
-
wCtxt.span.setAttribute(
|
591
|
+
wCtxt.span.setAttribute('cached', true);
|
594
592
|
wCtxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
595
593
|
}
|
596
594
|
else {
|
@@ -616,8 +614,8 @@ class DBOSExecutor {
|
|
616
614
|
}
|
617
615
|
finally {
|
618
616
|
this.tracer.endSpan(wCtxt.span);
|
619
|
-
if (wCtxt.tempWfOperationType === TempWorkflowType.transaction
|
620
|
-
|
617
|
+
if (wCtxt.tempWfOperationType === TempWorkflowType.transaction ||
|
618
|
+
wCtxt.tempWfOperationType === TempWorkflowType.procedure) {
|
621
619
|
// For single-transaction workflows, asynchronously record inputs.
|
622
620
|
// We must buffer inputs after workflow status is buffered/flushed because workflow_inputs table has a foreign key reference to the workflow_status table.
|
623
621
|
if (!this.debugMode) {
|
@@ -631,12 +629,13 @@ class DBOSExecutor {
|
|
631
629
|
}
|
632
630
|
return result;
|
633
631
|
};
|
634
|
-
if (this.debugMode ||
|
632
|
+
if (this.debugMode ||
|
633
|
+
(status !== 'SUCCESS' && status !== 'ERROR' && (params.queueName === undefined || params.executeWorkflow))) {
|
635
634
|
const workflowPromise = runWorkflow();
|
636
635
|
// Need to await for the workflow and capture errors.
|
637
636
|
const awaitWorkflowPromise = workflowPromise
|
638
637
|
.catch((error) => {
|
639
|
-
this.logger.debug(
|
638
|
+
this.logger.debug('Captured error in awaitWorkflowPromise: ' + error);
|
640
639
|
})
|
641
640
|
.finally(() => {
|
642
641
|
// Remove itself from pending workflow map.
|
@@ -663,7 +662,7 @@ class DBOSExecutor {
|
|
663
662
|
* Retrieve the transaction snapshot information of the current transaction
|
664
663
|
*/
|
665
664
|
static async #retrieveSnapshot(query) {
|
666
|
-
const rows = await query(
|
665
|
+
const rows = await query('SELECT pg_current_snapshot()::text as txn_snapshot;', []);
|
667
666
|
return rows[0].txn_snapshot;
|
668
667
|
}
|
669
668
|
/**
|
@@ -675,14 +674,14 @@ class DBOSExecutor {
|
|
675
674
|
*/
|
676
675
|
async #checkExecution(query, workflowUUID, funcID) {
|
677
676
|
// Note: we read the current snapshot, not the recorded one!
|
678
|
-
const rows = await query(
|
677
|
+
const rows = await query('(SELECT output, error, txn_snapshot, true as recorded FROM dbos.transaction_outputs WHERE workflow_uuid=$1 AND function_id=$2 UNION ALL SELECT null as output, null as error, pg_current_snapshot()::text as txn_snapshot, false as recorded) ORDER BY recorded', [workflowUUID, funcID]);
|
679
678
|
if (rows.length === 0 || rows.length > 2) {
|
680
|
-
this.logger.error(
|
681
|
-
throw new error_1.DBOSError(
|
679
|
+
this.logger.error('Unexpected! This should never happen. Returned rows: ' + rows.toString());
|
680
|
+
throw new error_1.DBOSError('This should never happen. Returned rows: ' + rows.toString());
|
682
681
|
}
|
683
682
|
const res = {
|
684
683
|
output: exports.dbosNull,
|
685
|
-
txn_snapshot:
|
684
|
+
txn_snapshot: '',
|
686
685
|
};
|
687
686
|
// recorded=false row will be first because we used ORDER BY.
|
688
687
|
res.txn_snapshot = rows[0].txn_snapshot;
|
@@ -701,11 +700,11 @@ class DBOSExecutor {
|
|
701
700
|
*/
|
702
701
|
async #recordOutput(query, workflowUUID, funcID, txnSnapshot, output, isKeyConflict) {
|
703
702
|
if (this.debugMode) {
|
704
|
-
throw new error_1.DBOSDebuggerError(
|
703
|
+
throw new error_1.DBOSDebuggerError('Cannot record output in debug mode.');
|
705
704
|
}
|
706
705
|
try {
|
707
706
|
const serialOutput = utils_1.DBOSJSON.stringify(output);
|
708
|
-
const rows = await query(
|
707
|
+
const rows = await query('INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, output, txn_id, txn_snapshot, created_at) VALUES ($1, $2, $3, (select pg_current_xact_id_if_assigned()::text), $4, $5) RETURNING txn_id;', [workflowUUID, funcID, serialOutput, txnSnapshot, Date.now()]);
|
709
708
|
return rows[0].txn_id;
|
710
709
|
}
|
711
710
|
catch (error) {
|
@@ -723,11 +722,11 @@ class DBOSExecutor {
|
|
723
722
|
*/
|
724
723
|
async #recordError(query, workflowUUID, funcID, txnSnapshot, err, isKeyConflict) {
|
725
724
|
if (this.debugMode) {
|
726
|
-
throw new error_1.DBOSDebuggerError(
|
725
|
+
throw new error_1.DBOSDebuggerError('Cannot record error in debug mode.');
|
727
726
|
}
|
728
727
|
try {
|
729
728
|
const serialErr = utils_1.DBOSJSON.stringify((0, serialize_error_1.serializeError)(err));
|
730
|
-
await query(
|
729
|
+
await query('INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, error, txn_id, txn_snapshot, created_at) VALUES ($1, $2, $3, null, $4, $5) RETURNING txn_id;', [workflowUUID, funcID, serialErr, txnSnapshot, Date.now()]);
|
731
730
|
}
|
732
731
|
catch (error) {
|
733
732
|
if (isKeyConflict(error)) {
|
@@ -749,11 +748,11 @@ class DBOSExecutor {
|
|
749
748
|
return;
|
750
749
|
}
|
751
750
|
if (this.debugMode) {
|
752
|
-
throw new error_1.DBOSDebuggerError(
|
751
|
+
throw new error_1.DBOSDebuggerError('Cannot flush result buffer in debug mode.');
|
753
752
|
}
|
754
753
|
funcIDs.sort();
|
755
754
|
try {
|
756
|
-
let sqlStmt =
|
755
|
+
let sqlStmt = 'INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, output, error, txn_id, txn_snapshot, created_at) VALUES ';
|
757
756
|
let paramCnt = 1;
|
758
757
|
const values = [];
|
759
758
|
for (const funcID of funcIDs) {
|
@@ -764,7 +763,7 @@ class DBOSExecutor {
|
|
764
763
|
const txnSnapshot = recorded.txn_snapshot;
|
765
764
|
const createdAt = recorded.created_at;
|
766
765
|
if (paramCnt > 1) {
|
767
|
-
sqlStmt +=
|
766
|
+
sqlStmt += ', ';
|
768
767
|
}
|
769
768
|
sqlStmt += `($${paramCnt++}, $${paramCnt++}, $${paramCnt++}, $${paramCnt++}, null, $${paramCnt++}, $${paramCnt++})`;
|
770
769
|
values.push(workflowUUID, funcID, utils_1.DBOSJSON.stringify(output), utils_1.DBOSJSON.stringify(null), txnSnapshot, createdAt);
|
@@ -787,7 +786,7 @@ class DBOSExecutor {
|
|
787
786
|
return this.#flushResultBuffer(func, resultBuffer, workflowUUID, (error) => this.userDatabase.isKeyConflictError(error));
|
788
787
|
}
|
789
788
|
#flushResultBufferProc(client, resultBuffer, workflowUUID) {
|
790
|
-
const func = (sql, args) => client.query(sql, args).then(v => v.rows);
|
789
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
791
790
|
return this.#flushResultBuffer(func, resultBuffer, workflowUUID, user_database_1.pgNodeIsKeyConflictError);
|
792
791
|
}
|
793
792
|
async transaction(txn, params, ...args) {
|
@@ -823,7 +822,7 @@ class DBOSExecutor {
|
|
823
822
|
isolationLevel: txnInfo.config.isolationLevel,
|
824
823
|
}, wfCtx.span);
|
825
824
|
while (true) {
|
826
|
-
let txn_snapshot =
|
825
|
+
let txn_snapshot = 'invalid';
|
827
826
|
const workflowUUID = wfCtx.workflowUUID;
|
828
827
|
const wrappedTransaction = async (client) => {
|
829
828
|
const tCtxt = new transaction_1.TransactionContextImpl(this.userDatabase.getName(), client, wfCtx, span, this.logger, funcId, txn.name);
|
@@ -834,7 +833,7 @@ class DBOSExecutor {
|
|
834
833
|
const check = await this.#checkExecution(func, workflowUUID, funcId);
|
835
834
|
txn_snapshot = check.txn_snapshot;
|
836
835
|
if (check.output !== exports.dbosNull) {
|
837
|
-
tCtxt.span.setAttribute(
|
836
|
+
tCtxt.span.setAttribute('cached', true);
|
838
837
|
tCtxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
839
838
|
this.tracer.endSpan(tCtxt.span);
|
840
839
|
return check.output;
|
@@ -881,7 +880,7 @@ class DBOSExecutor {
|
|
881
880
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
882
881
|
const func = (sql, args) => this.userDatabase.queryWithClient(client, sql, ...args);
|
883
882
|
const pg_txn_id = await this.#recordOutput(func, wfCtx.workflowUUID, funcId, txn_snapshot, result, (error) => this.userDatabase.isKeyConflictError(error));
|
884
|
-
tCtxt.span.setAttribute(
|
883
|
+
tCtxt.span.setAttribute('pg_txn_id', pg_txn_id);
|
885
884
|
wfCtx.resultBuffer.clear();
|
886
885
|
}
|
887
886
|
catch (error) {
|
@@ -908,7 +907,7 @@ class DBOSExecutor {
|
|
908
907
|
}
|
909
908
|
if (this.userDatabase.isRetriableTransactionError(err)) {
|
910
909
|
// serialization_failure in PostgreSQL
|
911
|
-
span.addEvent(
|
910
|
+
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
912
911
|
// Retry serialization failures.
|
913
912
|
await (0, utils_1.sleepms)(retryWaitMillis);
|
914
913
|
retryWaitMillis *= backoffFactor;
|
@@ -981,15 +980,15 @@ class DBOSExecutor {
|
|
981
980
|
const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
|
982
981
|
const readOnly = procInfo.config.readOnly ?? false;
|
983
982
|
while (true) {
|
984
|
-
let txn_snapshot =
|
983
|
+
let txn_snapshot = 'invalid';
|
985
984
|
const wrappedProcedure = async (client) => {
|
986
985
|
const ctxt = new procedure_1.StoredProcedureContextImpl(client, wfCtx, span, this.logger, funcId, proc.name);
|
987
986
|
if (wfCtx.presetUUID) {
|
988
|
-
const func = (sql, args) => this.procedurePool.query(sql, args).then(v => v.rows);
|
987
|
+
const func = (sql, args) => this.procedurePool.query(sql, args).then((v) => v.rows);
|
989
988
|
const check = await this.#checkExecution(func, wfCtx.workflowUUID, funcId);
|
990
989
|
txn_snapshot = check.txn_snapshot;
|
991
990
|
if (check.output !== exports.dbosNull) {
|
992
|
-
ctxt.span.setAttribute(
|
991
|
+
ctxt.span.setAttribute('cached', true);
|
993
992
|
ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
994
993
|
this.tracer.endSpan(ctxt.span);
|
995
994
|
return check.output;
|
@@ -997,7 +996,7 @@ class DBOSExecutor {
|
|
997
996
|
}
|
998
997
|
else {
|
999
998
|
// Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
|
1000
|
-
const func = (sql, args) => this.procedurePool.query(sql, args).then(v => v.rows);
|
999
|
+
const func = (sql, args) => this.procedurePool.query(sql, args).then((v) => v.rows);
|
1001
1000
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(func);
|
1002
1001
|
}
|
1003
1002
|
if (this.debugMode) {
|
@@ -1031,23 +1030,25 @@ class DBOSExecutor {
|
|
1031
1030
|
}
|
1032
1031
|
else {
|
1033
1032
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
1034
|
-
const func = (sql, args) => client.query(sql, args).then(v => v.rows);
|
1033
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1035
1034
|
const pg_txn_id = await this.#recordOutput(func, wfCtx.workflowUUID, funcId, txn_snapshot, result, user_database_1.pgNodeIsKeyConflictError);
|
1036
1035
|
// const pg_txn_id = await wfCtx.recordOutputProc<R>(client, funcId, txn_snapshot, result);
|
1037
|
-
ctxt.span.setAttribute(
|
1036
|
+
ctxt.span.setAttribute('pg_txn_id', pg_txn_id);
|
1038
1037
|
wfCtx.resultBuffer.clear();
|
1039
1038
|
}
|
1040
1039
|
return result;
|
1041
1040
|
};
|
1042
1041
|
try {
|
1043
|
-
const result = await this.invokeStoredProcFunction(wrappedProcedure, {
|
1042
|
+
const result = await this.invokeStoredProcFunction(wrappedProcedure, {
|
1043
|
+
isolationLevel: procInfo.config.isolationLevel,
|
1044
|
+
});
|
1044
1045
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1045
1046
|
return result;
|
1046
1047
|
}
|
1047
1048
|
catch (err) {
|
1048
1049
|
if (this.userDatabase.isRetriableTransactionError(err)) {
|
1049
1050
|
// serialization_failure in PostgreSQL
|
1050
|
-
span.addEvent(
|
1051
|
+
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
1051
1052
|
// Retry serialization failures.
|
1052
1053
|
await (0, utils_1.sleepms)(retryWaitMillis);
|
1053
1054
|
retryWaitMillis *= backoffFactor;
|
@@ -1058,7 +1059,7 @@ class DBOSExecutor {
|
|
1058
1059
|
const e = err;
|
1059
1060
|
await this.invokeStoredProcFunction(async (client) => {
|
1060
1061
|
await this.#flushResultBufferProc(client, wfCtx.resultBuffer, wfCtx.workflowUUID);
|
1061
|
-
const func = (sql, args) => client.query(sql, args).then(v => v.rows);
|
1062
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1062
1063
|
await this.#recordError(func, wfCtx.workflowUUID, funcId, txn_snapshot, e, user_database_1.pgNodeIsKeyConflictError);
|
1063
1064
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
1064
1065
|
await this.userDatabase.transaction(async (client) => {
|
@@ -1118,7 +1119,7 @@ class DBOSExecutor {
|
|
1118
1119
|
wfCtx.resultBuffer.clear();
|
1119
1120
|
}
|
1120
1121
|
if (txn_id) {
|
1121
|
-
span.setAttribute(
|
1122
|
+
span.setAttribute('pg_txn_id', txn_id);
|
1122
1123
|
}
|
1123
1124
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1124
1125
|
return output;
|
@@ -1128,13 +1129,11 @@ class DBOSExecutor {
|
|
1128
1129
|
const log = (msg) => this.#logNotice(msg);
|
1129
1130
|
const procClassName = this.getProcedureClassName(proc);
|
1130
1131
|
const plainProcName = `${procClassName}_${proc.name}_p`;
|
1131
|
-
const procName = this.config.appVersion
|
1132
|
-
? `v${this.config.appVersion}_${plainProcName}`
|
1133
|
-
: plainProcName;
|
1132
|
+
const procName = this.config.appVersion ? `v${this.config.appVersion}_${plainProcName}` : plainProcName;
|
1134
1133
|
const sql = `CALL "${procName}"(${args.map((_v, i) => `$${i + 1}`).join()});`;
|
1135
1134
|
try {
|
1136
1135
|
client.on('notice', log);
|
1137
|
-
return await client.query(sql, args).then(value => value.rows);
|
1136
|
+
return await client.query(sql, args).then((value) => value.rows);
|
1138
1137
|
}
|
1139
1138
|
finally {
|
1140
1139
|
client.off('notice', log);
|
@@ -1206,7 +1205,7 @@ class DBOSExecutor {
|
|
1206
1205
|
// Check if this execution previously happened, returning its original result if it did.
|
1207
1206
|
const check = await this.systemDatabase.checkOperationOutput(wfCtx.workflowUUID, ctxt.functionID);
|
1208
1207
|
if (check !== exports.dbosNull) {
|
1209
|
-
ctxt.span.setAttribute(
|
1208
|
+
ctxt.span.setAttribute('cached', true);
|
1210
1209
|
ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1211
1210
|
this.tracer.endSpan(ctxt.span);
|
1212
1211
|
return check;
|
@@ -1245,7 +1244,7 @@ class DBOSExecutor {
|
|
1245
1244
|
const e = error;
|
1246
1245
|
errors.push(e);
|
1247
1246
|
this.logger.warn(`Error in step being automatically retried. Attempt ${numAttempts} of ${ctxt.maxAttempts}. ${e.stack}`);
|
1248
|
-
span.addEvent(`Step attempt ${numAttempts + 1} failed`, {
|
1247
|
+
span.addEvent(`Step attempt ${numAttempts + 1} failed`, { retryIntervalSeconds: intervalSeconds, error: error.message }, performance.now());
|
1249
1248
|
if (numAttempts < ctxt.maxAttempts) {
|
1250
1249
|
// Sleep for an interval, then increase the interval by backoffRate.
|
1251
1250
|
// Cap at the maximum allowed retry interval.
|
@@ -1300,7 +1299,9 @@ class DBOSExecutor {
|
|
1300
1299
|
};
|
1301
1300
|
const workflowUUID = idempotencyKey ? destinationUUID + idempotencyKey : undefined;
|
1302
1301
|
return (await this.workflow(temp_workflow, {
|
1303
|
-
workflowUUID: workflowUUID,
|
1302
|
+
workflowUUID: workflowUUID,
|
1303
|
+
tempWfType: TempWorkflowType.send,
|
1304
|
+
configuredInstance: null,
|
1304
1305
|
}, destinationUUID, message, topic)).getResult();
|
1305
1306
|
}
|
1306
1307
|
/**
|
@@ -1337,7 +1338,7 @@ class DBOSExecutor {
|
|
1337
1338
|
for (const nname of channels) {
|
1338
1339
|
await notificationsClient.query(`LISTEN ${nname};`);
|
1339
1340
|
}
|
1340
|
-
notificationsClient.on(
|
1341
|
+
notificationsClient.on('notification', callback);
|
1341
1342
|
return {
|
1342
1343
|
close: async () => {
|
1343
1344
|
for (const nname of channels) {
|
@@ -1349,7 +1350,7 @@ class DBOSExecutor {
|
|
1349
1350
|
}
|
1350
1351
|
notificationsClient.release();
|
1351
1352
|
}
|
1352
|
-
}
|
1353
|
+
},
|
1353
1354
|
};
|
1354
1355
|
}
|
1355
1356
|
/* INTERNAL HELPERS */
|
@@ -1360,13 +1361,13 @@ class DBOSExecutor {
|
|
1360
1361
|
* A recovery process that by default runs during executor init time.
|
1361
1362
|
* It runs to completion all pending workflows that were executing when the previous executor failed.
|
1362
1363
|
*/
|
1363
|
-
async recoverPendingWorkflows(executorIDs = [
|
1364
|
+
async recoverPendingWorkflows(executorIDs = ['local']) {
|
1364
1365
|
if (this.debugMode) {
|
1365
|
-
throw new error_1.DBOSDebuggerError(
|
1366
|
+
throw new error_1.DBOSDebuggerError('Cannot recover pending workflows in debug mode.');
|
1366
1367
|
}
|
1367
1368
|
const pendingWorkflows = [];
|
1368
1369
|
for (const execID of executorIDs) {
|
1369
|
-
if (execID ===
|
1370
|
+
if (execID === 'local' && process.env.DBOS__VMID) {
|
1370
1371
|
this.logger.debug(`Skip local recovery because it's running in a VM: ${process.env.DBOS__VMID}`);
|
1371
1372
|
continue;
|
1372
1373
|
}
|
@@ -1386,7 +1387,7 @@ class DBOSExecutor {
|
|
1386
1387
|
return handlerArray;
|
1387
1388
|
}
|
1388
1389
|
async deactivateEventReceivers() {
|
1389
|
-
this.logger.info(
|
1390
|
+
this.logger.info('Deactivating event receivers');
|
1390
1391
|
for (const evtRcvr of this.eventReceivers || []) {
|
1391
1392
|
try {
|
1392
1393
|
await evtRcvr.destroy();
|
@@ -1425,15 +1426,18 @@ class DBOSExecutor {
|
|
1425
1426
|
const workflowStartUUID = startNewWorkflow ? undefined : workflowUUID;
|
1426
1427
|
if (wfInfo) {
|
1427
1428
|
return this.workflow(wfInfo.workflow, {
|
1428
|
-
workflowUUID: workflowStartUUID,
|
1429
|
-
|
1429
|
+
workflowUUID: workflowStartUUID,
|
1430
|
+
parentCtx: parentCtx,
|
1431
|
+
configuredInstance: configuredInst,
|
1432
|
+
queueName: wfStatus.queueName,
|
1433
|
+
executeWorkflow: true,
|
1430
1434
|
},
|
1431
1435
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1432
1436
|
...inputs);
|
1433
1437
|
}
|
1434
1438
|
// Should be temporary workflows. Parse the name of the workflow.
|
1435
1439
|
const wfName = wfStatus.workflowName;
|
1436
|
-
const nameArr = wfName.split(
|
1440
|
+
const nameArr = wfName.split('-');
|
1437
1441
|
if (!nameArr[0].startsWith(DBOSExecutor.tempWorkflowName)) {
|
1438
1442
|
// CB - Doesn't this happen if the user changed the function name in their code?
|
1439
1443
|
throw new error_1.DBOSError(`This should never happen! Cannot find workflow info for a non-temporary workflow! UUID ${workflowUUID}, name ${wfName}`);
|
@@ -1485,8 +1489,12 @@ class DBOSExecutor {
|
|
1485
1489
|
throw new error_1.DBOSNotRegisteredError(wfName);
|
1486
1490
|
}
|
1487
1491
|
return this.workflow(temp_workflow, {
|
1488
|
-
workflowUUID: workflowStartUUID,
|
1489
|
-
|
1492
|
+
workflowUUID: workflowStartUUID,
|
1493
|
+
parentCtx: parentCtx ?? undefined,
|
1494
|
+
configuredInstance: clsinst,
|
1495
|
+
tempWfType,
|
1496
|
+
tempWfClass,
|
1497
|
+
tempWfName,
|
1490
1498
|
},
|
1491
1499
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1492
1500
|
...inputs);
|
@@ -1510,6 +1518,13 @@ class DBOSExecutor {
|
|
1510
1518
|
oc.workflowUUID = workflowUUID;
|
1511
1519
|
return oc;
|
1512
1520
|
}
|
1521
|
+
async cancelWorkflow(workflowID) {
|
1522
|
+
await this.systemDatabase.cancelWorkflow(workflowID);
|
1523
|
+
}
|
1524
|
+
async resumeWorkflow(workflowID) {
|
1525
|
+
await this.systemDatabase.resumeWorkflow(workflowID);
|
1526
|
+
return await this.executeWorkflowUUID(workflowID, false);
|
1527
|
+
}
|
1513
1528
|
/* BACKGROUND PROCESSES */
|
1514
1529
|
/**
|
1515
1530
|
* Periodically flush the workflow output buffer to the system database.
|
@@ -1532,7 +1547,7 @@ class DBOSExecutor {
|
|
1532
1547
|
try {
|
1533
1548
|
let finishedCnt = 0;
|
1534
1549
|
while (finishedCnt < totalSize) {
|
1535
|
-
let sqlStmt =
|
1550
|
+
let sqlStmt = 'INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, output, error, txn_id, txn_snapshot, created_at) VALUES ';
|
1536
1551
|
let paramCnt = 1;
|
1537
1552
|
const values = [];
|
1538
1553
|
const batchUUIDs = [];
|
@@ -1542,7 +1557,7 @@ class DBOSExecutor {
|
|
1542
1557
|
const txnSnapshot = recorded.txn_snapshot;
|
1543
1558
|
const createdAt = recorded.created_at;
|
1544
1559
|
if (paramCnt > 1) {
|
1545
|
-
sqlStmt +=
|
1560
|
+
sqlStmt += ', ';
|
1546
1561
|
}
|
1547
1562
|
sqlStmt += `($${paramCnt++}, $${paramCnt++}, $${paramCnt++}, $${paramCnt++}, null, $${paramCnt++}, $${paramCnt++})`;
|
1548
1563
|
values.push(workflowUUID, funcID, utils_1.DBOSJSON.stringify(output), utils_1.DBOSJSON.stringify(null), txnSnapshot, createdAt);
|
@@ -1558,7 +1573,9 @@ class DBOSExecutor {
|
|
1558
1573
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1559
1574
|
await this.userDatabase.query(sqlStmt, ...values);
|
1560
1575
|
// Clean up after each batch succeeds
|
1561
|
-
batchUUIDs.forEach((value) => {
|
1576
|
+
batchUUIDs.forEach((value) => {
|
1577
|
+
localBuffer.delete(value);
|
1578
|
+
});
|
1562
1579
|
}
|
1563
1580
|
}
|
1564
1581
|
catch (error) {
|
@@ -1573,14 +1590,14 @@ class DBOSExecutor {
|
|
1573
1590
|
}
|
1574
1591
|
}
|
1575
1592
|
logRegisteredHTTPUrls() {
|
1576
|
-
this.logger.info(
|
1593
|
+
this.logger.info('HTTP endpoints supported:');
|
1577
1594
|
this.registeredOperations.forEach((registeredOperation) => {
|
1578
1595
|
const ro = registeredOperation;
|
1579
1596
|
if (ro.apiURL) {
|
1580
|
-
this.logger.info(
|
1597
|
+
this.logger.info(' ' + ro.apiType.padEnd(6) + ' : ' + ro.apiURL);
|
1581
1598
|
const roles = ro.getRequiredRoles();
|
1582
1599
|
if (roles.length > 0) {
|
1583
|
-
this.logger.info(
|
1600
|
+
this.logger.info(' Required Roles: ' + utils_1.DBOSJSON.stringify(roles));
|
1584
1601
|
}
|
1585
1602
|
}
|
1586
1603
|
});
|