@dbos-inc/dbos-sdk 2.2.10-preview.g90e74a1e32 → 2.3.9-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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
|
});
|