@dbos-inc/dbos-sdk 3.0.46-preview.g649d101bf0 → 3.0.51-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/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +3 -1
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/context.d.ts +2 -4
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +2 -4
- package/dist/src/context.js.map +1 -1
- package/dist/src/datasource.d.ts.map +1 -1
- package/dist/src/datasource.js +18 -14
- package/dist/src/datasource.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +6 -14
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +178 -129
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos-runtime/config.d.ts.map +1 -1
- package/dist/src/dbos-runtime/config.js +5 -2
- package/dist/src/dbos-runtime/config.js.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.d.ts +2 -2
- package/dist/src/dbos-runtime/workflow_management.d.ts.map +1 -1
- package/dist/src/dbos-runtime/workflow_management.js +5 -3
- package/dist/src/dbos-runtime/workflow_management.js.map +1 -1
- package/dist/src/dbos.d.ts +0 -9
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +48 -134
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/httpServer/middleware.d.ts +0 -8
- package/dist/src/httpServer/middleware.d.ts.map +1 -1
- package/dist/src/httpServer/middleware.js +1 -153
- package/dist/src/httpServer/middleware.js.map +1 -1
- package/dist/src/httpServer/server.d.ts.map +1 -1
- package/dist/src/httpServer/server.js +41 -38
- package/dist/src/httpServer/server.js.map +1 -1
- package/dist/src/telemetry/traces.d.ts +2 -0
- package/dist/src/telemetry/traces.d.ts.map +1 -1
- package/dist/src/telemetry/traces.js +22 -1
- package/dist/src/telemetry/traces.js.map +1 -1
- package/dist/src/user_database.d.ts +1 -1
- package/dist/src/user_database.d.ts.map +1 -1
- package/dist/src/user_database.js.map +1 -1
- package/dist/src/utils.d.ts +0 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +0 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -5
@@ -72,30 +72,30 @@ class DBOSExecutor {
|
|
72
72
|
config;
|
73
73
|
initialized;
|
74
74
|
// User Database
|
75
|
-
userDatabase =
|
75
|
+
#userDatabase = undefined;
|
76
|
+
#procedurePool = undefined;
|
76
77
|
// System Database
|
77
78
|
systemDatabase;
|
78
|
-
procedurePool;
|
79
79
|
// Temporary workflows are created by calling transaction/send/recv directly from the executor class
|
80
|
-
static tempWorkflowName = 'temp_workflow';
|
80
|
+
static #tempWorkflowName = 'temp_workflow';
|
81
81
|
telemetryCollector;
|
82
82
|
static defaultNotificationTimeoutSec = 60;
|
83
|
-
debugMode;
|
83
|
+
#debugMode;
|
84
84
|
static systemDBSchemaName = 'dbos';
|
85
85
|
logger;
|
86
86
|
ctxLogger;
|
87
87
|
tracer;
|
88
88
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
89
|
-
typeormEntities = [];
|
90
|
-
drizzleEntities = {};
|
91
|
-
scheduler = new scheduler_1.ScheduledReceiver();
|
92
|
-
wfqEnded = undefined;
|
89
|
+
#typeormEntities = [];
|
90
|
+
#drizzleEntities = {};
|
91
|
+
#scheduler = new scheduler_1.ScheduledReceiver();
|
92
|
+
#wfqEnded = undefined;
|
93
93
|
executorID = utils_1.globalParams.executorID;
|
94
94
|
static globalInstance = undefined;
|
95
95
|
/* WORKFLOW EXECUTOR LIFE CYCLE MANAGEMENT */
|
96
96
|
constructor(config, { systemDatabase, debugMode } = {}) {
|
97
97
|
this.config = config;
|
98
|
-
this
|
98
|
+
this.#debugMode = debugMode ?? false;
|
99
99
|
if (config.telemetry.OTLPExporter) {
|
100
100
|
const OTLPExporter = new exporters_1.TelemetryExporter(config.telemetry.OTLPExporter);
|
101
101
|
this.telemetryCollector = new collector_1.TelemetryCollector(OTLPExporter);
|
@@ -105,12 +105,12 @@ class DBOSExecutor {
|
|
105
105
|
this.telemetryCollector = new collector_1.TelemetryCollector();
|
106
106
|
}
|
107
107
|
this.logger = new logs_1.GlobalLogger(this.telemetryCollector, this.config.telemetry.logs);
|
108
|
-
this.ctxLogger = new logs_1.DBOSContextualLogger(this.logger, () =>
|
108
|
+
this.ctxLogger = new logs_1.DBOSContextualLogger(this.logger, () => api_1.trace.getActiveSpan());
|
109
109
|
this.tracer = new traces_1.Tracer(this.telemetryCollector);
|
110
|
-
if (this
|
110
|
+
if (this.#debugMode) {
|
111
111
|
this.logger.info('Running in debug mode!');
|
112
112
|
}
|
113
|
-
this
|
113
|
+
this.#procedurePool = this.config.userDbClient ? new pg_1.Pool((0, utils_2.getClientConfig)(this.config.databaseUrl)) : undefined;
|
114
114
|
if (systemDatabase) {
|
115
115
|
this.logger.debug('Using provided system database'); // XXX print the name or something
|
116
116
|
this.systemDatabase = systemDatabase;
|
@@ -122,7 +122,10 @@ class DBOSExecutor {
|
|
122
122
|
this.initialized = false;
|
123
123
|
DBOSExecutor.globalInstance = this;
|
124
124
|
}
|
125
|
-
|
125
|
+
get appName() {
|
126
|
+
return this.config.name;
|
127
|
+
}
|
128
|
+
#configureDbClient() {
|
126
129
|
const userDbClient = this.config.userDbClient;
|
127
130
|
const userDBConfig = (0, utils_2.getClientConfig)(this.config.databaseUrl);
|
128
131
|
userDBConfig.max = this.config.userDbPoolSize ?? 20;
|
@@ -130,7 +133,7 @@ class DBOSExecutor {
|
|
130
133
|
// TODO: make Prisma work with debugger proxy.
|
131
134
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
132
135
|
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
|
133
|
-
this
|
136
|
+
this.#userDatabase = new user_database_1.PrismaUserDatabase(
|
134
137
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
135
138
|
new PrismaClient({
|
136
139
|
datasources: {
|
@@ -145,13 +148,13 @@ class DBOSExecutor {
|
|
145
148
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports
|
146
149
|
const DataSourceExports = require('typeorm');
|
147
150
|
try {
|
148
|
-
this
|
151
|
+
this.#userDatabase = new user_database_1.TypeORMDatabase(
|
149
152
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
150
153
|
new DataSourceExports.DataSource({
|
151
154
|
type: 'postgres',
|
152
155
|
url: userDBConfig.connectionString,
|
153
156
|
connectTimeoutMS: userDBConfig.connectionTimeoutMillis,
|
154
|
-
entities: this
|
157
|
+
entities: this.#typeormEntities,
|
155
158
|
poolSize: userDBConfig.max,
|
156
159
|
}));
|
157
160
|
}
|
@@ -170,7 +173,7 @@ class DBOSExecutor {
|
|
170
173
|
max: userDBConfig.max,
|
171
174
|
},
|
172
175
|
};
|
173
|
-
this
|
176
|
+
this.#userDatabase = new user_database_1.KnexUserDatabase((0, knex_1.default)(knexConfig));
|
174
177
|
this.logger.debug('Loaded Knex user database');
|
175
178
|
}
|
176
179
|
else if (userDbClient === user_database_1.UserDatabaseName.DRIZZLE) {
|
@@ -178,13 +181,13 @@ class DBOSExecutor {
|
|
178
181
|
const DrizzleExports = require('drizzle-orm/node-postgres');
|
179
182
|
const drizzlePool = new pg_1.Pool(userDBConfig);
|
180
183
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
181
|
-
const drizzle = DrizzleExports.drizzle(drizzlePool, { schema: this
|
184
|
+
const drizzle = DrizzleExports.drizzle(drizzlePool, { schema: this.#drizzleEntities });
|
182
185
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
183
|
-
this
|
186
|
+
this.#userDatabase = new user_database_1.DrizzleUserDatabase(drizzlePool, drizzle);
|
184
187
|
this.logger.debug('Loaded Drizzle user database');
|
185
188
|
}
|
186
189
|
else {
|
187
|
-
this
|
190
|
+
this.#userDatabase = new user_database_1.PGNodeUserDatabase(userDBConfig);
|
188
191
|
this.logger.debug('Loaded Postgres user database');
|
189
192
|
}
|
190
193
|
}
|
@@ -208,29 +211,31 @@ class DBOSExecutor {
|
|
208
211
|
* With TSORM, we take an array of entities (Function[]) and add them to this.entities:
|
209
212
|
*/
|
210
213
|
if (Array.isArray(reg.ormEntities)) {
|
211
|
-
this
|
214
|
+
this.#typeormEntities = this.#typeormEntities.concat(reg.ormEntities);
|
212
215
|
length = reg.ormEntities.length;
|
213
216
|
}
|
214
217
|
else {
|
215
218
|
/**
|
216
219
|
* With Drizzle, we need to take an object of entities, since the object keys are used to access the entities from ctx.client.query:
|
217
220
|
*/
|
218
|
-
this
|
221
|
+
this.#drizzleEntities = { ...this.#drizzleEntities, ...reg.ormEntities };
|
219
222
|
length = Object.keys(reg.ormEntities).length;
|
220
223
|
}
|
221
224
|
this.logger.debug(`Loaded ${length} ORM entities`);
|
222
225
|
}
|
223
|
-
if (
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
226
|
+
if (this.config.userDbClient) {
|
227
|
+
if (!this.#debugMode) {
|
228
|
+
await (0, user_database_1.createDBIfDoesNotExist)(this.config.databaseUrl, this.logger);
|
229
|
+
}
|
230
|
+
this.#configureDbClient();
|
231
|
+
if (!this.#userDatabase) {
|
232
|
+
this.logger.error('No user database configured!');
|
233
|
+
throw new error_1.DBOSInitializationError('No user database configured!');
|
234
|
+
}
|
235
|
+
// Debug mode doesn't need to initialize the DBs. Everything should appear to be read-only.
|
236
|
+
await this.#userDatabase.init(this.#debugMode);
|
230
237
|
}
|
231
|
-
|
232
|
-
await this.userDatabase.init(this.debugMode);
|
233
|
-
if (!this.debugMode) {
|
238
|
+
if (!this.#debugMode) {
|
234
239
|
await this.systemDatabase.init();
|
235
240
|
}
|
236
241
|
}
|
@@ -253,7 +258,7 @@ class DBOSExecutor {
|
|
253
258
|
}
|
254
259
|
this.initialized = true;
|
255
260
|
// Only execute init code if under non-debug mode
|
256
|
-
if (!this
|
261
|
+
if (!this.#debugMode) {
|
257
262
|
for (const cls of classnames) {
|
258
263
|
// Init its configurations
|
259
264
|
const creg = (0, decorators_1.getClassRegistrationByName)(cls);
|
@@ -306,10 +311,8 @@ class DBOSExecutor {
|
|
306
311
|
try {
|
307
312
|
await this.systemDatabase.awaitRunningWorkflows();
|
308
313
|
await this.systemDatabase.destroy();
|
309
|
-
|
310
|
-
|
311
|
-
}
|
312
|
-
await this.procedurePool.end();
|
314
|
+
await this.#userDatabase?.destroy();
|
315
|
+
await this.#procedurePool?.end();
|
313
316
|
await this.logger.destroy();
|
314
317
|
if (DBOSExecutor.globalInstance === this) {
|
315
318
|
DBOSExecutor.globalInstance = undefined;
|
@@ -321,6 +324,24 @@ class DBOSExecutor {
|
|
321
324
|
throw err;
|
322
325
|
}
|
323
326
|
}
|
327
|
+
async createUserSchema() {
|
328
|
+
if (!this.#userDatabase) {
|
329
|
+
throw new Error('User database not enabled.');
|
330
|
+
}
|
331
|
+
await this.#userDatabase.createSchema();
|
332
|
+
}
|
333
|
+
async dropUserSchema() {
|
334
|
+
if (!this.#userDatabase) {
|
335
|
+
throw new Error('User database not enabled.');
|
336
|
+
}
|
337
|
+
await this.#userDatabase.dropSchema();
|
338
|
+
}
|
339
|
+
async queryUserDbFunction(queryFunction, ...params) {
|
340
|
+
if (!this.#userDatabase) {
|
341
|
+
throw new Error('User database not enabled.');
|
342
|
+
}
|
343
|
+
return await this.#userDatabase.queryFunction(queryFunction, ...params);
|
344
|
+
}
|
324
345
|
// This could return WF, or the function underlying a temp wf
|
325
346
|
#getFunctionInfoFromWFStatus(wf) {
|
326
347
|
const methReg = (0, decorators_1.getFunctionRegistrationByName)(wf.workflowClassName, wf.workflowName);
|
@@ -364,7 +385,7 @@ class DBOSExecutor {
|
|
364
385
|
const pctx = (0, context_1.getCurrentContextStore)();
|
365
386
|
let wConfig = {};
|
366
387
|
const wInfo = (0, decorators_1.getFunctionRegistration)(wf);
|
367
|
-
if (wf.name !== DBOSExecutor
|
388
|
+
if (wf.name !== DBOSExecutor.#tempWorkflowName) {
|
368
389
|
if (!wInfo || !wInfo.workflowConfig) {
|
369
390
|
throw new error_1.DBOSNotRegisteredError(wf.name);
|
370
391
|
}
|
@@ -382,8 +403,8 @@ class DBOSExecutor {
|
|
382
403
|
authenticatedUser: pctx?.authenticatedUser ?? '',
|
383
404
|
authenticatedRoles: pctx?.authenticatedRoles ?? [],
|
384
405
|
assumedRole: pctx?.assumedRole ?? '',
|
385
|
-
}
|
386
|
-
const isTempWorkflow = DBOSExecutor
|
406
|
+
});
|
407
|
+
const isTempWorkflow = DBOSExecutor.#tempWorkflowName === wfname;
|
387
408
|
const internalStatus = {
|
388
409
|
workflowUUID: workflowID,
|
389
410
|
status: params.queueName !== undefined ? workflow_1.StatusString.ENQUEUED : workflow_1.StatusString.PENDING,
|
@@ -408,14 +429,14 @@ class DBOSExecutor {
|
|
408
429
|
priority: priority ?? 0,
|
409
430
|
};
|
410
431
|
if (isTempWorkflow) {
|
411
|
-
internalStatus.workflowName = `${DBOSExecutor
|
432
|
+
internalStatus.workflowName = `${DBOSExecutor.#tempWorkflowName}-${params.tempWfType}-${params.tempWfName}`;
|
412
433
|
internalStatus.workflowClassName = params.tempWfClass ?? '';
|
413
434
|
}
|
414
435
|
let status = undefined;
|
415
436
|
let $deadlineEpochMS = undefined;
|
416
437
|
// Synchronously set the workflow's status to PENDING and record workflow inputs.
|
417
438
|
// We have to do it for all types of workflows because operation_outputs table has a foreign key constraint on workflow status table.
|
418
|
-
if (this
|
439
|
+
if (this.#debugMode) {
|
419
440
|
const wfStatus = await this.systemDatabase.getWorkflowStatus(workflowID);
|
420
441
|
if (!wfStatus) {
|
421
442
|
throw new error_1.DBOSDebuggerError(`Failed to find inputs for workflow UUID ${workflowID}`);
|
@@ -471,7 +492,7 @@ class DBOSExecutor {
|
|
471
492
|
e.dbos_already_logged = true;
|
472
493
|
internalStatus.error = utils_1.DBOSJSON.stringify((0, serialize_error_1.serializeError)(e));
|
473
494
|
internalStatus.status = workflow_1.StatusString.ERROR;
|
474
|
-
if (!exec
|
495
|
+
if (!exec.#debugMode) {
|
475
496
|
await exec.systemDatabase.recordWorkflowError(workflowID, internalStatus);
|
476
497
|
}
|
477
498
|
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e.message });
|
@@ -480,23 +501,24 @@ class DBOSExecutor {
|
|
480
501
|
let result;
|
481
502
|
// Execute the workflow.
|
482
503
|
try {
|
483
|
-
const callResult = await (
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
504
|
+
const callResult = await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
505
|
+
return await (0, context_1.runWithParentContext)(pctx, {
|
506
|
+
presetID,
|
507
|
+
timeoutMS,
|
508
|
+
deadlineEpochMS,
|
509
|
+
workflowId: workflowID,
|
510
|
+
logger: this.ctxLogger,
|
511
|
+
}, () => {
|
512
|
+
const callPromise = wf.call(params.configuredInstance, ...args);
|
513
|
+
if ($deadlineEpochMS === undefined) {
|
514
|
+
return callPromise;
|
515
|
+
}
|
516
|
+
else {
|
517
|
+
return callPromiseWithTimeout(callPromise, $deadlineEpochMS, this.systemDatabase);
|
518
|
+
}
|
519
|
+
});
|
498
520
|
});
|
499
|
-
if (this
|
521
|
+
if (this.#debugMode) {
|
500
522
|
const recordedResult = DBOSExecutor.reviveResultOrError((await this.systemDatabase.awaitWorkflowResult(workflowID)));
|
501
523
|
if (!resultsMatch(recordedResult, callResult)) {
|
502
524
|
this.logger.error(`Detect different output for the workflow UUID ${workflowID}!\n Received: ${utils_1.DBOSJSON.stringify(callResult)}\n Original: ${utils_1.DBOSJSON.stringify(recordedResult)}`);
|
@@ -514,7 +536,7 @@ class DBOSExecutor {
|
|
514
536
|
}
|
515
537
|
internalStatus.output = utils_1.DBOSJSON.stringify(result);
|
516
538
|
internalStatus.status = workflow_1.StatusString.SUCCESS;
|
517
|
-
if (!this
|
539
|
+
if (!this.#debugMode) {
|
518
540
|
await this.systemDatabase.recordWorkflowOutput(workflowID, internalStatus);
|
519
541
|
}
|
520
542
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
@@ -550,7 +572,7 @@ class DBOSExecutor {
|
|
550
572
|
}
|
551
573
|
return result;
|
552
574
|
};
|
553
|
-
if (this
|
575
|
+
if (this.#debugMode ||
|
554
576
|
(status !== 'SUCCESS' && status !== 'ERROR' && (params.queueName === undefined || params.executeWorkflow))) {
|
555
577
|
const workflowPromise = runWorkflow();
|
556
578
|
this.systemDatabase.registerRunningWorkflow(workflowID, workflowPromise);
|
@@ -616,7 +638,7 @@ class DBOSExecutor {
|
|
616
638
|
* Write a operation's output to the database.
|
617
639
|
*/
|
618
640
|
async #recordOutput(query, workflowUUID, funcID, txnSnapshot, output, isKeyConflict, function_name) {
|
619
|
-
if (this
|
641
|
+
if (this.#debugMode) {
|
620
642
|
throw new error_1.DBOSDebuggerError('Cannot record output in debug mode.');
|
621
643
|
}
|
622
644
|
try {
|
@@ -638,7 +660,7 @@ class DBOSExecutor {
|
|
638
660
|
* Record an error in an operation to the database.
|
639
661
|
*/
|
640
662
|
async #recordError(query, workflowUUID, funcID, txnSnapshot, err, isKeyConflict, function_name) {
|
641
|
-
if (this
|
663
|
+
if (this.#debugMode) {
|
642
664
|
throw new error_1.DBOSDebuggerError('Cannot record error in debug mode.');
|
643
665
|
}
|
644
666
|
try {
|
@@ -656,12 +678,17 @@ class DBOSExecutor {
|
|
656
678
|
}
|
657
679
|
}
|
658
680
|
async getTransactions(workflowUUID) {
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
681
|
+
if (this.#userDatabase) {
|
682
|
+
const rows = await this.#userDatabase.query(`SELECT function_id, function_name, output, error FROM ${DBOSExecutor.systemDBSchemaName}.transaction_outputs WHERE workflow_uuid=$1`, workflowUUID);
|
683
|
+
for (const row of rows) {
|
684
|
+
row.output = row.output !== null ? utils_1.DBOSJSON.parse(row.output) : null;
|
685
|
+
row.error = row.error !== null ? (0, serialize_error_1.deserializeError)(utils_1.DBOSJSON.parse(row.error)) : null;
|
686
|
+
}
|
687
|
+
return rows;
|
688
|
+
}
|
689
|
+
else {
|
690
|
+
return [];
|
663
691
|
}
|
664
|
-
return rows;
|
665
692
|
}
|
666
693
|
async runTransactionTempWF(txn, params, ...args) {
|
667
694
|
return await (await this.startTransactionTempWF(txn, params, undefined, undefined, ...args)).getResult();
|
@@ -679,6 +706,10 @@ class DBOSExecutor {
|
|
679
706
|
}, callerWFID, callerFunctionID, ...args);
|
680
707
|
}
|
681
708
|
async callTransactionFunction(txn, clsinst, ...args) {
|
709
|
+
const userDB = this.#userDatabase;
|
710
|
+
if (!userDB) {
|
711
|
+
throw new Error('No user database configured for transactions.');
|
712
|
+
}
|
682
713
|
const txnReg = (0, decorators_1.getFunctionRegistration)(txn);
|
683
714
|
if (!txnReg || !txnReg.txnConfig) {
|
684
715
|
throw new error_1.DBOSNotRegisteredError(txn.name);
|
@@ -698,7 +729,7 @@ class DBOSExecutor {
|
|
698
729
|
assumedRole: pctx.assumedRole ?? '',
|
699
730
|
authenticatedRoles: pctx.authenticatedRoles ?? [],
|
700
731
|
isolationLevel: txnReg.txnConfig.isolationLevel,
|
701
|
-
}
|
732
|
+
});
|
702
733
|
while (true) {
|
703
734
|
await this.systemDatabase.checkIfCanceled(wfid);
|
704
735
|
let txn_snapshot = 'invalid';
|
@@ -707,7 +738,7 @@ class DBOSExecutor {
|
|
707
738
|
// If the UUID is preset, it is possible this execution previously happened. Check, and return its original result if it did.
|
708
739
|
// Note: It is possible to retrieve a generated ID from a workflow handle, run a concurrent execution, and cause trouble for yourself. We recommend against this.
|
709
740
|
let prevResult = exports.dbosNull;
|
710
|
-
const queryFunc = (sql, args) =>
|
741
|
+
const queryFunc = (sql, args) => userDB.queryWithClient(client, sql, ...args);
|
711
742
|
if (pctx.presetID) {
|
712
743
|
const executionResult = await this.#checkExecution(queryFunc, wfid, funcId, txn.name);
|
713
744
|
prevResult = executionResult.result;
|
@@ -728,32 +759,33 @@ class DBOSExecutor {
|
|
728
759
|
// Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
|
729
760
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(queryFunc);
|
730
761
|
}
|
731
|
-
if (this
|
762
|
+
if (this.#debugMode && prevResult === exports.dbosNull) {
|
732
763
|
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the transaction: workflow UUID ${wfid}, step number ${funcId}`);
|
733
764
|
}
|
734
765
|
// Execute the user's transaction.
|
735
766
|
const ctxlog = this.ctxLogger;
|
736
767
|
const result = await (async function () {
|
737
768
|
try {
|
738
|
-
return await (
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
769
|
+
return await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
770
|
+
return await (0, context_1.runWithParentContext)(pctx, {
|
771
|
+
authenticatedRoles: pctx?.authenticatedRoles,
|
772
|
+
authenticatedUser: pctx?.authenticatedUser,
|
773
|
+
workflowId: wfid,
|
774
|
+
curTxFunctionId: funcId,
|
775
|
+
parentCtx: pctx,
|
776
|
+
sqlClient: client,
|
777
|
+
logger: ctxlog,
|
778
|
+
}, async () => {
|
779
|
+
const tf = txn;
|
780
|
+
return await tf.call(clsinst, ...args);
|
781
|
+
});
|
750
782
|
});
|
751
783
|
}
|
752
784
|
catch (e) {
|
753
785
|
return e instanceof Error ? e : new Error(`${e}`);
|
754
786
|
}
|
755
787
|
})();
|
756
|
-
if (this
|
788
|
+
if (this.#debugMode) {
|
757
789
|
if (prevResult instanceof Error) {
|
758
790
|
throw prevResult;
|
759
791
|
}
|
@@ -770,11 +802,11 @@ class DBOSExecutor {
|
|
770
802
|
// Record the execution, commit, and return.
|
771
803
|
try {
|
772
804
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
773
|
-
const pg_txn_id = await this.#recordOutput(queryFunc, wfid, funcId, txn_snapshot, result, (error) =>
|
805
|
+
const pg_txn_id = await this.#recordOutput(queryFunc, wfid, funcId, txn_snapshot, result, (error) => userDB.isKeyConflictError(error), txn.name);
|
774
806
|
span.setAttribute('pg_txn_id', pg_txn_id);
|
775
807
|
}
|
776
808
|
catch (error) {
|
777
|
-
if (
|
809
|
+
if (userDB.isFailedSqlTransactionError(error)) {
|
778
810
|
this.logger.error(`Postgres aborted the ${txn.name} @DBOS.transaction of Workflow ${wfid}, but the function did not raise an exception. Please ensure that the @DBOS.transaction method raises an exception if the database transaction is aborted.`);
|
779
811
|
throw new error_1.DBOSFailedSqlTransactionError(wfid, txn.name);
|
780
812
|
}
|
@@ -785,15 +817,15 @@ class DBOSExecutor {
|
|
785
817
|
return result;
|
786
818
|
};
|
787
819
|
try {
|
788
|
-
const result = await
|
820
|
+
const result = await userDB.transaction(wrappedTransaction, txnReg.txnConfig);
|
789
821
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
790
822
|
this.tracer.endSpan(span);
|
791
823
|
return result;
|
792
824
|
}
|
793
825
|
catch (err) {
|
794
826
|
const e = err;
|
795
|
-
if (!prevResultFound && !this
|
796
|
-
if (
|
827
|
+
if (!prevResultFound && !this.#debugMode && !(e instanceof error_1.DBOSUnexpectedStepError)) {
|
828
|
+
if (userDB.isRetriableTransactionError(err)) {
|
797
829
|
// serialization_failure in PostgreSQL
|
798
830
|
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
799
831
|
// Retry serialization failures.
|
@@ -804,9 +836,9 @@ class DBOSExecutor {
|
|
804
836
|
}
|
805
837
|
// Record and throw other errors.
|
806
838
|
const e = err;
|
807
|
-
await
|
808
|
-
const func = (sql, args) =>
|
809
|
-
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) =>
|
839
|
+
await userDB.transaction(async (client) => {
|
840
|
+
const func = (sql, args) => userDB.queryWithClient(client, sql, ...args);
|
841
|
+
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) => userDB.isKeyConflictError(error), txn.name);
|
810
842
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
811
843
|
}
|
812
844
|
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e.message });
|
@@ -836,7 +868,7 @@ class DBOSExecutor {
|
|
836
868
|
const pctx = (0, context_1.getCurrentContextStore)();
|
837
869
|
const wfid = pctx.workflowId;
|
838
870
|
await this.systemDatabase.checkIfCanceled(wfid);
|
839
|
-
const executeLocally = this
|
871
|
+
const executeLocally = this.#debugMode || (procConfig.executeLocally ?? false);
|
840
872
|
const funcId = (0, context_1.functionIDGetIncrement)();
|
841
873
|
const span = this.tracer.startSpan(proc.name, {
|
842
874
|
operationUUID: wfid,
|
@@ -847,7 +879,7 @@ class DBOSExecutor {
|
|
847
879
|
authenticatedRoles: pctx.authenticatedRoles ?? [],
|
848
880
|
isolationLevel: procInfo.procConfig.isolationLevel,
|
849
881
|
executeLocally,
|
850
|
-
}
|
882
|
+
});
|
851
883
|
try {
|
852
884
|
const result = executeLocally
|
853
885
|
? await this.#callProcedureFunctionLocal(proc, args, span, procInfo, funcId)
|
@@ -865,6 +897,11 @@ class DBOSExecutor {
|
|
865
897
|
}
|
866
898
|
}
|
867
899
|
async #callProcedureFunctionLocal(proc, args, span, procInfo, funcId) {
|
900
|
+
const procPool = this.#procedurePool;
|
901
|
+
const userDB = this.#userDatabase;
|
902
|
+
if (!procPool || !userDB) {
|
903
|
+
throw new Error('User database not enabled.');
|
904
|
+
}
|
868
905
|
let retryWaitMillis = 1;
|
869
906
|
const backoffFactor = 1.5;
|
870
907
|
const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
|
@@ -875,7 +912,7 @@ class DBOSExecutor {
|
|
875
912
|
let txn_snapshot = 'invalid';
|
876
913
|
const wrappedProcedure = async (client) => {
|
877
914
|
let prevResult = exports.dbosNull;
|
878
|
-
const queryFunc = (sql, args) =>
|
915
|
+
const queryFunc = (sql, args) => procPool.query(sql, args).then((v) => v.rows);
|
879
916
|
if (pctx.presetID) {
|
880
917
|
const executionResult = await this.#checkExecution(queryFunc, wfid, funcId, proc.name);
|
881
918
|
prevResult = executionResult.result;
|
@@ -895,7 +932,7 @@ class DBOSExecutor {
|
|
895
932
|
// Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
|
896
933
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(queryFunc);
|
897
934
|
}
|
898
|
-
if (this
|
935
|
+
if (this.#debugMode && prevResult === exports.dbosNull) {
|
899
936
|
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the procedure: workflow UUID ${wfid}, step number ${funcId}`);
|
900
937
|
}
|
901
938
|
// Execute the user's transaction.
|
@@ -908,23 +945,24 @@ class DBOSExecutor {
|
|
908
945
|
throw new error_1.DBOSInvalidWorkflowTransitionError();
|
909
946
|
if (!(0, context_1.isInWorkflowCtx)(pctx))
|
910
947
|
throw new error_1.DBOSInvalidWorkflowTransitionError();
|
911
|
-
return await (
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
948
|
+
return await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
949
|
+
return await (0, context_1.runWithParentContext)(pctx, {
|
950
|
+
curTxFunctionId: funcId,
|
951
|
+
parentCtx: pctx,
|
952
|
+
isInStoredProc: true,
|
953
|
+
sqlClient: client,
|
954
|
+
logger: ctxlog,
|
955
|
+
}, async () => {
|
956
|
+
const pf = proc;
|
957
|
+
return await pf(...args);
|
958
|
+
});
|
921
959
|
});
|
922
960
|
}
|
923
961
|
catch (e) {
|
924
962
|
return e instanceof Error ? e : new Error(`${e}`);
|
925
963
|
}
|
926
964
|
})();
|
927
|
-
if (this
|
965
|
+
if (this.#debugMode) {
|
928
966
|
if (prevResult instanceof Error) {
|
929
967
|
throw prevResult;
|
930
968
|
}
|
@@ -953,8 +991,8 @@ class DBOSExecutor {
|
|
953
991
|
return result;
|
954
992
|
}
|
955
993
|
catch (err) {
|
956
|
-
if (!this
|
957
|
-
if (
|
994
|
+
if (!this.#debugMode) {
|
995
|
+
if (userDB.isRetriableTransactionError(err)) {
|
958
996
|
// serialization_failure in PostgreSQL
|
959
997
|
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
960
998
|
// Retry serialization failures.
|
@@ -969,9 +1007,9 @@ class DBOSExecutor {
|
|
969
1007
|
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
970
1008
|
await this.#recordError(func, wfid, funcId, txn_snapshot, e, user_database_1.pgNodeIsKeyConflictError, proc.name);
|
971
1009
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
972
|
-
await
|
973
|
-
const func = (sql, args) =>
|
974
|
-
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) =>
|
1010
|
+
await userDB.transaction(async (client) => {
|
1011
|
+
const func = (sql, args) => userDB.queryWithClient(client, sql, ...args);
|
1012
|
+
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) => userDB.isKeyConflictError(error), proc.name);
|
975
1013
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
976
1014
|
}
|
977
1015
|
throw err;
|
@@ -979,7 +1017,7 @@ class DBOSExecutor {
|
|
979
1017
|
}
|
980
1018
|
}
|
981
1019
|
async #callProcedureFunctionRemote(proc, args, span, config, funcId) {
|
982
|
-
if (this
|
1020
|
+
if (this.#debugMode) {
|
983
1021
|
throw new error_1.DBOSDebuggerError("Can't invoke stored procedure in debug mode.");
|
984
1022
|
}
|
985
1023
|
const pctx = (0, context_1.getCurrentContextStore)();
|
@@ -1013,7 +1051,10 @@ class DBOSExecutor {
|
|
1013
1051
|
return output;
|
1014
1052
|
}
|
1015
1053
|
async #invokeStoredProc(proc, args) {
|
1016
|
-
|
1054
|
+
if (!this.#procedurePool) {
|
1055
|
+
throw new Error('User Database not enabled.');
|
1056
|
+
}
|
1057
|
+
const client = await this.#procedurePool.connect();
|
1017
1058
|
const log = (msg) => this.#logNotice(msg);
|
1018
1059
|
const procname = (0, decorators_1.getRegisteredFunctionFullName)(proc);
|
1019
1060
|
const plainProcName = `${procname.className}_${procname.name}_p`;
|
@@ -1029,7 +1070,10 @@ class DBOSExecutor {
|
|
1029
1070
|
}
|
1030
1071
|
}
|
1031
1072
|
async invokeStoredProcFunction(func, config) {
|
1032
|
-
|
1073
|
+
if (!this.#procedurePool) {
|
1074
|
+
throw new Error('User Database not enabled.');
|
1075
|
+
}
|
1076
|
+
const client = await this.#procedurePool.connect();
|
1033
1077
|
try {
|
1034
1078
|
const readOnly = config.readOnly ?? false;
|
1035
1079
|
const isolationLevel = config.isolationLevel ?? transaction_1.IsolationLevel.Serializable;
|
@@ -1094,7 +1138,7 @@ class DBOSExecutor {
|
|
1094
1138
|
intervalSeconds: stepConfig.intervalSeconds,
|
1095
1139
|
maxAttempts: stepConfig.maxAttempts,
|
1096
1140
|
backoffRate: stepConfig.backoffRate,
|
1097
|
-
}
|
1141
|
+
});
|
1098
1142
|
// Check if this execution previously happened, returning its original result if it did.
|
1099
1143
|
const checkr = await this.systemDatabase.getOperationResultAndThrowIfCancelled(wfid, funcID);
|
1100
1144
|
if (checkr) {
|
@@ -1107,7 +1151,7 @@ class DBOSExecutor {
|
|
1107
1151
|
this.tracer.endSpan(span);
|
1108
1152
|
return check;
|
1109
1153
|
}
|
1110
|
-
if (this
|
1154
|
+
if (this.#debugMode) {
|
1111
1155
|
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the step: workflow UUID: ${wfid}, step number: ${funcID}`);
|
1112
1156
|
}
|
1113
1157
|
const maxAttempts = stepConfig.maxAttempts ?? 3;
|
@@ -1126,7 +1170,7 @@ class DBOSExecutor {
|
|
1126
1170
|
try {
|
1127
1171
|
await this.systemDatabase.checkIfCanceled(wfid);
|
1128
1172
|
let cresult;
|
1129
|
-
await (0, context_1.runInStepContext)(lctx, funcID,
|
1173
|
+
await (0, context_1.runInStepContext)(lctx, funcID, maxAttempts, attemptNum, async () => {
|
1130
1174
|
const sf = stepFn;
|
1131
1175
|
cresult = await sf.call(clsInst, ...args);
|
1132
1176
|
});
|
@@ -1150,9 +1194,11 @@ class DBOSExecutor {
|
|
1150
1194
|
else {
|
1151
1195
|
try {
|
1152
1196
|
let cresult;
|
1153
|
-
await (
|
1154
|
-
|
1155
|
-
|
1197
|
+
await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
1198
|
+
await (0, context_1.runInStepContext)(lctx, funcID, maxAttempts, undefined, async () => {
|
1199
|
+
const sf = stepFn;
|
1200
|
+
cresult = await sf.call(clsInst, ...args);
|
1201
|
+
});
|
1156
1202
|
});
|
1157
1203
|
result = cresult;
|
1158
1204
|
}
|
@@ -1207,7 +1253,7 @@ class DBOSExecutor {
|
|
1207
1253
|
*/
|
1208
1254
|
forkWorkflow(workflowID, startStep, options = {}) {
|
1209
1255
|
const newWorkflowID = options.newWorkflowID ?? (0, context_1.getNextWFID)(undefined);
|
1210
|
-
return (0, workflow_management_1.forkWorkflow)(this.systemDatabase, this
|
1256
|
+
return (0, workflow_management_1.forkWorkflow)(this.systemDatabase, this.#userDatabase, workflowID, startStep, { ...options, newWorkflowID });
|
1211
1257
|
}
|
1212
1258
|
/**
|
1213
1259
|
* Retrieve a handle for a workflow UUID.
|
@@ -1251,14 +1297,17 @@ class DBOSExecutor {
|
|
1251
1297
|
return (0, workflow_management_1.listQueuedWorkflows)(this.systemDatabase, input);
|
1252
1298
|
}
|
1253
1299
|
async listWorkflowSteps(workflowID) {
|
1254
|
-
return (0, workflow_management_1.listWorkflowSteps)(this.systemDatabase, this
|
1300
|
+
return (0, workflow_management_1.listWorkflowSteps)(this.systemDatabase, this.#userDatabase, workflowID);
|
1255
1301
|
}
|
1256
1302
|
async queryUserDB(sql, params) {
|
1303
|
+
if (!this.#userDatabase) {
|
1304
|
+
throw new Error('User database not enabled.');
|
1305
|
+
}
|
1257
1306
|
if (params !== undefined) {
|
1258
|
-
return await this
|
1307
|
+
return await this.#userDatabase.query(sql, ...params);
|
1259
1308
|
}
|
1260
1309
|
else {
|
1261
|
-
return await this
|
1310
|
+
return await this.#userDatabase.query(sql);
|
1262
1311
|
}
|
1263
1312
|
}
|
1264
1313
|
/* INTERNAL HELPERS */
|
@@ -1267,7 +1316,7 @@ class DBOSExecutor {
|
|
1267
1316
|
* It runs to completion all pending workflows that were executing when the previous executor failed.
|
1268
1317
|
*/
|
1269
1318
|
async recoverPendingWorkflows(executorIDs = ['local']) {
|
1270
|
-
if (this
|
1319
|
+
if (this.#debugMode) {
|
1271
1320
|
throw new error_1.DBOSDebuggerError('Cannot recover pending workflows in debug mode.');
|
1272
1321
|
}
|
1273
1322
|
const handlerArray = [];
|
@@ -1305,7 +1354,7 @@ class DBOSExecutor {
|
|
1305
1354
|
return handlerArray;
|
1306
1355
|
}
|
1307
1356
|
async initEventReceivers() {
|
1308
|
-
this
|
1357
|
+
this.#wfqEnded = wfqueue_1.wfQueueRunner.dispatchLoop(this);
|
1309
1358
|
for (const lcl of (0, decorators_1.getLifecycleListeners)()) {
|
1310
1359
|
await lcl.initialize?.();
|
1311
1360
|
}
|
@@ -1325,7 +1374,7 @@ class DBOSExecutor {
|
|
1325
1374
|
if (stopQueueThread) {
|
1326
1375
|
try {
|
1327
1376
|
wfqueue_1.wfQueueRunner.stop();
|
1328
|
-
await this
|
1377
|
+
await this.#wfqEnded;
|
1329
1378
|
}
|
1330
1379
|
catch (err) {
|
1331
1380
|
const e = err;
|
@@ -1362,7 +1411,7 @@ class DBOSExecutor {
|
|
1362
1411
|
// Should be temporary workflows. Parse the name of the workflow.
|
1363
1412
|
const wfName = wfStatus.workflowName;
|
1364
1413
|
const nameArr = wfName.split('-');
|
1365
|
-
if (!nameArr[0].startsWith(DBOSExecutor
|
1414
|
+
if (!nameArr[0].startsWith(DBOSExecutor.#tempWorkflowName)) {
|
1366
1415
|
throw new error_1.DBOSError(`Cannot find workflow function for a non-temporary workflow, ID ${workflowID}, class '${wfStatus.workflowClassName}', function '${wfName}'; did you change your code?`);
|
1367
1416
|
}
|
1368
1417
|
if (nameArr[1] === exports.TempWorkflowType.transaction) {
|