chore: idempotent cart operations (#13236)

* chore(core-flows): idempotent cart operations

* changeset

* add tests

* revert

* revert route

* promo test

* skip bugs

* fix test

* tests

* avoid workflow name conflict

* prevent nested workflow from being deleted until the top level parent finishes

* remove unused setTimeout

* update changeset

* rm comments

---------

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
This commit is contained in:
Carlos R. L. Rodrigues
2025-08-28 10:04:00 -03:00
committed by GitHub
parent b111d01898
commit 9412669e65
38 changed files with 890 additions and 64 deletions

View File

@@ -177,6 +177,15 @@
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_workflow_execution_state\" ON \"workflow_execution\" (state) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_workflow_execution_run_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_workflow_execution_run_id\" ON \"workflow_execution\" (run_id) WHERE deleted_at IS NULL"
},
{
"keyName": "workflow_execution_pkey",
"columnNames": [

View File

@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20250819110923 extends Migration {
override async up(): Promise<void> {
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_workflow_execution_run_id" ON "workflow_execution" (run_id) WHERE deleted_at IS NULL;`);
}
override async down(): Promise<void> {
this.addSql(`drop index if exists "IDX_workflow_execution_run_id";`);
}
}

View File

@@ -34,4 +34,8 @@ export const WorkflowExecution = model
on: ["state"],
where: "deleted_at IS NULL",
},
{
on: ["run_id"],
where: "deleted_at IS NULL",
},
])

View File

@@ -220,8 +220,6 @@ export class RedisDistributedTransactionStorage
private async deleteFromDb(data: TransactionCheckpoint) {
await this.workflowExecutionService_.delete([
{
workflow_id: data.flow.modelId,
transaction_id: data.flow.transactionId,
run_id: data.flow.runId,
},
])
@@ -351,7 +349,7 @@ export class RedisDistributedTransactionStorage
TransactionState.REVERTED,
].includes(data.flow.state)
const { retentionTime, idempotent } = options ?? {}
const { retentionTime } = options ?? {}
await this.#preventRaceConditionExecutionIfNecessary({
data,
@@ -416,8 +414,13 @@ export class RedisDistributedTransactionStorage
})
// Database operations
if (hasFinished && !retentionTime && !idempotent) {
await promiseAll([pipelinePromise, this.deleteFromDb(data)])
if (hasFinished && !retentionTime) {
// If the workflow is nested, we cant just remove it because it would break the compensation algorithm. Instead, it will get deleted when the top level parent is deleted.
if (!data.flow.metadata?.parentStepIdempotencyKey) {
await promiseAll([pipelinePromise, this.deleteFromDb(data)])
} else {
await promiseAll([pipelinePromise, this.saveToDb(data, retentionTime)])
}
} else {
await promiseAll([pipelinePromise, this.saveToDb(data, retentionTime)])
}