@dbos-inc/dbos-sdk 2.2.10-preview.g90e74a1e32 → 2.3.9-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 -3
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +152 -141
- 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 +80 -39
- 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 +39 -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 +3 -3
- package/dist/src/dbos-runtime/debug.d.ts.map +1 -1
- package/dist/src/dbos-runtime/debug.js +7 -12
- 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 +20 -13
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +168 -60
- 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 +8 -8
- package/dist/src/wfqueue.js.map +1 -1
- package/dist/src/workflow.d.ts +22 -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: {},
|
@@ -76,8 +76,7 @@ class DBOSExecutor {
|
|
76
76
|
isFlushingBuffers = false;
|
77
77
|
static defaultNotificationTimeoutSec = 60;
|
78
78
|
debugMode;
|
79
|
-
|
80
|
-
static systemDBSchemaName = "dbos";
|
79
|
+
static systemDBSchemaName = 'dbos';
|
81
80
|
logger;
|
82
81
|
tracer;
|
83
82
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
@@ -86,17 +85,16 @@ class DBOSExecutor {
|
|
86
85
|
eventReceivers = [];
|
87
86
|
scheduler = undefined;
|
88
87
|
wfqEnded = undefined;
|
89
|
-
executorID = process.env.DBOS__VMID ||
|
88
|
+
executorID = process.env.DBOS__VMID || 'local';
|
90
89
|
static globalInstance = undefined;
|
91
90
|
/* WORKFLOW EXECUTOR LIFE CYCLE MANAGEMENT */
|
92
91
|
constructor(config, systemDatabase) {
|
93
92
|
this.config = config;
|
94
93
|
this.debugMode = config.debugMode ?? false;
|
95
|
-
this.debugProxy = config.debugProxy;
|
96
94
|
// Set configured environment variables
|
97
95
|
if (config.env) {
|
98
96
|
for (const [key, value] of Object.entries(config.env)) {
|
99
|
-
if (typeof value ===
|
97
|
+
if (typeof value === 'string') {
|
100
98
|
process.env[key] = value;
|
101
99
|
}
|
102
100
|
else {
|
@@ -115,27 +113,15 @@ class DBOSExecutor {
|
|
115
113
|
this.logger = new logs_1.GlobalLogger(this.telemetryCollector, this.config.telemetry?.logs);
|
116
114
|
this.tracer = new traces_1.Tracer(this.telemetryCollector);
|
117
115
|
if (this.debugMode) {
|
118
|
-
this.logger.info(
|
119
|
-
if (this.debugProxy) {
|
120
|
-
try {
|
121
|
-
const url = new URL(this.config.debugProxy);
|
122
|
-
this.config.poolConfig.host = url.hostname;
|
123
|
-
this.config.poolConfig.port = parseInt(url.port, 10);
|
124
|
-
this.logger.info(`Debugging mode proxy: ${this.config.poolConfig.host}:${this.config.poolConfig.port}`);
|
125
|
-
}
|
126
|
-
catch (err) {
|
127
|
-
this.logger.error(err);
|
128
|
-
throw err;
|
129
|
-
}
|
130
|
-
}
|
116
|
+
this.logger.info('Running in debug mode!');
|
131
117
|
}
|
132
118
|
this.procedurePool = new pg_1.Pool(this.config.poolConfig);
|
133
119
|
if (systemDatabase) {
|
134
|
-
this.logger.debug(
|
120
|
+
this.logger.debug('Using provided system database'); // XXX print the name or something
|
135
121
|
this.systemDatabase = systemDatabase;
|
136
122
|
}
|
137
123
|
else {
|
138
|
-
this.logger.debug(
|
124
|
+
this.logger.debug('Using Postgres system database');
|
139
125
|
this.systemDatabase = new system_database_1.PostgresSystemDatabase(this.config.poolConfig, this.config.system_database, this.logger);
|
140
126
|
}
|
141
127
|
this.flushBufferID = setInterval(() => {
|
@@ -144,7 +130,7 @@ class DBOSExecutor {
|
|
144
130
|
void this.flushWorkflowBuffers();
|
145
131
|
}
|
146
132
|
}, this.flushBufferIntervalMs);
|
147
|
-
this.logger.debug(
|
133
|
+
this.logger.debug('Started workflow status buffer worker');
|
148
134
|
this.initialized = false;
|
149
135
|
DBOSExecutor.globalInstance = this;
|
150
136
|
}
|
@@ -154,25 +140,26 @@ class DBOSExecutor {
|
|
154
140
|
if (userDbClient === user_database_1.UserDatabaseName.PRISMA) {
|
155
141
|
// TODO: make Prisma work with debugger proxy.
|
156
142
|
// 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(),
|
143
|
+
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
|
144
|
+
this.userDatabase = new user_database_1.PrismaUserDatabase(
|
158
145
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
159
|
-
|
146
|
+
new PrismaClient({
|
160
147
|
datasources: {
|
161
148
|
db: {
|
162
149
|
url: `postgresql://${userDBConfig.user}:${userDBConfig.password}@${userDBConfig.host}:${userDBConfig.port}/${userDBConfig.database}`,
|
163
150
|
},
|
164
|
-
}
|
151
|
+
},
|
165
152
|
}));
|
166
|
-
this.logger.debug(
|
153
|
+
this.logger.debug('Loaded Prisma user database');
|
167
154
|
}
|
168
155
|
else if (userDbClient === user_database_1.UserDatabaseName.TYPEORM) {
|
169
156
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
170
|
-
const DataSourceExports = require(
|
157
|
+
const DataSourceExports = require('typeorm');
|
171
158
|
try {
|
172
159
|
this.userDatabase = new user_database_1.TypeORMDatabase(
|
173
160
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
174
161
|
new DataSourceExports.DataSource({
|
175
|
-
type:
|
162
|
+
type: 'postgres', // perhaps should move to config file
|
176
163
|
host: userDBConfig.host,
|
177
164
|
port: userDBConfig.port,
|
178
165
|
username: userDBConfig.user,
|
@@ -186,11 +173,11 @@ class DBOSExecutor {
|
|
186
173
|
s.message = `Error loading TypeORM user database: ${s.message}`;
|
187
174
|
this.logger.error(s);
|
188
175
|
}
|
189
|
-
this.logger.debug(
|
176
|
+
this.logger.debug('Loaded TypeORM user database');
|
190
177
|
}
|
191
178
|
else if (userDbClient === user_database_1.UserDatabaseName.KNEX) {
|
192
179
|
const knexConfig = {
|
193
|
-
client:
|
180
|
+
client: 'postgres',
|
194
181
|
connection: {
|
195
182
|
host: userDBConfig.host,
|
196
183
|
port: userDBConfig.port,
|
@@ -201,21 +188,21 @@ class DBOSExecutor {
|
|
201
188
|
},
|
202
189
|
};
|
203
190
|
this.userDatabase = new user_database_1.KnexUserDatabase((0, knex_1.default)(knexConfig));
|
204
|
-
this.logger.debug(
|
191
|
+
this.logger.debug('Loaded Knex user database');
|
205
192
|
}
|
206
193
|
else if (userDbClient === user_database_1.UserDatabaseName.DRIZZLE) {
|
207
194
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
208
|
-
const DrizzleExports = require(
|
195
|
+
const DrizzleExports = require('drizzle-orm/node-postgres');
|
209
196
|
const drizzlePool = new pg_1.Pool(userDBConfig);
|
210
197
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
211
198
|
const drizzle = DrizzleExports.drizzle(drizzlePool, { schema: this.drizzleEntities });
|
212
199
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
213
200
|
this.userDatabase = new user_database_1.DrizzleUserDatabase(drizzlePool, drizzle);
|
214
|
-
this.logger.debug(
|
201
|
+
this.logger.debug('Loaded Drizzle user database');
|
215
202
|
}
|
216
203
|
else {
|
217
204
|
this.userDatabase = new user_database_1.PGNodeUserDatabase(userDBConfig);
|
218
|
-
this.logger.debug(
|
205
|
+
this.logger.debug('Loaded Postgres user database');
|
219
206
|
}
|
220
207
|
}
|
221
208
|
#registerClass(cls) {
|
@@ -253,7 +240,7 @@ class DBOSExecutor {
|
|
253
240
|
}
|
254
241
|
async init(classes) {
|
255
242
|
if (this.initialized) {
|
256
|
-
this.logger.error(
|
243
|
+
this.logger.error('Workflow executor already initialized!');
|
257
244
|
return;
|
258
245
|
}
|
259
246
|
if (!classes || !classes.length) {
|
@@ -267,7 +254,7 @@ class DBOSExecutor {
|
|
267
254
|
* With TSORM, we take an array of entities (Function[]) and add them to this.entities:
|
268
255
|
*/
|
269
256
|
if (Array.isArray(reg.ormEntities)) {
|
270
|
-
this.typeormEntities =
|
257
|
+
this.typeormEntities = this.typeormEntities.concat(reg.ormEntities);
|
271
258
|
length = reg.ormEntities.length;
|
272
259
|
}
|
273
260
|
else {
|
@@ -279,11 +266,13 @@ class DBOSExecutor {
|
|
279
266
|
}
|
280
267
|
this.logger.debug(`Loaded ${length} ORM entities`);
|
281
268
|
}
|
282
|
-
|
269
|
+
if (!this.debugMode) {
|
270
|
+
await (0, user_database_1.createDBIfDoesNotExist)(this.config.poolConfig, this.logger);
|
271
|
+
}
|
283
272
|
this.configureDbClient();
|
284
273
|
if (!this.userDatabase) {
|
285
|
-
this.logger.error(
|
286
|
-
throw new error_1.DBOSInitializationError(
|
274
|
+
this.logger.error('No user database configured!');
|
275
|
+
throw new error_1.DBOSInitializationError('No user database configured!');
|
287
276
|
}
|
288
277
|
for (const cls of classes) {
|
289
278
|
this.#registerClass(cls);
|
@@ -324,30 +313,30 @@ class DBOSExecutor {
|
|
324
313
|
for (const v of this.registeredOperations) {
|
325
314
|
const m = v;
|
326
315
|
if (m.init === true) {
|
327
|
-
this.logger.debug(
|
316
|
+
this.logger.debug('Executing init method: ' + m.name);
|
328
317
|
await m.origFunction(new context_1.InitContext(this));
|
329
318
|
}
|
330
319
|
}
|
331
320
|
await this.recoverPendingWorkflows();
|
332
321
|
}
|
333
|
-
this.logger.info(
|
322
|
+
this.logger.info('Workflow executor initialized');
|
334
323
|
}
|
335
324
|
#logNotice(msg) {
|
336
325
|
switch (msg.severity) {
|
337
|
-
case
|
338
|
-
case
|
339
|
-
case
|
326
|
+
case 'INFO':
|
327
|
+
case 'LOG':
|
328
|
+
case 'NOTICE':
|
340
329
|
this.logger.info(msg.message);
|
341
330
|
break;
|
342
|
-
case
|
331
|
+
case 'WARNING':
|
343
332
|
this.logger.warn(msg.message);
|
344
333
|
break;
|
345
|
-
case
|
334
|
+
case 'DEBUG':
|
346
335
|
this.logger.debug(msg.message);
|
347
336
|
break;
|
348
|
-
case
|
349
|
-
case
|
350
|
-
case
|
337
|
+
case 'ERROR':
|
338
|
+
case 'FATAL':
|
339
|
+
case 'PANIC':
|
351
340
|
this.logger.error(msg.message);
|
352
341
|
break;
|
353
342
|
default:
|
@@ -356,7 +345,7 @@ class DBOSExecutor {
|
|
356
345
|
}
|
357
346
|
async destroy() {
|
358
347
|
if (this.pendingWorkflowMap.size > 0) {
|
359
|
-
this.logger.info(
|
348
|
+
this.logger.info('Waiting for pending workflows to finish.');
|
360
349
|
await Promise.allSettled(this.pendingWorkflowMap.values());
|
361
350
|
}
|
362
351
|
clearInterval(this.flushBufferID);
|
@@ -365,7 +354,7 @@ class DBOSExecutor {
|
|
365
354
|
await this.flushWorkflowBuffers();
|
366
355
|
}
|
367
356
|
while (this.isFlushingBuffers) {
|
368
|
-
this.logger.info(
|
357
|
+
this.logger.info('Waiting for result buffers to be exported.');
|
369
358
|
await (0, utils_1.sleepms)(1000);
|
370
359
|
}
|
371
360
|
await this.systemDatabase.destroy();
|
@@ -439,9 +428,7 @@ class DBOSExecutor {
|
|
439
428
|
this.logger.debug(`Registered stored proc ${cfn}`);
|
440
429
|
}
|
441
430
|
getWorkflowInfo(wf) {
|
442
|
-
const wfname =
|
443
|
-
? wf.name
|
444
|
-
: (0, decorators_1.getRegisteredMethodClassName)(wf) + '.' + wf.name;
|
431
|
+
const wfname = wf.name === DBOSExecutor.tempWorkflowName ? wf.name : (0, decorators_1.getRegisteredMethodClassName)(wf) + '.' + wf.name;
|
445
432
|
return this.workflowInfoMap.get(wfname);
|
446
433
|
}
|
447
434
|
getWorkflowInfoByStatus(wf) {
|
@@ -498,14 +485,14 @@ class DBOSExecutor {
|
|
498
485
|
const wCtxt = new workflow_1.WorkflowContextImpl(this, params.parentCtx, workflowUUID, wConfig, wf.name, presetUUID, params.tempWfType, params.tempWfName);
|
499
486
|
const internalStatus = {
|
500
487
|
workflowUUID: workflowUUID,
|
501
|
-
status:
|
488
|
+
status: params.queueName !== undefined ? workflow_1.StatusString.ENQUEUED : workflow_1.StatusString.PENDING,
|
502
489
|
name: wf.name,
|
503
|
-
className: wCtxt.isTempWorkflow ?
|
504
|
-
configName: params.configuredInstance?.name ||
|
490
|
+
className: wCtxt.isTempWorkflow ? '' : (0, decorators_1.getRegisteredMethodClassName)(wf),
|
491
|
+
configName: params.configuredInstance?.name || '',
|
505
492
|
queueName: params.queueName,
|
506
493
|
authenticatedUser: wCtxt.authenticatedUser,
|
507
494
|
output: undefined,
|
508
|
-
error:
|
495
|
+
error: '',
|
509
496
|
assumedRole: wCtxt.assumedRole,
|
510
497
|
authenticatedRoles: wCtxt.authenticatedRoles,
|
511
498
|
request: wCtxt.request,
|
@@ -514,18 +501,17 @@ class DBOSExecutor {
|
|
514
501
|
applicationID: wCtxt.applicationID,
|
515
502
|
createdAt: Date.now(), // Remember the start time of this workflow
|
516
503
|
maxRetries: wCtxt.maxRecoveryAttempts,
|
517
|
-
recovery: params.recovery === true,
|
518
504
|
};
|
519
505
|
if (wCtxt.isTempWorkflow) {
|
520
506
|
internalStatus.name = `${DBOSExecutor.tempWorkflowName}-${wCtxt.tempWfOperationType}-${wCtxt.tempWfOperationName}`;
|
521
|
-
internalStatus.className = params.tempWfClass ??
|
507
|
+
internalStatus.className = params.tempWfClass ?? '';
|
522
508
|
}
|
523
509
|
let status = undefined;
|
524
510
|
// Synchronously set the workflow's status to PENDING and record workflow inputs (for non single-transaction workflows).
|
525
511
|
// 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
|
-
|
512
|
+
if ((wCtxt.tempWfOperationType !== TempWorkflowType.transaction &&
|
513
|
+
wCtxt.tempWfOperationType !== TempWorkflowType.procedure) ||
|
514
|
+
params.queueName !== undefined) {
|
529
515
|
if (this.debugMode) {
|
530
516
|
const wfStatus = await this.systemDatabase.getWorkflowStatus(workflowUUID);
|
531
517
|
const wfInputs = await this.systemDatabase.getWorkflowInputs(workflowUUID);
|
@@ -590,7 +576,7 @@ class DBOSExecutor {
|
|
590
576
|
// Retrieve the handle and wait for the result.
|
591
577
|
const retrievedHandle = this.retrieveWorkflow(workflowUUID);
|
592
578
|
result = await retrievedHandle.getResult();
|
593
|
-
wCtxt.span.setAttribute(
|
579
|
+
wCtxt.span.setAttribute('cached', true);
|
594
580
|
wCtxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
595
581
|
}
|
596
582
|
else {
|
@@ -616,8 +602,8 @@ class DBOSExecutor {
|
|
616
602
|
}
|
617
603
|
finally {
|
618
604
|
this.tracer.endSpan(wCtxt.span);
|
619
|
-
if (wCtxt.tempWfOperationType === TempWorkflowType.transaction
|
620
|
-
|
605
|
+
if (wCtxt.tempWfOperationType === TempWorkflowType.transaction ||
|
606
|
+
wCtxt.tempWfOperationType === TempWorkflowType.procedure) {
|
621
607
|
// For single-transaction workflows, asynchronously record inputs.
|
622
608
|
// 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
609
|
if (!this.debugMode) {
|
@@ -631,12 +617,13 @@ class DBOSExecutor {
|
|
631
617
|
}
|
632
618
|
return result;
|
633
619
|
};
|
634
|
-
if (this.debugMode ||
|
620
|
+
if (this.debugMode ||
|
621
|
+
(status !== 'SUCCESS' && status !== 'ERROR' && (params.queueName === undefined || params.executeWorkflow))) {
|
635
622
|
const workflowPromise = runWorkflow();
|
636
623
|
// Need to await for the workflow and capture errors.
|
637
624
|
const awaitWorkflowPromise = workflowPromise
|
638
625
|
.catch((error) => {
|
639
|
-
this.logger.debug(
|
626
|
+
this.logger.debug('Captured error in awaitWorkflowPromise: ' + error);
|
640
627
|
})
|
641
628
|
.finally(() => {
|
642
629
|
// Remove itself from pending workflow map.
|
@@ -663,7 +650,7 @@ class DBOSExecutor {
|
|
663
650
|
* Retrieve the transaction snapshot information of the current transaction
|
664
651
|
*/
|
665
652
|
static async #retrieveSnapshot(query) {
|
666
|
-
const rows = await query(
|
653
|
+
const rows = await query('SELECT pg_current_snapshot()::text as txn_snapshot;', []);
|
667
654
|
return rows[0].txn_snapshot;
|
668
655
|
}
|
669
656
|
/**
|
@@ -675,14 +662,14 @@ class DBOSExecutor {
|
|
675
662
|
*/
|
676
663
|
async #checkExecution(query, workflowUUID, funcID) {
|
677
664
|
// Note: we read the current snapshot, not the recorded one!
|
678
|
-
const rows = await query(
|
665
|
+
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
666
|
if (rows.length === 0 || rows.length > 2) {
|
680
|
-
this.logger.error(
|
681
|
-
throw new error_1.DBOSError(
|
667
|
+
this.logger.error('Unexpected! This should never happen. Returned rows: ' + rows.toString());
|
668
|
+
throw new error_1.DBOSError('This should never happen. Returned rows: ' + rows.toString());
|
682
669
|
}
|
683
670
|
const res = {
|
684
671
|
output: exports.dbosNull,
|
685
|
-
txn_snapshot:
|
672
|
+
txn_snapshot: '',
|
686
673
|
};
|
687
674
|
// recorded=false row will be first because we used ORDER BY.
|
688
675
|
res.txn_snapshot = rows[0].txn_snapshot;
|
@@ -701,11 +688,11 @@ class DBOSExecutor {
|
|
701
688
|
*/
|
702
689
|
async #recordOutput(query, workflowUUID, funcID, txnSnapshot, output, isKeyConflict) {
|
703
690
|
if (this.debugMode) {
|
704
|
-
throw new error_1.DBOSDebuggerError(
|
691
|
+
throw new error_1.DBOSDebuggerError('Cannot record output in debug mode.');
|
705
692
|
}
|
706
693
|
try {
|
707
694
|
const serialOutput = utils_1.DBOSJSON.stringify(output);
|
708
|
-
const rows = await query(
|
695
|
+
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
696
|
return rows[0].txn_id;
|
710
697
|
}
|
711
698
|
catch (error) {
|
@@ -723,11 +710,11 @@ class DBOSExecutor {
|
|
723
710
|
*/
|
724
711
|
async #recordError(query, workflowUUID, funcID, txnSnapshot, err, isKeyConflict) {
|
725
712
|
if (this.debugMode) {
|
726
|
-
throw new error_1.DBOSDebuggerError(
|
713
|
+
throw new error_1.DBOSDebuggerError('Cannot record error in debug mode.');
|
727
714
|
}
|
728
715
|
try {
|
729
716
|
const serialErr = utils_1.DBOSJSON.stringify((0, serialize_error_1.serializeError)(err));
|
730
|
-
await query(
|
717
|
+
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
718
|
}
|
732
719
|
catch (error) {
|
733
720
|
if (isKeyConflict(error)) {
|
@@ -749,11 +736,11 @@ class DBOSExecutor {
|
|
749
736
|
return;
|
750
737
|
}
|
751
738
|
if (this.debugMode) {
|
752
|
-
throw new error_1.DBOSDebuggerError(
|
739
|
+
throw new error_1.DBOSDebuggerError('Cannot flush result buffer in debug mode.');
|
753
740
|
}
|
754
741
|
funcIDs.sort();
|
755
742
|
try {
|
756
|
-
let sqlStmt =
|
743
|
+
let sqlStmt = 'INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, output, error, txn_id, txn_snapshot, created_at) VALUES ';
|
757
744
|
let paramCnt = 1;
|
758
745
|
const values = [];
|
759
746
|
for (const funcID of funcIDs) {
|
@@ -764,7 +751,7 @@ class DBOSExecutor {
|
|
764
751
|
const txnSnapshot = recorded.txn_snapshot;
|
765
752
|
const createdAt = recorded.created_at;
|
766
753
|
if (paramCnt > 1) {
|
767
|
-
sqlStmt +=
|
754
|
+
sqlStmt += ', ';
|
768
755
|
}
|
769
756
|
sqlStmt += `($${paramCnt++}, $${paramCnt++}, $${paramCnt++}, $${paramCnt++}, null, $${paramCnt++}, $${paramCnt++})`;
|
770
757
|
values.push(workflowUUID, funcID, utils_1.DBOSJSON.stringify(output), utils_1.DBOSJSON.stringify(null), txnSnapshot, createdAt);
|
@@ -787,7 +774,7 @@ class DBOSExecutor {
|
|
787
774
|
return this.#flushResultBuffer(func, resultBuffer, workflowUUID, (error) => this.userDatabase.isKeyConflictError(error));
|
788
775
|
}
|
789
776
|
#flushResultBufferProc(client, resultBuffer, workflowUUID) {
|
790
|
-
const func = (sql, args) => client.query(sql, args).then(v => v.rows);
|
777
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
791
778
|
return this.#flushResultBuffer(func, resultBuffer, workflowUUID, user_database_1.pgNodeIsKeyConflictError);
|
792
779
|
}
|
793
780
|
async transaction(txn, params, ...args) {
|
@@ -823,7 +810,7 @@ class DBOSExecutor {
|
|
823
810
|
isolationLevel: txnInfo.config.isolationLevel,
|
824
811
|
}, wfCtx.span);
|
825
812
|
while (true) {
|
826
|
-
let txn_snapshot =
|
813
|
+
let txn_snapshot = 'invalid';
|
827
814
|
const workflowUUID = wfCtx.workflowUUID;
|
828
815
|
const wrappedTransaction = async (client) => {
|
829
816
|
const tCtxt = new transaction_1.TransactionContextImpl(this.userDatabase.getName(), client, wfCtx, span, this.logger, funcId, txn.name);
|
@@ -834,7 +821,7 @@ class DBOSExecutor {
|
|
834
821
|
const check = await this.#checkExecution(func, workflowUUID, funcId);
|
835
822
|
txn_snapshot = check.txn_snapshot;
|
836
823
|
if (check.output !== exports.dbosNull) {
|
837
|
-
tCtxt.span.setAttribute(
|
824
|
+
tCtxt.span.setAttribute('cached', true);
|
838
825
|
tCtxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
839
826
|
this.tracer.endSpan(tCtxt.span);
|
840
827
|
return check.output;
|
@@ -881,7 +868,7 @@ class DBOSExecutor {
|
|
881
868
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
882
869
|
const func = (sql, args) => this.userDatabase.queryWithClient(client, sql, ...args);
|
883
870
|
const pg_txn_id = await this.#recordOutput(func, wfCtx.workflowUUID, funcId, txn_snapshot, result, (error) => this.userDatabase.isKeyConflictError(error));
|
884
|
-
tCtxt.span.setAttribute(
|
871
|
+
tCtxt.span.setAttribute('pg_txn_id', pg_txn_id);
|
885
872
|
wfCtx.resultBuffer.clear();
|
886
873
|
}
|
887
874
|
catch (error) {
|
@@ -908,7 +895,7 @@ class DBOSExecutor {
|
|
908
895
|
}
|
909
896
|
if (this.userDatabase.isRetriableTransactionError(err)) {
|
910
897
|
// serialization_failure in PostgreSQL
|
911
|
-
span.addEvent(
|
898
|
+
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
912
899
|
// Retry serialization failures.
|
913
900
|
await (0, utils_1.sleepms)(retryWaitMillis);
|
914
901
|
retryWaitMillis *= backoffFactor;
|
@@ -981,15 +968,15 @@ class DBOSExecutor {
|
|
981
968
|
const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
|
982
969
|
const readOnly = procInfo.config.readOnly ?? false;
|
983
970
|
while (true) {
|
984
|
-
let txn_snapshot =
|
971
|
+
let txn_snapshot = 'invalid';
|
985
972
|
const wrappedProcedure = async (client) => {
|
986
973
|
const ctxt = new procedure_1.StoredProcedureContextImpl(client, wfCtx, span, this.logger, funcId, proc.name);
|
987
974
|
if (wfCtx.presetUUID) {
|
988
|
-
const func = (sql, args) => this.procedurePool.query(sql, args).then(v => v.rows);
|
975
|
+
const func = (sql, args) => this.procedurePool.query(sql, args).then((v) => v.rows);
|
989
976
|
const check = await this.#checkExecution(func, wfCtx.workflowUUID, funcId);
|
990
977
|
txn_snapshot = check.txn_snapshot;
|
991
978
|
if (check.output !== exports.dbosNull) {
|
992
|
-
ctxt.span.setAttribute(
|
979
|
+
ctxt.span.setAttribute('cached', true);
|
993
980
|
ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
994
981
|
this.tracer.endSpan(ctxt.span);
|
995
982
|
return check.output;
|
@@ -997,7 +984,7 @@ class DBOSExecutor {
|
|
997
984
|
}
|
998
985
|
else {
|
999
986
|
// 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);
|
987
|
+
const func = (sql, args) => this.procedurePool.query(sql, args).then((v) => v.rows);
|
1001
988
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(func);
|
1002
989
|
}
|
1003
990
|
if (this.debugMode) {
|
@@ -1031,23 +1018,25 @@ class DBOSExecutor {
|
|
1031
1018
|
}
|
1032
1019
|
else {
|
1033
1020
|
// 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);
|
1021
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1035
1022
|
const pg_txn_id = await this.#recordOutput(func, wfCtx.workflowUUID, funcId, txn_snapshot, result, user_database_1.pgNodeIsKeyConflictError);
|
1036
1023
|
// const pg_txn_id = await wfCtx.recordOutputProc<R>(client, funcId, txn_snapshot, result);
|
1037
|
-
ctxt.span.setAttribute(
|
1024
|
+
ctxt.span.setAttribute('pg_txn_id', pg_txn_id);
|
1038
1025
|
wfCtx.resultBuffer.clear();
|
1039
1026
|
}
|
1040
1027
|
return result;
|
1041
1028
|
};
|
1042
1029
|
try {
|
1043
|
-
const result = await this.invokeStoredProcFunction(wrappedProcedure, {
|
1030
|
+
const result = await this.invokeStoredProcFunction(wrappedProcedure, {
|
1031
|
+
isolationLevel: procInfo.config.isolationLevel,
|
1032
|
+
});
|
1044
1033
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1045
1034
|
return result;
|
1046
1035
|
}
|
1047
1036
|
catch (err) {
|
1048
1037
|
if (this.userDatabase.isRetriableTransactionError(err)) {
|
1049
1038
|
// serialization_failure in PostgreSQL
|
1050
|
-
span.addEvent(
|
1039
|
+
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
1051
1040
|
// Retry serialization failures.
|
1052
1041
|
await (0, utils_1.sleepms)(retryWaitMillis);
|
1053
1042
|
retryWaitMillis *= backoffFactor;
|
@@ -1058,7 +1047,7 @@ class DBOSExecutor {
|
|
1058
1047
|
const e = err;
|
1059
1048
|
await this.invokeStoredProcFunction(async (client) => {
|
1060
1049
|
await this.#flushResultBufferProc(client, wfCtx.resultBuffer, wfCtx.workflowUUID);
|
1061
|
-
const func = (sql, args) => client.query(sql, args).then(v => v.rows);
|
1050
|
+
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1062
1051
|
await this.#recordError(func, wfCtx.workflowUUID, funcId, txn_snapshot, e, user_database_1.pgNodeIsKeyConflictError);
|
1063
1052
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
1064
1053
|
await this.userDatabase.transaction(async (client) => {
|
@@ -1118,7 +1107,7 @@ class DBOSExecutor {
|
|
1118
1107
|
wfCtx.resultBuffer.clear();
|
1119
1108
|
}
|
1120
1109
|
if (txn_id) {
|
1121
|
-
span.setAttribute(
|
1110
|
+
span.setAttribute('pg_txn_id', txn_id);
|
1122
1111
|
}
|
1123
1112
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1124
1113
|
return output;
|
@@ -1128,13 +1117,11 @@ class DBOSExecutor {
|
|
1128
1117
|
const log = (msg) => this.#logNotice(msg);
|
1129
1118
|
const procClassName = this.getProcedureClassName(proc);
|
1130
1119
|
const plainProcName = `${procClassName}_${proc.name}_p`;
|
1131
|
-
const procName = this.config.appVersion
|
1132
|
-
? `v${this.config.appVersion}_${plainProcName}`
|
1133
|
-
: plainProcName;
|
1120
|
+
const procName = this.config.appVersion ? `v${this.config.appVersion}_${plainProcName}` : plainProcName;
|
1134
1121
|
const sql = `CALL "${procName}"(${args.map((_v, i) => `$${i + 1}`).join()});`;
|
1135
1122
|
try {
|
1136
1123
|
client.on('notice', log);
|
1137
|
-
return await client.query(sql, args).then(value => value.rows);
|
1124
|
+
return await client.query(sql, args).then((value) => value.rows);
|
1138
1125
|
}
|
1139
1126
|
finally {
|
1140
1127
|
client.off('notice', log);
|
@@ -1206,7 +1193,7 @@ class DBOSExecutor {
|
|
1206
1193
|
// Check if this execution previously happened, returning its original result if it did.
|
1207
1194
|
const check = await this.systemDatabase.checkOperationOutput(wfCtx.workflowUUID, ctxt.functionID);
|
1208
1195
|
if (check !== exports.dbosNull) {
|
1209
|
-
ctxt.span.setAttribute(
|
1196
|
+
ctxt.span.setAttribute('cached', true);
|
1210
1197
|
ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1211
1198
|
this.tracer.endSpan(ctxt.span);
|
1212
1199
|
return check;
|
@@ -1245,7 +1232,7 @@ class DBOSExecutor {
|
|
1245
1232
|
const e = error;
|
1246
1233
|
errors.push(e);
|
1247
1234
|
this.logger.warn(`Error in step being automatically retried. Attempt ${numAttempts} of ${ctxt.maxAttempts}. ${e.stack}`);
|
1248
|
-
span.addEvent(`Step attempt ${numAttempts + 1} failed`, {
|
1235
|
+
span.addEvent(`Step attempt ${numAttempts + 1} failed`, { retryIntervalSeconds: intervalSeconds, error: error.message }, performance.now());
|
1249
1236
|
if (numAttempts < ctxt.maxAttempts) {
|
1250
1237
|
// Sleep for an interval, then increase the interval by backoffRate.
|
1251
1238
|
// Cap at the maximum allowed retry interval.
|
@@ -1300,7 +1287,9 @@ class DBOSExecutor {
|
|
1300
1287
|
};
|
1301
1288
|
const workflowUUID = idempotencyKey ? destinationUUID + idempotencyKey : undefined;
|
1302
1289
|
return (await this.workflow(temp_workflow, {
|
1303
|
-
workflowUUID: workflowUUID,
|
1290
|
+
workflowUUID: workflowUUID,
|
1291
|
+
tempWfType: TempWorkflowType.send,
|
1292
|
+
configuredInstance: null,
|
1304
1293
|
}, destinationUUID, message, topic)).getResult();
|
1305
1294
|
}
|
1306
1295
|
/**
|
@@ -1337,7 +1326,7 @@ class DBOSExecutor {
|
|
1337
1326
|
for (const nname of channels) {
|
1338
1327
|
await notificationsClient.query(`LISTEN ${nname};`);
|
1339
1328
|
}
|
1340
|
-
notificationsClient.on(
|
1329
|
+
notificationsClient.on('notification', callback);
|
1341
1330
|
return {
|
1342
1331
|
close: async () => {
|
1343
1332
|
for (const nname of channels) {
|
@@ -1349,7 +1338,7 @@ class DBOSExecutor {
|
|
1349
1338
|
}
|
1350
1339
|
notificationsClient.release();
|
1351
1340
|
}
|
1352
|
-
}
|
1341
|
+
},
|
1353
1342
|
};
|
1354
1343
|
}
|
1355
1344
|
/* INTERNAL HELPERS */
|
@@ -1360,33 +1349,39 @@ class DBOSExecutor {
|
|
1360
1349
|
* A recovery process that by default runs during executor init time.
|
1361
1350
|
* It runs to completion all pending workflows that were executing when the previous executor failed.
|
1362
1351
|
*/
|
1363
|
-
async recoverPendingWorkflows(executorIDs = [
|
1352
|
+
async recoverPendingWorkflows(executorIDs = ['local']) {
|
1364
1353
|
if (this.debugMode) {
|
1365
|
-
throw new error_1.DBOSDebuggerError(
|
1354
|
+
throw new error_1.DBOSDebuggerError('Cannot recover pending workflows in debug mode.');
|
1366
1355
|
}
|
1367
|
-
const
|
1356
|
+
const handlerArray = [];
|
1368
1357
|
for (const execID of executorIDs) {
|
1369
|
-
if (execID ===
|
1358
|
+
if (execID === 'local' && process.env.DBOS__VMID) {
|
1370
1359
|
this.logger.debug(`Skip local recovery because it's running in a VM: ${process.env.DBOS__VMID}`);
|
1371
1360
|
continue;
|
1372
1361
|
}
|
1373
|
-
this.logger.debug(`Recovering workflows
|
1374
|
-
const
|
1375
|
-
pendingWorkflows
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1362
|
+
this.logger.debug(`Recovering workflows assigned to executor: ${execID}`);
|
1363
|
+
const pendingWorkflows = await this.systemDatabase.getPendingWorkflows(execID);
|
1364
|
+
for (const pendingWorkflow of pendingWorkflows) {
|
1365
|
+
this.logger.debug(`Recovering workflow: ${pendingWorkflow.workflowUUID}. Queue name: ${pendingWorkflow.queueName}`);
|
1366
|
+
try {
|
1367
|
+
// If the workflow is member of a queue, re-enqueue it.
|
1368
|
+
if (pendingWorkflow.queueName) {
|
1369
|
+
await this.systemDatabase.clearQueueAssignment(pendingWorkflow.workflowUUID);
|
1370
|
+
handlerArray.push(this.retrieveWorkflow(pendingWorkflow.workflowUUID));
|
1371
|
+
}
|
1372
|
+
else {
|
1373
|
+
handlerArray.push(await this.executeWorkflowUUID(pendingWorkflow.workflowUUID));
|
1374
|
+
}
|
1375
|
+
}
|
1376
|
+
catch (e) {
|
1377
|
+
this.logger.warn(`Recovery of workflow ${pendingWorkflow.workflowUUID} failed: ${e.message}`);
|
1378
|
+
}
|
1384
1379
|
}
|
1385
1380
|
}
|
1386
1381
|
return handlerArray;
|
1387
1382
|
}
|
1388
1383
|
async deactivateEventReceivers() {
|
1389
|
-
this.logger.info(
|
1384
|
+
this.logger.info('Deactivating event receivers');
|
1390
1385
|
for (const evtRcvr of this.eventReceivers || []) {
|
1391
1386
|
try {
|
1392
1387
|
await evtRcvr.destroy();
|
@@ -1425,15 +1420,18 @@ class DBOSExecutor {
|
|
1425
1420
|
const workflowStartUUID = startNewWorkflow ? undefined : workflowUUID;
|
1426
1421
|
if (wfInfo) {
|
1427
1422
|
return this.workflow(wfInfo.workflow, {
|
1428
|
-
workflowUUID: workflowStartUUID,
|
1429
|
-
|
1423
|
+
workflowUUID: workflowStartUUID,
|
1424
|
+
parentCtx: parentCtx,
|
1425
|
+
configuredInstance: configuredInst,
|
1426
|
+
queueName: wfStatus.queueName,
|
1427
|
+
executeWorkflow: true,
|
1430
1428
|
},
|
1431
1429
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1432
1430
|
...inputs);
|
1433
1431
|
}
|
1434
1432
|
// Should be temporary workflows. Parse the name of the workflow.
|
1435
1433
|
const wfName = wfStatus.workflowName;
|
1436
|
-
const nameArr = wfName.split(
|
1434
|
+
const nameArr = wfName.split('-');
|
1437
1435
|
if (!nameArr[0].startsWith(DBOSExecutor.tempWorkflowName)) {
|
1438
1436
|
// CB - Doesn't this happen if the user changed the function name in their code?
|
1439
1437
|
throw new error_1.DBOSError(`This should never happen! Cannot find workflow info for a non-temporary workflow! UUID ${workflowUUID}, name ${wfName}`);
|
@@ -1485,8 +1483,12 @@ class DBOSExecutor {
|
|
1485
1483
|
throw new error_1.DBOSNotRegisteredError(wfName);
|
1486
1484
|
}
|
1487
1485
|
return this.workflow(temp_workflow, {
|
1488
|
-
workflowUUID: workflowStartUUID,
|
1489
|
-
|
1486
|
+
workflowUUID: workflowStartUUID,
|
1487
|
+
parentCtx: parentCtx ?? undefined,
|
1488
|
+
configuredInstance: clsinst,
|
1489
|
+
tempWfType,
|
1490
|
+
tempWfClass,
|
1491
|
+
tempWfName,
|
1490
1492
|
},
|
1491
1493
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1492
1494
|
...inputs);
|
@@ -1510,6 +1512,13 @@ class DBOSExecutor {
|
|
1510
1512
|
oc.workflowUUID = workflowUUID;
|
1511
1513
|
return oc;
|
1512
1514
|
}
|
1515
|
+
async cancelWorkflow(workflowID) {
|
1516
|
+
await this.systemDatabase.cancelWorkflow(workflowID);
|
1517
|
+
}
|
1518
|
+
async resumeWorkflow(workflowID) {
|
1519
|
+
await this.systemDatabase.resumeWorkflow(workflowID);
|
1520
|
+
return await this.executeWorkflowUUID(workflowID, false);
|
1521
|
+
}
|
1513
1522
|
/* BACKGROUND PROCESSES */
|
1514
1523
|
/**
|
1515
1524
|
* Periodically flush the workflow output buffer to the system database.
|
@@ -1532,7 +1541,7 @@ class DBOSExecutor {
|
|
1532
1541
|
try {
|
1533
1542
|
let finishedCnt = 0;
|
1534
1543
|
while (finishedCnt < totalSize) {
|
1535
|
-
let sqlStmt =
|
1544
|
+
let sqlStmt = 'INSERT INTO dbos.transaction_outputs (workflow_uuid, function_id, output, error, txn_id, txn_snapshot, created_at) VALUES ';
|
1536
1545
|
let paramCnt = 1;
|
1537
1546
|
const values = [];
|
1538
1547
|
const batchUUIDs = [];
|
@@ -1542,7 +1551,7 @@ class DBOSExecutor {
|
|
1542
1551
|
const txnSnapshot = recorded.txn_snapshot;
|
1543
1552
|
const createdAt = recorded.created_at;
|
1544
1553
|
if (paramCnt > 1) {
|
1545
|
-
sqlStmt +=
|
1554
|
+
sqlStmt += ', ';
|
1546
1555
|
}
|
1547
1556
|
sqlStmt += `($${paramCnt++}, $${paramCnt++}, $${paramCnt++}, $${paramCnt++}, null, $${paramCnt++}, $${paramCnt++})`;
|
1548
1557
|
values.push(workflowUUID, funcID, utils_1.DBOSJSON.stringify(output), utils_1.DBOSJSON.stringify(null), txnSnapshot, createdAt);
|
@@ -1558,7 +1567,9 @@ class DBOSExecutor {
|
|
1558
1567
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
1559
1568
|
await this.userDatabase.query(sqlStmt, ...values);
|
1560
1569
|
// Clean up after each batch succeeds
|
1561
|
-
batchUUIDs.forEach((value) => {
|
1570
|
+
batchUUIDs.forEach((value) => {
|
1571
|
+
localBuffer.delete(value);
|
1572
|
+
});
|
1562
1573
|
}
|
1563
1574
|
}
|
1564
1575
|
catch (error) {
|
@@ -1573,14 +1584,14 @@ class DBOSExecutor {
|
|
1573
1584
|
}
|
1574
1585
|
}
|
1575
1586
|
logRegisteredHTTPUrls() {
|
1576
|
-
this.logger.info(
|
1587
|
+
this.logger.info('HTTP endpoints supported:');
|
1577
1588
|
this.registeredOperations.forEach((registeredOperation) => {
|
1578
1589
|
const ro = registeredOperation;
|
1579
1590
|
if (ro.apiURL) {
|
1580
|
-
this.logger.info(
|
1591
|
+
this.logger.info(' ' + ro.apiType.padEnd(6) + ' : ' + ro.apiURL);
|
1581
1592
|
const roles = ro.getRequiredRoles();
|
1582
1593
|
if (roles.length > 0) {
|
1583
|
-
this.logger.info(
|
1594
|
+
this.logger.info(' Required Roles: ' + utils_1.DBOSJSON.stringify(roles));
|
1584
1595
|
}
|
1585
1596
|
}
|
1586
1597
|
});
|