@dbos-inc/typeorm-datasource 5.2.4-preview → 5.2.6-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/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/index.ts +15 -6
- package/package.json +1 -1
- package/tests/datasource.test.ts +156 -45
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAQ,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAQ,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAYL,cAAc,EAEd,mBAAmB,EAGpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKpD,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAsQD,qBAAa,iBAAkB,YAAW,cAAc,CAAC,wBAAwB,CAAC;;IAsC9E,QAAQ,CAAC,IAAI,EAAE,MAAM;IArBvB,MAAM,KAAK,aAAa,kBAEvB;IAED,IAAI,aAAa,kBAEhB;WAEY,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,GAAE,MAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjG,OAAO;IAqBP,MAAM,CAAC,gBAAgB,CACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAElB,QAAQ,EAAE,QAAQ,EAAE,EACpB,UAAU,GAAE,MAAe;IAK7B,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,UAAU,GAAE,MAAe;IAIrF;;;;;;OAMG;IACG,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,wBAAwB;IAIjF;;;;;;;;OAQG;IACH,mBAAmB,CAAC,IAAI,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,EACtD,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,EACpD,MAAM,CAAC,EAAE,wBAAwB,GAAG,YAAY,GAC/C,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC;IAIjD;;OAEG;IACH,WAAW,CAAC,MAAM,CAAC,EAAE,wBAAwB,kDAIjC,MAAM,eACD,WAAW,cACZ,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,QAAQ,MAAM,CAAC,CAAC,oCAAxC,IAAI,WAAW,IAAI,KAAK,QAAQ,MAAM,CAAC;CAevF"}
|
package/dist/index.js
CHANGED
|
@@ -81,9 +81,12 @@ class TypeOrmTransactionHandler {
|
|
|
81
81
|
this.#createdDataSource = undefined;
|
|
82
82
|
await ds?.destroy();
|
|
83
83
|
}
|
|
84
|
-
async deleteCheckpoints(workflowID, startStep) {
|
|
85
|
-
await this.dataSource.
|
|
86
|
-
|
|
84
|
+
async deleteCheckpoints(workflowID, startStep, beforeCommit) {
|
|
85
|
+
await this.dataSource.transaction(async (entityManager) => {
|
|
86
|
+
await entityManager.query(`DELETE FROM "${this.schemaName}".transaction_completion
|
|
87
|
+
WHERE workflow_id=$1 AND function_num>=$2;`, [workflowID, startStep]);
|
|
88
|
+
await beforeCommit?.();
|
|
89
|
+
});
|
|
87
90
|
}
|
|
88
91
|
async #checkExecution(workflowID, stepID) {
|
|
89
92
|
const rows = await this.dataSource.query(`SELECT output, error FROM "${this.schemaName}".transaction_completion
|
|
@@ -102,6 +105,8 @@ class TypeOrmTransactionHandler {
|
|
|
102
105
|
if (rows.length === 0) {
|
|
103
106
|
throw new datasource_1.DBOSStepAlreadyRecordedError(workflowID, stepID);
|
|
104
107
|
}
|
|
108
|
+
// Holding this step's row, so a later owner's insert waits on our commit.
|
|
109
|
+
await (0, datasource_1.assertStillOwnsWorkflow)(workflowID);
|
|
105
110
|
}
|
|
106
111
|
async #recordError(workflowID, stepID, error) {
|
|
107
112
|
const rows = await this.dataSource.query(`INSERT INTO "${this.schemaName}".transaction_completion (workflow_id, function_num, error)
|
|
@@ -160,6 +165,9 @@ class TypeOrmTransactionHandler {
|
|
|
160
165
|
if (saveResults && error instanceof datasource_1.DBOSStepAlreadyRecordedError) {
|
|
161
166
|
return await this.#replayConflictingStep(workflowID, stepID);
|
|
162
167
|
}
|
|
168
|
+
// The new owner wins; recording an error here would replay it as this step's outcome.
|
|
169
|
+
if (error instanceof datasource_1.DBOSWorkflowConflictError)
|
|
170
|
+
throw error;
|
|
163
171
|
if ((0, datasource_1.isPGRetriableTransactionError)(error)) {
|
|
164
172
|
dbos_sdk_1.DBOS.span?.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMS }, performance.now());
|
|
165
173
|
// Retry serialization failures.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AACA,iDAAwD;AACxD,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
isPGRetriableTransactionError,
|
|
8
8
|
DBOSError,
|
|
9
9
|
DBOSStepAlreadyRecordedError,
|
|
10
|
+
DBOSWorkflowConflictError,
|
|
11
|
+
assertStillOwnsWorkflow,
|
|
10
12
|
replayRecordedStep,
|
|
11
13
|
registerTransaction,
|
|
12
14
|
runTransaction,
|
|
@@ -124,12 +126,15 @@ class TypeOrmTransactionHandler implements DataSourceTransactionHandler {
|
|
|
124
126
|
await ds?.destroy();
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
async deleteCheckpoints(workflowID: string, startStep: number): Promise<void> {
|
|
128
|
-
await this.dataSource.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
async deleteCheckpoints(workflowID: string, startStep: number, beforeCommit?: () => Promise<void>): Promise<void> {
|
|
130
|
+
await this.dataSource.transaction(async (entityManager: EntityManager) => {
|
|
131
|
+
await entityManager.query(
|
|
132
|
+
`DELETE FROM "${this.schemaName}".transaction_completion
|
|
133
|
+
WHERE workflow_id=$1 AND function_num>=$2;`,
|
|
134
|
+
[workflowID, startStep],
|
|
135
|
+
);
|
|
136
|
+
await beforeCommit?.();
|
|
137
|
+
});
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
async #checkExecution(
|
|
@@ -168,6 +173,8 @@ class TypeOrmTransactionHandler implements DataSourceTransactionHandler {
|
|
|
168
173
|
if (rows.length === 0) {
|
|
169
174
|
throw new DBOSStepAlreadyRecordedError(workflowID, stepID);
|
|
170
175
|
}
|
|
176
|
+
// Holding this step's row, so a later owner's insert waits on our commit.
|
|
177
|
+
await assertStillOwnsWorkflow(workflowID);
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
async #recordError(workflowID: string, stepID: number, error: string): Promise<void> {
|
|
@@ -252,6 +259,8 @@ class TypeOrmTransactionHandler implements DataSourceTransactionHandler {
|
|
|
252
259
|
if (saveResults && error instanceof DBOSStepAlreadyRecordedError) {
|
|
253
260
|
return await this.#replayConflictingStep<Return>(workflowID, stepID!);
|
|
254
261
|
}
|
|
262
|
+
// The new owner wins; recording an error here would replay it as this step's outcome.
|
|
263
|
+
if (error instanceof DBOSWorkflowConflictError) throw error;
|
|
255
264
|
if (isPGRetriableTransactionError(error)) {
|
|
256
265
|
DBOS.span?.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMS }, performance.now());
|
|
257
266
|
// Retry serialization failures.
|
package/package.json
CHANGED
package/tests/datasource.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
1
|
+
import { DBOS, DBOSWorkflowConflictError } from '@dbos-inc/dbos-sdk';
|
|
2
2
|
import { Client, Pool } from 'pg';
|
|
3
3
|
import { TypeOrmDataSource } from '..';
|
|
4
4
|
import { dropDB, ensureDB } from './test-helpers';
|
|
@@ -60,11 +60,8 @@ describe('TypeOrmDataSource', () => {
|
|
|
60
60
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
61
61
|
[workflowID],
|
|
62
62
|
);
|
|
63
|
-
|
|
64
|
-
expect(rows
|
|
65
|
-
expect(rows[0].function_num).toBe(0);
|
|
66
|
-
expect(rows[0].output).not.toBeNull();
|
|
67
|
-
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
63
|
+
// Completion cleared the checkpoint the transaction wrote.
|
|
64
|
+
expect(rows).toHaveLength(0);
|
|
68
65
|
});
|
|
69
66
|
|
|
70
67
|
test('rerun insert dataSource.register function', async () => {
|
|
@@ -94,11 +91,8 @@ describe('TypeOrmDataSource', () => {
|
|
|
94
91
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
95
92
|
[workflowID],
|
|
96
93
|
);
|
|
97
|
-
|
|
98
|
-
expect(rows
|
|
99
|
-
expect(rows[0].function_num).toBe(0);
|
|
100
|
-
expect(rows[0].output).not.toBeNull();
|
|
101
|
-
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
94
|
+
// Completion cleared the checkpoint the transaction wrote.
|
|
95
|
+
expect(rows).toHaveLength(0);
|
|
102
96
|
});
|
|
103
97
|
|
|
104
98
|
test('rerun insert dataSource.runAsTx function', async () => {
|
|
@@ -143,14 +137,8 @@ describe('TypeOrmDataSource', () => {
|
|
|
143
137
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
144
138
|
[workflowID],
|
|
145
139
|
);
|
|
146
|
-
|
|
147
|
-
expect(txOutput
|
|
148
|
-
expect(txOutput[0].function_num).toBe(0);
|
|
149
|
-
expect(txOutput[0].output).toBeNull();
|
|
150
|
-
expect(txOutput[0].error).not.toBeNull();
|
|
151
|
-
const $error = SuperJSON.parse(txOutput[0].error!);
|
|
152
|
-
expect($error).toBeInstanceOf(Error);
|
|
153
|
-
expect(($error as Error).message).toMatch(/^test error \d+$/);
|
|
140
|
+
// Completion cleared the checkpoint the transaction wrote.
|
|
141
|
+
expect(txOutput).toHaveLength(0);
|
|
154
142
|
});
|
|
155
143
|
|
|
156
144
|
test('rerun error dataSource.register function', async () => {
|
|
@@ -188,14 +176,8 @@ describe('TypeOrmDataSource', () => {
|
|
|
188
176
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
189
177
|
[workflowID],
|
|
190
178
|
);
|
|
191
|
-
|
|
192
|
-
expect(txOutput
|
|
193
|
-
expect(txOutput[0].function_num).toBe(0);
|
|
194
|
-
expect(txOutput[0].output).toBeNull();
|
|
195
|
-
expect(txOutput[0].error).not.toBeNull();
|
|
196
|
-
const $error = SuperJSON.parse(txOutput[0].error!);
|
|
197
|
-
expect($error).toBeInstanceOf(Error);
|
|
198
|
-
expect(($error as Error).message).toMatch(/^test error \d+$/);
|
|
179
|
+
// Completion cleared the checkpoint the transaction wrote.
|
|
180
|
+
expect(txOutput).toHaveLength(0);
|
|
199
181
|
});
|
|
200
182
|
|
|
201
183
|
test('rerun error dataSource.runAsTx function', async () => {
|
|
@@ -338,25 +320,68 @@ describe('TypeOrmDataSource', () => {
|
|
|
338
320
|
expect((winnerError as Error).message).toBe('winner-error');
|
|
339
321
|
expect(raceState.callCount).toBe(3);
|
|
340
322
|
|
|
341
|
-
// Every loser's writes were discarded and the winners'
|
|
323
|
+
// Every loser's writes were discarded, and completion cleared the winners' checkpoints.
|
|
342
324
|
const { rows: tags } = await userDB.query<{ tag: string }>('SELECT tag FROM race_side_effects');
|
|
343
325
|
expect(tags).toHaveLength(0);
|
|
344
326
|
const { rows: txOutput } = await userDB.query<transaction_completion>(
|
|
345
327
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = ANY($1)',
|
|
346
|
-
[[wfid1, wfid2]],
|
|
328
|
+
[[wfid1, wfid2, wfid3]],
|
|
347
329
|
);
|
|
348
|
-
expect(txOutput).toHaveLength(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
330
|
+
expect(txOutput).toHaveLength(0);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/** Completion clears the checkpoints, so the workflow reads them back while it still runs. */
|
|
334
|
+
test('transactions record their outcomes as checkpoints', async () => {
|
|
335
|
+
const user = 'checkpointProbe';
|
|
336
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
337
|
+
const workflowID = randomUUID();
|
|
338
|
+
const message = await DBOS.withNextWorkflowID(workflowID, () => regCheckpointProbeWorkflow(user));
|
|
339
|
+
expect(message).toMatch(/^test error \d+$/);
|
|
340
|
+
|
|
341
|
+
expect(probeState.rows).toHaveLength(2);
|
|
342
|
+
const [output, error] = probeState.rows;
|
|
343
|
+
expect(output).toMatchObject({ workflow_id: workflowID, function_num: 0, error: null });
|
|
344
|
+
expect(SuperJSON.parse(output.output!)).toMatchObject({ user, greet_count: 1 });
|
|
345
|
+
expect(error).toMatchObject({ workflow_id: workflowID, function_num: 1, output: null });
|
|
346
|
+
expect(SuperJSON.parse<Error>(error.error!).message).toBe(message);
|
|
347
|
+
expect(await readCompletions(workflowID)).toHaveLength(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
/** The checkpoint may be the new owner's only record of the transaction, so a stale execution's cleanup rolls back. */
|
|
351
|
+
test('a stale execution keeps the checkpoints at completion', async () => {
|
|
352
|
+
const user = 'handOffTest';
|
|
353
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
354
|
+
const handedOff = new Promise<void>((resolve) => (handOffState.signal = resolve));
|
|
355
|
+
const workflowID = randomUUID();
|
|
356
|
+
const execution = DBOS.withNextWorkflowID(workflowID, () => regHandOffWorkflow(user));
|
|
357
|
+
// Surfaces the workflow's own error if it fails before handing off.
|
|
358
|
+
await Promise.race([handedOff, execution]);
|
|
359
|
+
|
|
360
|
+
// Its outcome is refused too, so the execution parks until the workflow is cancelled.
|
|
361
|
+
await DBOS.cancelWorkflow(workflowID);
|
|
362
|
+
await expect(execution).rejects.toThrow(/has been cancelled/);
|
|
363
|
+
expect(await readCompletions(workflowID)).toHaveLength(1);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
/** A stale execution cannot apply a step the new owner also runs, nor leave an outcome for it. */
|
|
367
|
+
test('an execution that loses the workflow mid-transaction rolls back', async () => {
|
|
368
|
+
await userDB.query('DELETE FROM race_side_effects');
|
|
369
|
+
const workflowID = randomUUID();
|
|
370
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regStaleWorkflow())).resolves.toBe('conflicted');
|
|
371
|
+
|
|
372
|
+
const { rows: tags } = await userDB.query<{ tag: string }>('SELECT tag FROM race_side_effects');
|
|
373
|
+
expect(tags).toHaveLength(0);
|
|
374
|
+
expect(staleState.completionsAtConflict).toBe(0);
|
|
375
|
+
const sysDB = new Client(sysConfig);
|
|
376
|
+
try {
|
|
377
|
+
await sysDB.connect();
|
|
378
|
+
const { rows: steps } = await sysDB.query('SELECT * FROM dbos.operation_outputs WHERE workflow_uuid = $1', [
|
|
379
|
+
workflowID,
|
|
380
|
+
]);
|
|
381
|
+
expect(steps).toHaveLength(0);
|
|
382
|
+
} finally {
|
|
383
|
+
await sysDB.end();
|
|
352
384
|
}
|
|
353
|
-
const { rows: txError } = await userDB.query<transaction_completion>(
|
|
354
|
-
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
355
|
-
[wfid3],
|
|
356
|
-
);
|
|
357
|
-
expect(txError).toHaveLength(1);
|
|
358
|
-
expect(txError[0].output).toBeNull(); // the loser never overwrote the winner's error with its own result
|
|
359
|
-
expect(SuperJSON.parse<Error>(txError[0].error!).message).toBe('winner-error');
|
|
360
385
|
});
|
|
361
386
|
});
|
|
362
387
|
|
|
@@ -462,6 +487,95 @@ async function readFunction(user: string) {
|
|
|
462
487
|
|
|
463
488
|
const regInsertFunction = dataSource.registerTransaction(insertFunction);
|
|
464
489
|
const regErrorFunction = dataSource.registerTransaction(errorFunction);
|
|
490
|
+
|
|
491
|
+
const sysConfig = { ...config, database: 'typeorm_ds_test_dbos_sys' };
|
|
492
|
+
|
|
493
|
+
/** Set the workflow's ownership token, returning the one it replaced. */
|
|
494
|
+
async function setOwner(workflowID: string, ownerXid: string | null): Promise<string | null> {
|
|
495
|
+
const sysDB = new Client(sysConfig);
|
|
496
|
+
try {
|
|
497
|
+
await sysDB.connect();
|
|
498
|
+
const { rows } = await sysDB.query<{ owner_xid: string | null }>(
|
|
499
|
+
'SELECT owner_xid FROM dbos.workflow_status WHERE workflow_uuid = $1',
|
|
500
|
+
[workflowID],
|
|
501
|
+
);
|
|
502
|
+
await sysDB.query('UPDATE dbos.workflow_status SET owner_xid = $2 WHERE workflow_uuid = $1', [
|
|
503
|
+
workflowID,
|
|
504
|
+
ownerXid,
|
|
505
|
+
]);
|
|
506
|
+
return rows[0].owner_xid;
|
|
507
|
+
} finally {
|
|
508
|
+
await sysDB.end();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** The checkpoints this data source holds for the workflow, read on a connection of their own. */
|
|
513
|
+
async function readCompletions(workflowID: string): Promise<transaction_completion[]> {
|
|
514
|
+
const client = new Client(config);
|
|
515
|
+
try {
|
|
516
|
+
await client.connect();
|
|
517
|
+
const { rows } = await client.query<transaction_completion>(
|
|
518
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1 ORDER BY function_num',
|
|
519
|
+
[workflowID],
|
|
520
|
+
);
|
|
521
|
+
return rows;
|
|
522
|
+
} finally {
|
|
523
|
+
await client.end();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const probeState = { rows: [] as transaction_completion[] };
|
|
528
|
+
|
|
529
|
+
// Reads back the checkpoints its transactions wrote, before completion clears them.
|
|
530
|
+
async function checkpointProbeWorkflow(user: string) {
|
|
531
|
+
await regInsertFunction(user);
|
|
532
|
+
const error = await regErrorFunction(user).then(
|
|
533
|
+
() => undefined,
|
|
534
|
+
(e: Error) => e,
|
|
535
|
+
);
|
|
536
|
+
probeState.rows = await readCompletions(DBOS.workflowID!);
|
|
537
|
+
return error?.message;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const regCheckpointProbeWorkflow = DBOS.registerWorkflow(checkpointProbeWorkflow);
|
|
541
|
+
|
|
542
|
+
const staleState = { originalOwner: null as string | null, completionsAtConflict: -1 };
|
|
543
|
+
|
|
544
|
+
// Another execution claims the workflow while this transaction is still open.
|
|
545
|
+
async function staleFunction() {
|
|
546
|
+
await dataSource.entityManager.sql`INSERT INTO race_side_effects(tag) VALUES ('stale')`;
|
|
547
|
+
staleState.originalOwner = await setOwner(DBOS.workflowID!, 'another-execution');
|
|
548
|
+
return 'stale';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const regStaleFunction = dataSource.registerTransaction(staleFunction);
|
|
552
|
+
|
|
553
|
+
async function staleWorkflow() {
|
|
554
|
+
try {
|
|
555
|
+
return await regStaleFunction();
|
|
556
|
+
} catch (e) {
|
|
557
|
+
if (!(e instanceof DBOSWorkflowConflictError)) throw e;
|
|
558
|
+
// Read before completion clears it: the conflict must not have been recorded as the step's outcome.
|
|
559
|
+
staleState.completionsAtConflict = (await readCompletions(DBOS.workflowID!)).length;
|
|
560
|
+
// A real duplicate parks here instead; reclaimed so this execution's outcome lands.
|
|
561
|
+
await setOwner(DBOS.workflowID!, staleState.originalOwner);
|
|
562
|
+
return 'conflicted';
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const regStaleWorkflow = DBOS.registerWorkflow(staleWorkflow);
|
|
567
|
+
|
|
568
|
+
const handOffState = { signal: () => {} };
|
|
569
|
+
|
|
570
|
+
// Loses the workflow after its transaction, so its completion is a stale execution's.
|
|
571
|
+
async function handOffWorkflow(user: string) {
|
|
572
|
+
const result = await regInsertFunction(user);
|
|
573
|
+
await setOwner(DBOS.workflowID!, 'another-execution');
|
|
574
|
+
handOffState.signal();
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const regHandOffWorkflow = DBOS.registerWorkflow(handOffWorkflow);
|
|
465
579
|
const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
|
|
466
580
|
|
|
467
581
|
class StaticClass {
|
|
@@ -578,10 +692,7 @@ describe('TypeOrmDataSourceCreateTxC', () => {
|
|
|
578
692
|
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
579
693
|
[workflowID],
|
|
580
694
|
);
|
|
581
|
-
|
|
582
|
-
expect(rows
|
|
583
|
-
expect(rows[0].function_num).toBe(0);
|
|
584
|
-
expect(rows[0].output).not.toBeNull();
|
|
585
|
-
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
695
|
+
// Completion cleared the checkpoint the transaction wrote.
|
|
696
|
+
expect(rows).toHaveLength(0);
|
|
586
697
|
});
|
|
587
698
|
});
|