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:
1 parent
b111d01898
commit
9412669e65
38 files changed
+890
-64
No files matched your search
@@ -114,7 +114,10 @@ export const addToCartWorkflowId = "add-to-cart"
|
||||
* :::
|
||||
*/
|
||||
export const addToCartWorkflow = createWorkflow(
|
||||
addToCartWorkflowId,
|
||||
{
|
||||
name: addToCartWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<AddToCartWorkflowInputDTO & AdditionalData>) => {
|
||||
const cartQuery = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
|
||||
@@ -79,7 +79,10 @@ export const createPaymentCollectionForCartWorkflowId =
|
||||
* Create payment collection for cart.
|
||||
*/
|
||||
export const createPaymentCollectionForCartWorkflow = createWorkflow(
|
||||
createPaymentCollectionForCartWorkflowId,
|
||||
{
|
||||
name: createPaymentCollectionForCartWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(
|
||||
input: WorkflowData<CreatePaymentCollectionForCartWorkflowInputDTO>
|
||||
): WorkflowData<void> => {
|
||||
|
||||
@@ -128,7 +128,10 @@ export const refreshCartItemsWorkflowId = "refresh-cart-items"
|
||||
*
|
||||
*/
|
||||
export const refreshCartItemsWorkflow = createWorkflow(
|
||||
refreshCartItemsWorkflowId,
|
||||
{
|
||||
name: refreshCartItemsWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<RefreshCartItemsWorkflowInput & AdditionalData>) => {
|
||||
const setPricingContext = createHook(
|
||||
"setPricingContext",
|
||||
|
||||
@@ -50,7 +50,10 @@ export const refreshCartShippingMethodsWorkflowId =
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const refreshCartShippingMethodsWorkflow = createWorkflow(
|
||||
refreshCartShippingMethodsWorkflowId,
|
||||
{
|
||||
name: refreshCartShippingMethodsWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<RefreshCartShippingMethodsWorkflowInput>) => {
|
||||
const fetchCart = when("fetch-cart", { input }, ({ input }) => {
|
||||
return !input.cart
|
||||
|
||||
@@ -54,7 +54,10 @@ export const refreshPaymentCollectionForCartWorkflowId =
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
|
||||
refreshPaymentCollectionForCartWorkflowId,
|
||||
{
|
||||
name: refreshPaymentCollectionForCartWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<RefreshPaymentCollectionForCartWorklowInput>) => {
|
||||
const fetchCart = when("should-fetch-cart", { input }, ({ input }) => {
|
||||
return !input.cart
|
||||
|
||||
+4
-1
@@ -62,7 +62,10 @@ export const refundPaymentAndRecreatePaymentSessionWorkflowId =
|
||||
* Refund a payment and create a new payment session.
|
||||
*/
|
||||
export const refundPaymentAndRecreatePaymentSessionWorkflow = createWorkflow(
|
||||
refundPaymentAndRecreatePaymentSessionWorkflowId,
|
||||
{
|
||||
name: refundPaymentAndRecreatePaymentSessionWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(
|
||||
input: WorkflowData<refundPaymentAndRecreatePaymentSessionWorkflowInput>
|
||||
): WorkflowResponse<PaymentSessionDTO> => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CartWorkflowEvents } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createHook,
|
||||
createWorkflow,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
import { emitEventStep, useQueryGraphStep } from "../../common"
|
||||
import { updateCartsStep } from "../steps"
|
||||
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
||||
import { CartWorkflowEvents } from "@medusajs/framework/utils"
|
||||
|
||||
/**
|
||||
* The cart ownership transfer details.
|
||||
@@ -49,7 +49,10 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer"
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const transferCartCustomerWorkflow = createWorkflow(
|
||||
transferCartCustomerWorkflowId,
|
||||
{
|
||||
name: transferCartCustomerWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<TransferCartCustomerWorkflowInput>) => {
|
||||
const cartQuery = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
|
||||
@@ -73,7 +73,10 @@ export const updateCartPromotionsWorkflowId = "update-cart-promotions"
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const updateCartPromotionsWorkflow = createWorkflow(
|
||||
updateCartPromotionsWorkflowId,
|
||||
{
|
||||
name: updateCartPromotionsWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<UpdateCartPromotionsWorkflowInput>) => {
|
||||
const fetchCart = when("should-fetch-cart", { input }, ({ input }) => {
|
||||
return !input.cart
|
||||
|
||||
@@ -78,7 +78,10 @@ export const updateCartWorkflowId = "update-cart"
|
||||
* @property hooks.cartUpdated - This hook is executed after a cart is update. You can consume this hook to perform custom actions on the updated cart.
|
||||
*/
|
||||
export const updateCartWorkflow = createWorkflow(
|
||||
updateCartWorkflowId,
|
||||
{
|
||||
name: updateCartWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<UpdateCartWorkflowInput>) => {
|
||||
const { data: cartToUpdate } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
|
||||
@@ -104,7 +104,10 @@ export const updateLineItemInCartWorkflowId = "update-line-item-in-cart"
|
||||
* :::
|
||||
*/
|
||||
export const updateLineItemInCartWorkflow = createWorkflow(
|
||||
updateLineItemInCartWorkflowId,
|
||||
{
|
||||
name: updateLineItemInCartWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(
|
||||
input: WorkflowData<UpdateLineItemInCartWorkflowInputDTO & AdditionalData>
|
||||
) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { deleteLineItemsStep } from "../steps/delete-line-items"
|
||||
/**
|
||||
* The data to delete line items from a cart.
|
||||
*/
|
||||
export type DeleteLineItemsWorkflowInput = {
|
||||
export type DeleteLineItemsWorkflowInput = {
|
||||
/**
|
||||
* The cart's ID.
|
||||
*/
|
||||
@@ -20,10 +20,10 @@ export const deleteLineItemsWorkflowId = "delete-line-items"
|
||||
/**
|
||||
* This workflow deletes line items from a cart. It's used by the
|
||||
* [Delete Line Item Store API Route](https://docs.medusajs.com/api/store#carts_deletecartsidlineitemsline_id).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to
|
||||
* delete line items from a cart within your custom flows.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await deleteLineItemsWorkflow(container)
|
||||
* .run({
|
||||
@@ -32,13 +32,16 @@ export const deleteLineItemsWorkflowId = "delete-line-items"
|
||||
* ids: ["li_123"]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Delete line items from a cart.
|
||||
*/
|
||||
export const deleteLineItemsWorkflow = createWorkflow(
|
||||
deleteLineItemsWorkflowId,
|
||||
{
|
||||
name: deleteLineItemsWorkflowId,
|
||||
idempotent: true,
|
||||
},
|
||||
(input: WorkflowData<DeleteLineItemsWorkflowInput>) => {
|
||||
deleteLineItemsStep(input.ids)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
TransactionStepTimeoutError,
|
||||
TransactionTimeoutError,
|
||||
} from "./errors"
|
||||
import { Context } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* @class TransactionOrchestrator is responsible for managing and executing distributed transactions.
|
||||
@@ -1380,7 +1381,8 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
private createTransactionFlow(
|
||||
transactionId: string,
|
||||
flowMetadata?: TransactionFlow["metadata"]
|
||||
flowMetadata?: TransactionFlow["metadata"],
|
||||
context?: Context
|
||||
): TransactionFlow {
|
||||
const [steps, features] = TransactionOrchestrator.buildSteps(
|
||||
this.definition
|
||||
@@ -1390,7 +1392,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
modelId: this.id,
|
||||
options: this.options,
|
||||
transactionId: transactionId,
|
||||
runId: ulid(),
|
||||
runId: context?.runId ?? ulid(),
|
||||
metadata: flowMetadata,
|
||||
hasAsyncSteps: features.hasAsyncSteps,
|
||||
hasFailedSteps: false,
|
||||
@@ -1541,12 +1543,14 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
handler,
|
||||
payload,
|
||||
flowMetadata,
|
||||
context,
|
||||
onLoad,
|
||||
}: {
|
||||
transactionId: string
|
||||
handler: TransactionStepHandler
|
||||
payload?: unknown
|
||||
flowMetadata?: TransactionFlow["metadata"]
|
||||
context?: Context
|
||||
onLoad?: (transaction: DistributedTransactionType) => Promise<void> | void
|
||||
}): Promise<DistributedTransactionType> {
|
||||
const existingTransaction =
|
||||
@@ -1555,7 +1559,11 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
let newTransaction = false
|
||||
let modelFlow: TransactionFlow
|
||||
if (!existingTransaction) {
|
||||
modelFlow = this.createTransactionFlow(transactionId, flowMetadata)
|
||||
modelFlow = this.createTransactionFlow(
|
||||
transactionId,
|
||||
flowMetadata,
|
||||
context
|
||||
)
|
||||
newTransaction = true
|
||||
} else {
|
||||
modelFlow = existingTransaction.flow
|
||||
|
||||
@@ -362,6 +362,7 @@ export class LocalWorkflow {
|
||||
handler: handler(this.container_, context),
|
||||
payload: input,
|
||||
flowMetadata,
|
||||
context,
|
||||
onLoad: this.onLoad.bind(this),
|
||||
})
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ export type Context<TManager = unknown> = {
|
||||
*/
|
||||
transactionId?: string
|
||||
|
||||
/**
|
||||
* A string indicating the ID of the current run.
|
||||
*/
|
||||
runId?: string
|
||||
|
||||
/**
|
||||
* An instance of a message aggregator, which is used to aggregate messages to be emitted at a later point.
|
||||
*/
|
||||
|
||||
@@ -211,6 +211,7 @@ export function createWorkflow<TData, TResult, THooks extends any[]>(
|
||||
step.__step__ + "-" + (stepContext.transactionId ?? ulid()),
|
||||
parentStepIdempotencyKey: stepContext.idempotencyKey,
|
||||
preventReleaseEvents: true,
|
||||
runId: stepContext.runId,
|
||||
}
|
||||
|
||||
let transaction
|
||||
|
||||
@@ -38,6 +38,7 @@ function buildStepContext({
|
||||
parentStepIdempotencyKey: flowMetadata?.parentStepIdempotencyKey as string,
|
||||
preventReleaseEvents: flowMetadata?.preventReleaseEvents ?? false,
|
||||
transactionId: stepArguments.context!.transactionId,
|
||||
runId: flow.runId,
|
||||
context: stepArguments.context!,
|
||||
" stepDefinition": stepDefinition,
|
||||
" getStepResult"(
|
||||
|
||||
@@ -192,6 +192,11 @@ export interface StepExecutionContext {
|
||||
*/
|
||||
transactionId?: string
|
||||
|
||||
/**
|
||||
* A string indicating the ID of the current run.
|
||||
*/
|
||||
runId?: string
|
||||
|
||||
/**
|
||||
* Get access to the result returned by a named step. Returns undefined
|
||||
* when step is not found or when nothing was returned.
|
||||
|
||||
Reference in new issue
Block a user