1 parent
ae33f4825f
commit
f12299deb1
51 files changed
+1356
-329
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/orchestration": minor
|
||||||
|
"@medusajs/modules-sdk": minor
|
||||||
|
"@medusajs/workflows": minor
|
||||||
|
"@medusajs/medusa": minor
|
||||||
|
"@medusajs/types": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Medusa workflows package
|
||||||
@@ -95,7 +95,7 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"option_id": "test-option",
|
"option_id": "test-option",
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"value": "Default variant",
|
"value": Any<String>,
|
||||||
"variant_id": "test-variant",
|
"variant_id": "test-variant",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -187,7 +187,7 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"option_id": Any<String>,
|
"option_id": Any<String>,
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"value": "Practical",
|
"value": Any<String>,
|
||||||
"variant_id": Any<String>,
|
"variant_id": Any<String>,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ describe("/store/variants", () => {
|
|||||||
{
|
{
|
||||||
created_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
|
value: expect.any(String),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
prices: [
|
prices: [
|
||||||
@@ -361,6 +362,7 @@ describe("/store/variants", () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
option_id: expect.any(String),
|
option_id: expect.any(String),
|
||||||
variant_id: expect.any(String),
|
variant_id: expect.any(String),
|
||||||
|
value: expect.any(String),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
prices: [
|
prices: [
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import {
|
|||||||
ShippingProfileType,
|
ShippingProfileType,
|
||||||
Store,
|
Store,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import faker from "faker"
|
|
||||||
import { DataSource } from "typeorm"
|
|
||||||
import {
|
import {
|
||||||
ProductVariantFactoryData,
|
ProductVariantFactoryData,
|
||||||
simpleProductVariantFactory,
|
simpleProductVariantFactory,
|
||||||
@@ -18,6 +16,9 @@ import {
|
|||||||
simpleSalesChannelFactory,
|
simpleSalesChannelFactory,
|
||||||
} from "./simple-sales-channel-factory"
|
} from "./simple-sales-channel-factory"
|
||||||
|
|
||||||
|
import { DataSource } from "typeorm"
|
||||||
|
import faker from "faker"
|
||||||
|
|
||||||
export type ProductFactoryData = {
|
export type ProductFactoryData = {
|
||||||
id?: string
|
id?: string
|
||||||
is_giftcard?: boolean
|
is_giftcard?: boolean
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa-cli": "^1.3.17",
|
"@medusajs/medusa-cli": "^1.3.17",
|
||||||
"@medusajs/modules-sdk": "^1.8.8",
|
"@medusajs/modules-sdk": "^1.8.8",
|
||||||
|
"@medusajs/orchestration": "^0.0.2",
|
||||||
"@medusajs/utils": "^1.9.2",
|
"@medusajs/utils": "^1.9.2",
|
||||||
"awilix": "^8.0.0",
|
"awilix": "^8.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
|||||||
TransactionPayload,
|
TransactionPayload,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
TransactionStepsDefinition,
|
TransactionStepsDefinition,
|
||||||
} from "../../../../../utils/transaction"
|
} from "@medusajs/orchestration"
|
||||||
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
CreateProductVariantInput,
|
||||||
|
ProductVariantPricesCreateReq,
|
||||||
|
} from "../../../../types/product-variant"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
@@ -8,7 +12,6 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
|
||||||
import {
|
import {
|
||||||
PricingService,
|
PricingService,
|
||||||
ProductService,
|
ProductService,
|
||||||
@@ -23,26 +26,23 @@ import {
|
|||||||
ProductTagReq,
|
ProductTagReq,
|
||||||
ProductTypeReq,
|
ProductTypeReq,
|
||||||
} from "../../../../types/product"
|
} from "../../../../types/product"
|
||||||
import {
|
|
||||||
CreateProductVariantInput,
|
|
||||||
ProductVariantPricesCreateReq,
|
|
||||||
} from "../../../../types/product-variant"
|
|
||||||
|
|
||||||
import { IInventoryService } from "@medusajs/types"
|
|
||||||
import { Type } from "class-transformer"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
|
||||||
import { ProductStatus } from "../../../../models"
|
|
||||||
import { Logger } from "../../../../types/global"
|
|
||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
|
||||||
import { DistributedTransaction } from "../../../../utils/transaction"
|
|
||||||
import { validator } from "../../../../utils/validator"
|
|
||||||
import {
|
import {
|
||||||
createVariantsTransaction,
|
createVariantsTransaction,
|
||||||
revertVariantTransaction,
|
revertVariantTransaction,
|
||||||
} from "./transaction/create-product-variant"
|
} from "./transaction/create-product-variant"
|
||||||
|
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||||
|
|
||||||
|
import { DistributedTransaction } from "@medusajs/orchestration"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
|
import { IInventoryService } from "@medusajs/types"
|
||||||
|
import { Logger } from "../../../../types/global"
|
||||||
|
import { ProductStatus } from "../../../../models"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
import { createProductsWorkflow } from "../../../../workflows/admin/create-products"
|
import { createProductsWorkflow } from "../../../../workflows/admin/create-products"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /admin/products
|
* @oas [post] /admin/products
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
TransactionPayload,
|
TransactionPayload,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
TransactionStepsDefinition,
|
TransactionStepsDefinition,
|
||||||
} from "../../../../../utils/transaction"
|
} from "@medusajs/orchestration"
|
||||||
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { IInventoryService } from "@medusajs/types"
|
import {
|
||||||
import { MedusaError } from "@medusajs/utils"
|
CreateProductVariantInput,
|
||||||
import { Type } from "class-transformer"
|
ProductVariantPricesUpdateReq,
|
||||||
|
UpdateProductVariantInput,
|
||||||
|
} from "../../../../types/product-variant"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
@@ -14,36 +16,35 @@ import {
|
|||||||
ValidateIf,
|
ValidateIf,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
|
||||||
import { ProductStatus, ProductVariant } from "../../../../models"
|
|
||||||
import { ProductVariantRepository } from "../../../../repositories/product-variant"
|
|
||||||
import {
|
import {
|
||||||
PricingService,
|
PricingService,
|
||||||
ProductService,
|
ProductService,
|
||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
ProductVariantService,
|
ProductVariantService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import { Logger } from "../../../../types/global"
|
|
||||||
import {
|
import {
|
||||||
ProductProductCategoryReq,
|
ProductProductCategoryReq,
|
||||||
ProductSalesChannelReq,
|
ProductSalesChannelReq,
|
||||||
ProductTagReq,
|
ProductTagReq,
|
||||||
ProductTypeReq,
|
ProductTypeReq,
|
||||||
} from "../../../../types/product"
|
} from "../../../../types/product"
|
||||||
import {
|
import { ProductStatus, ProductVariant } from "../../../../models"
|
||||||
CreateProductVariantInput,
|
|
||||||
ProductVariantPricesUpdateReq,
|
|
||||||
UpdateProductVariantInput,
|
|
||||||
} from "../../../../types/product-variant"
|
|
||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
|
||||||
import { DistributedTransaction } from "../../../../utils/transaction"
|
|
||||||
import { validator } from "../../../../utils/validator"
|
|
||||||
import {
|
import {
|
||||||
createVariantsTransaction,
|
createVariantsTransaction,
|
||||||
revertVariantTransaction,
|
revertVariantTransaction,
|
||||||
} from "./transaction/create-product-variant"
|
} from "./transaction/create-product-variant"
|
||||||
|
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||||
|
|
||||||
|
import { DistributedTransaction } from "@medusajs/orchestration"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
|
import { IInventoryService } from "@medusajs/types"
|
||||||
|
import { Logger } from "../../../../types/global"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
import { ProductVariantRepository } from "../../../../repositories/product-variant"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /admin/products/{id}
|
* @oas [post] /admin/products/{id}
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
import { MedusaContainer } from "@medusajs/types"
|
|
||||||
import {
|
|
||||||
DistributedTransaction,
|
|
||||||
TransactionMetadata,
|
|
||||||
} from "./distributed-transaction"
|
|
||||||
import { TransactionOrchestrator } from "./transaction-orchestrator"
|
|
||||||
import { TransactionStepHandler } from "./transaction-step"
|
|
||||||
import { TransactionHandlerType, TransactionStepsDefinition } from "./types"
|
|
||||||
import { OrchestratorBuilder } from "./orchestrator-builder"
|
|
||||||
|
|
||||||
interface Workflow {
|
|
||||||
id: string
|
|
||||||
handler: (container: MedusaContainer) => TransactionStepHandler
|
|
||||||
orchestrator: TransactionOrchestrator
|
|
||||||
flow_: TransactionStepsDefinition
|
|
||||||
handlers_: Map<
|
|
||||||
string,
|
|
||||||
{ invoke: InvokeHandler; compensate?: CompensateHandler }
|
|
||||||
>
|
|
||||||
requiredModules?: Set<string>
|
|
||||||
optionalModules?: Set<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
type InvokeHandler = (
|
|
||||||
container: MedusaContainer,
|
|
||||||
payload: any,
|
|
||||||
invoke: { [actions: string]: any },
|
|
||||||
metadata: TransactionMetadata
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
type CompensateHandler = (
|
|
||||||
container: MedusaContainer,
|
|
||||||
payload: any,
|
|
||||||
invoke: { [actions: string]: any },
|
|
||||||
compensate: { [actions: string]: any },
|
|
||||||
metadata: TransactionMetadata
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
export class WorkflowManager {
|
|
||||||
protected static workflows: Map<string, Workflow> = new Map()
|
|
||||||
protected container: MedusaContainer
|
|
||||||
|
|
||||||
constructor(container?: MedusaContainer) {
|
|
||||||
this.container = container as MedusaContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
static unregister(workflowId: string) {
|
|
||||||
WorkflowManager.workflows.delete(workflowId)
|
|
||||||
}
|
|
||||||
|
|
||||||
static unregisterAll() {
|
|
||||||
WorkflowManager.workflows.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
static getWorkflows() {
|
|
||||||
return WorkflowManager.workflows
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTransactionDefinition(workflowId): OrchestratorBuilder {
|
|
||||||
if (!WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflow = WorkflowManager.workflows.get(workflowId)!
|
|
||||||
return new OrchestratorBuilder(workflow.flow_)
|
|
||||||
}
|
|
||||||
|
|
||||||
static register(
|
|
||||||
workflowId: string,
|
|
||||||
flow: TransactionStepsDefinition | OrchestratorBuilder,
|
|
||||||
handlers: Map<
|
|
||||||
string,
|
|
||||||
{ invoke: InvokeHandler; compensate?: CompensateHandler }
|
|
||||||
>,
|
|
||||||
requiredModules?: Set<string>,
|
|
||||||
optionalModules?: Set<string>
|
|
||||||
) {
|
|
||||||
if (WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" is already defined.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
|
|
||||||
|
|
||||||
WorkflowManager.workflows.set(workflowId, {
|
|
||||||
id: workflowId,
|
|
||||||
flow_: finalFlow,
|
|
||||||
orchestrator: new TransactionOrchestrator(workflowId, finalFlow),
|
|
||||||
handler: WorkflowManager.buildHandlers(handlers),
|
|
||||||
handlers_: handlers,
|
|
||||||
requiredModules,
|
|
||||||
optionalModules,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static update(
|
|
||||||
workflowId: string,
|
|
||||||
flow: TransactionStepsDefinition | OrchestratorBuilder,
|
|
||||||
handlers: Map<
|
|
||||||
string,
|
|
||||||
{ invoke: InvokeHandler; compensate?: CompensateHandler }
|
|
||||||
>,
|
|
||||||
requiredModules?: Set<string>,
|
|
||||||
optionalModules?: Set<string>
|
|
||||||
) {
|
|
||||||
if (!WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflow = WorkflowManager.workflows.get(workflowId)!
|
|
||||||
|
|
||||||
for (const [key, value] of handlers.entries()) {
|
|
||||||
workflow.handlers_.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
|
|
||||||
|
|
||||||
WorkflowManager.workflows.set(workflowId, {
|
|
||||||
id: workflowId,
|
|
||||||
flow_: finalFlow,
|
|
||||||
orchestrator: new TransactionOrchestrator(workflowId, finalFlow),
|
|
||||||
handler: WorkflowManager.buildHandlers(workflow.handlers_),
|
|
||||||
handlers_: workflow.handlers_,
|
|
||||||
requiredModules,
|
|
||||||
optionalModules,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private static buildHandlers(
|
|
||||||
handlers: Map<
|
|
||||||
string,
|
|
||||||
{ invoke: InvokeHandler; compensate?: CompensateHandler }
|
|
||||||
>
|
|
||||||
): (container: MedusaContainer) => TransactionStepHandler {
|
|
||||||
return (container: MedusaContainer): TransactionStepHandler => {
|
|
||||||
return async (
|
|
||||||
actionId: string,
|
|
||||||
handlerType: TransactionHandlerType,
|
|
||||||
payload?: any
|
|
||||||
) => {
|
|
||||||
const command = handlers.get(actionId)
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
throw new Error(`Handler for action "${actionId}" not found.`)
|
|
||||||
} else if (!command[handlerType]) {
|
|
||||||
throw new Error(
|
|
||||||
`"${handlerType}" handler for action "${actionId}" not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { invoke, compensate, payload: input } = payload.context
|
|
||||||
const { metadata } = payload
|
|
||||||
|
|
||||||
if (handlerType === TransactionHandlerType.COMPENSATE) {
|
|
||||||
return await command[handlerType]!(
|
|
||||||
container,
|
|
||||||
input,
|
|
||||||
invoke,
|
|
||||||
compensate,
|
|
||||||
metadata
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await command[handlerType](container, input, invoke, metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async begin(
|
|
||||||
workflowId: string,
|
|
||||||
uniqueTransactionId: string,
|
|
||||||
input?: unknown
|
|
||||||
) {
|
|
||||||
if (!WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflow = WorkflowManager.workflows.get(workflowId)!
|
|
||||||
|
|
||||||
const orchestrator = workflow.orchestrator
|
|
||||||
|
|
||||||
const transaction = await orchestrator.beginTransaction(
|
|
||||||
uniqueTransactionId,
|
|
||||||
workflow.handler(this.container),
|
|
||||||
input
|
|
||||||
)
|
|
||||||
|
|
||||||
await orchestrator.resume(transaction)
|
|
||||||
|
|
||||||
return transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerStepSuccess(
|
|
||||||
workflowId: string,
|
|
||||||
idempotencyKey: string,
|
|
||||||
response?: unknown
|
|
||||||
): Promise<DistributedTransaction> {
|
|
||||||
if (!WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflow = WorkflowManager.workflows.get(workflowId)!
|
|
||||||
return await workflow.orchestrator.registerStepSuccess(
|
|
||||||
idempotencyKey,
|
|
||||||
workflow.handler(this.container),
|
|
||||||
undefined,
|
|
||||||
response
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerStepFailure(
|
|
||||||
workflowId: string,
|
|
||||||
idempotencyKey: string,
|
|
||||||
error?: Error | any
|
|
||||||
): Promise<DistributedTransaction> {
|
|
||||||
if (!WorkflowManager.workflows.has(workflowId)) {
|
|
||||||
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflow = WorkflowManager.workflows.get(workflowId)!
|
|
||||||
return await workflow.orchestrator.registerStepFailure(
|
|
||||||
idempotencyKey,
|
|
||||||
error,
|
|
||||||
workflow.handler(this.container)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
TransactionPayload,
|
TransactionPayload,
|
||||||
TransactionStepHandler,
|
TransactionStepHandler,
|
||||||
TransactionStepsDefinition,
|
TransactionStepsDefinition,
|
||||||
} from "../../../utils/transaction"
|
} from "@medusajs/orchestration"
|
||||||
import {
|
import {
|
||||||
IInventoryService,
|
IInventoryService,
|
||||||
MedusaContainer,
|
MedusaContainer,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { MedusaError } from "@medusajs/utils"
|
|||||||
import {
|
import {
|
||||||
TransactionOrchestrator,
|
TransactionOrchestrator,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
} from "../../../utils/transaction"
|
} from "@medusajs/orchestration"
|
||||||
import { AdminPostProductsReq } from "../../../api"
|
import { AdminPostProductsReq } from "../../../api"
|
||||||
import { Product } from "../../../models"
|
import { Product } from "../../../models"
|
||||||
import { PricedProduct } from "../../../types/pricing"
|
import { PricedProduct } from "../../../types/pricing"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
JoinerServiceConfig,
|
LoadedModule,
|
||||||
MODULE_RESOURCE_TYPE,
|
MODULE_RESOURCE_TYPE,
|
||||||
MODULE_SCOPE,
|
MODULE_SCOPE,
|
||||||
ModuleDefinition,
|
|
||||||
ModuleExports,
|
ModuleExports,
|
||||||
ModuleResolution,
|
ModuleResolution,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
@@ -27,7 +26,10 @@ const logger: any = {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface MedusaModule {
|
interface MedusaModule {
|
||||||
getLoadedModules(): Map<string, any>
|
getLoadedModules(
|
||||||
|
aliases?: Map<string, string>
|
||||||
|
): { [key: string]: LoadedModule }[]
|
||||||
|
getModuleInstance(moduleKey: string, alias?: string): LoadedModule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +45,16 @@ export class MedusaModule {
|
|||||||
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
||||||
private static loading_: Map<string, Promise<any>> = new Map()
|
private static loading_: Map<string, Promise<any>> = new Map()
|
||||||
|
|
||||||
public static getLoadedModules(): Map<
|
public static getLoadedModules(
|
||||||
string,
|
aliases?: Map<string, string>
|
||||||
any & {
|
): { [key: string]: LoadedModule }[] {
|
||||||
__joinerConfig: JoinerServiceConfig
|
return [...MedusaModule.modules_.entries()].map(([key]) => {
|
||||||
__definition: ModuleDefinition
|
if (aliases?.has(key)) {
|
||||||
|
return MedusaModule.getModuleInstance(key, aliases.get(key))
|
||||||
}
|
}
|
||||||
> {
|
|
||||||
return MedusaModule.instances_
|
return MedusaModule.getModuleInstance(key)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static clearInstances(): void {
|
public static clearInstances(): void {
|
||||||
@@ -267,3 +271,6 @@ export class MedusaModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global.MedusaModule ??= MedusaModule
|
||||||
|
exports.MedusaModule = global.MedusaModule
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
JoinerRelationship,
|
JoinerRelationship,
|
||||||
JoinerServiceConfig,
|
JoinerServiceConfig,
|
||||||
ModuleDefinition,
|
LoadedModule,
|
||||||
RemoteExpandProperty,
|
RemoteExpandProperty,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
@@ -11,13 +11,10 @@ import { toPascalCase } from "@medusajs/utils"
|
|||||||
|
|
||||||
export class RemoteQuery {
|
export class RemoteQuery {
|
||||||
private remoteJoiner: RemoteJoiner
|
private remoteJoiner: RemoteJoiner
|
||||||
private modulesMap: Map<string, any> = new Map()
|
private modulesMap: Map<string, LoadedModule> = new Map()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
modulesLoaded?: (any & {
|
modulesLoaded?: LoadedModule[],
|
||||||
__joinerConfig: JoinerServiceConfig
|
|
||||||
__definition: ModuleDefinition
|
|
||||||
})[],
|
|
||||||
remoteFetchData?: (
|
remoteFetchData?: (
|
||||||
expand: RemoteExpandProperty,
|
expand: RemoteExpandProperty,
|
||||||
keyField: string,
|
keyField: string,
|
||||||
@@ -29,8 +26,8 @@ export class RemoteQuery {
|
|||||||
}>
|
}>
|
||||||
) {
|
) {
|
||||||
if (!modulesLoaded?.length) {
|
if (!modulesLoaded?.length) {
|
||||||
modulesLoaded = [...MedusaModule.getLoadedModules().entries()].map(
|
modulesLoaded = MedusaModule.getLoadedModules().map(
|
||||||
([, mod]) => mod
|
(mod) => Object.values(mod)[0]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +127,7 @@ export class RemoteQuery {
|
|||||||
path?: string
|
path?: string
|
||||||
}> {
|
}> {
|
||||||
const serviceConfig = expand.serviceConfig
|
const serviceConfig = expand.serviceConfig
|
||||||
const service = this.modulesMap.get(serviceConfig.serviceName)
|
const service = this.modulesMap.get(serviceConfig.serviceName)!
|
||||||
|
|
||||||
let filters = {}
|
let filters = {}
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
File renamed without changes.
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
TransactionOrchestrator,
|
|
||||||
TransactionStepsDefinition,
|
|
||||||
TransactionHandlerType,
|
TransactionHandlerType,
|
||||||
|
TransactionOrchestrator,
|
||||||
TransactionPayload,
|
TransactionPayload,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
|
TransactionStepsDefinition,
|
||||||
} from "../../transaction"
|
} from "../../transaction"
|
||||||
|
|
||||||
describe("Transaction Orchestrator", () => {
|
describe("Transaction Orchestrator", () => {
|
||||||
@@ -96,7 +96,7 @@ describe("Transaction Orchestrator", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should run steps in parallel if 'next' is an array", async () => {
|
it("Should resume steps in parallel if 'next' is an array", async () => {
|
||||||
const actionOrder: string[] = []
|
const actionOrder: string[] = []
|
||||||
async function handler(
|
async function handler(
|
||||||
actionId: string,
|
actionId: string,
|
||||||
+11
-11
@@ -1,11 +1,12 @@
|
|||||||
import { WorkflowManager } from "../../transaction/workflow-manager"
|
import { GlobalWorkflow } from "../../workflow/global-workflow"
|
||||||
import { TransactionState } from "../../transaction/types"
|
import { TransactionState } from "../../transaction/types"
|
||||||
|
import { WorkflowManager } from "../../workflow/workflow-manager"
|
||||||
|
|
||||||
describe("WorkflowManager", () => {
|
describe("WorkflowManager", () => {
|
||||||
const container: any = {}
|
const container: any = {}
|
||||||
|
|
||||||
let handlers
|
let handlers
|
||||||
let flow: WorkflowManager
|
let flow: GlobalWorkflow
|
||||||
let asyncStepIdempotencyKey: string
|
let asyncStepIdempotencyKey: string
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -31,7 +32,7 @@ describe("WorkflowManager", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
handlers.set("callExternal", {
|
handlers.set("callExternal", {
|
||||||
invoke: jest.fn((container, payload, invoke, metadata) => {
|
invoke: jest.fn(({ metadata }) => {
|
||||||
asyncStepIdempotencyKey = metadata.idempotency_key
|
asyncStepIdempotencyKey = metadata.idempotency_key
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -74,7 +75,7 @@ describe("WorkflowManager", () => {
|
|||||||
handlers
|
handlers
|
||||||
)
|
)
|
||||||
|
|
||||||
flow = new WorkflowManager(container)
|
flow = new GlobalWorkflow(container)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return all registered workflows", () => {
|
it("should return all registered workflows", () => {
|
||||||
@@ -83,7 +84,7 @@ describe("WorkflowManager", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should begin a transaction and returns its final state", async () => {
|
it("should begin a transaction and returns its final state", async () => {
|
||||||
const transaction = await flow.begin("create-product", "t-id", {
|
const transaction = await flow.run("create-product", "t-id", {
|
||||||
input: 123,
|
input: 123,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ describe("WorkflowManager", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should begin a transaction and revert it when fail", async () => {
|
it("should begin a transaction and revert it when fail", async () => {
|
||||||
const transaction = await flow.begin("broken-delivery", "t-id")
|
const transaction = await flow.run("broken-delivery", "t-id")
|
||||||
|
|
||||||
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
expect(handlers.get("broken").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("broken").invoke).toHaveBeenCalledTimes(1)
|
||||||
@@ -109,7 +110,7 @@ describe("WorkflowManager", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should continue an asyncronous transaction after reporting a successful step", async () => {
|
it("should continue an asyncronous transaction after reporting a successful step", async () => {
|
||||||
const transaction = await flow.begin("deliver-product", "t-id")
|
const transaction = await flow.run("deliver-product", "t-id")
|
||||||
|
|
||||||
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
||||||
@@ -128,7 +129,7 @@ describe("WorkflowManager", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should revert an asyncronous transaction after reporting a failure step", async () => {
|
it("should revert an asyncronous transaction after reporting a failure step", async () => {
|
||||||
const transaction = await flow.begin("deliver-product", "t-id")
|
const transaction = await flow.run("deliver-product", "t-id")
|
||||||
|
|
||||||
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
||||||
@@ -150,7 +151,7 @@ describe("WorkflowManager", () => {
|
|||||||
expect(continuation.getState()).toBe(TransactionState.FAILED)
|
expect(continuation.getState()).toBe(TransactionState.FAILED)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should update an existing flow with a new step and a new handler", async () => {
|
it("should update an existing global flow with a new step and a new handler", async () => {
|
||||||
const definition =
|
const definition =
|
||||||
WorkflowManager.getTransactionDefinition("create-product")
|
WorkflowManager.getTransactionDefinition("create-product")
|
||||||
|
|
||||||
@@ -164,8 +165,7 @@ describe("WorkflowManager", () => {
|
|||||||
|
|
||||||
WorkflowManager.update("create-product", definition, additionalHandlers)
|
WorkflowManager.update("create-product", definition, additionalHandlers)
|
||||||
|
|
||||||
const transaction = await flow.begin("create-product", "t-id")
|
const transaction = await flow.run("create-product", "t-id")
|
||||||
console.log(transaction)
|
|
||||||
|
|
||||||
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(1)
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(1)
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import { LocalWorkflow } from "../../workflow/local-workflow"
|
||||||
|
import { TransactionState } from "../../transaction/types"
|
||||||
|
import { WorkflowManager } from "../../workflow/workflow-manager"
|
||||||
|
|
||||||
|
describe("WorkflowManager", () => {
|
||||||
|
const container: any = {}
|
||||||
|
|
||||||
|
let handlers
|
||||||
|
let asyncStepIdempotencyKey: string
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
WorkflowManager.unregisterAll()
|
||||||
|
|
||||||
|
handlers = new Map()
|
||||||
|
handlers.set("foo", {
|
||||||
|
invoke: jest.fn().mockResolvedValue({ done: true }),
|
||||||
|
compensate: jest.fn(() => {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
handlers.set("bar", {
|
||||||
|
invoke: jest.fn().mockResolvedValue({ done: true }),
|
||||||
|
compensate: jest.fn().mockResolvedValue({}),
|
||||||
|
})
|
||||||
|
|
||||||
|
handlers.set("broken", {
|
||||||
|
invoke: jest.fn(() => {
|
||||||
|
throw new Error("Step Failed")
|
||||||
|
}),
|
||||||
|
compensate: jest.fn().mockResolvedValue({ bar: 123, reverted: true }),
|
||||||
|
})
|
||||||
|
|
||||||
|
handlers.set("callExternal", {
|
||||||
|
invoke: jest.fn(({ metadata }) => {
|
||||||
|
asyncStepIdempotencyKey = metadata.idempotency_key
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
WorkflowManager.register(
|
||||||
|
"create-product",
|
||||||
|
{
|
||||||
|
action: "foo",
|
||||||
|
next: {
|
||||||
|
action: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handlers
|
||||||
|
)
|
||||||
|
|
||||||
|
WorkflowManager.register(
|
||||||
|
"broken-delivery",
|
||||||
|
{
|
||||||
|
action: "foo",
|
||||||
|
next: {
|
||||||
|
action: "broken",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handlers
|
||||||
|
)
|
||||||
|
|
||||||
|
WorkflowManager.register(
|
||||||
|
"deliver-product",
|
||||||
|
{
|
||||||
|
action: "foo",
|
||||||
|
next: {
|
||||||
|
action: "callExternal",
|
||||||
|
async: true,
|
||||||
|
noCompensation: true,
|
||||||
|
next: {
|
||||||
|
action: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handlers
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return all registered workflows", () => {
|
||||||
|
const wf = Object.keys(Object.fromEntries(WorkflowManager.getWorkflows()))
|
||||||
|
expect(wf).toEqual(["create-product", "broken-delivery", "deliver-product"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should begin a transaction and returns its final state", async () => {
|
||||||
|
const flow = new LocalWorkflow("create-product", container)
|
||||||
|
const transaction = await flow.run("t-id", {
|
||||||
|
input: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(handlers.get("foo").compensate).toHaveBeenCalledTimes(0)
|
||||||
|
expect(handlers.get("foo").compensate).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
expect(transaction.getState()).toBe(TransactionState.DONE)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should begin a transaction and revert it when fail", async () => {
|
||||||
|
const flow = new LocalWorkflow("broken-delivery", container)
|
||||||
|
const transaction = await flow.run("t-id")
|
||||||
|
|
||||||
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("broken").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(handlers.get("foo").compensate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("broken").compensate).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(transaction.getState()).toBe(TransactionState.REVERTED)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should continue an asyncronous transaction after reporting a successful step", async () => {
|
||||||
|
const flow = new LocalWorkflow("deliver-product", container)
|
||||||
|
const transaction = await flow.run("t-id")
|
||||||
|
|
||||||
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
expect(transaction.getState()).toBe(TransactionState.INVOKING)
|
||||||
|
|
||||||
|
const continuation = await flow.registerStepSuccess(
|
||||||
|
asyncStepIdempotencyKey,
|
||||||
|
{ ok: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(continuation.getState()).toBe(TransactionState.DONE)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should revert an asyncronous transaction after reporting a failure step", async () => {
|
||||||
|
const flow = new LocalWorkflow("deliver-product", container)
|
||||||
|
const transaction = await flow.run("t-id")
|
||||||
|
|
||||||
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("callExternal").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
expect(transaction.getState()).toBe(TransactionState.INVOKING)
|
||||||
|
|
||||||
|
const continuation = await flow.registerStepFailure(
|
||||||
|
asyncStepIdempotencyKey,
|
||||||
|
{ ok: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(handlers.get("foo").compensate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(0)
|
||||||
|
expect(handlers.get("bar").compensate).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
// Failed because the async is flagged as noCompensation
|
||||||
|
expect(continuation.getState()).toBe(TransactionState.FAILED)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a flow with a new step and a new handler", async () => {
|
||||||
|
const flow = new LocalWorkflow("create-product", container)
|
||||||
|
|
||||||
|
const additionalHandler = {
|
||||||
|
invoke: jest.fn().mockResolvedValue({ done: true }),
|
||||||
|
compensate: jest.fn().mockResolvedValue({}),
|
||||||
|
}
|
||||||
|
|
||||||
|
flow.insertActionBefore("bar", "xor", additionalHandler, { maxRetries: 3 })
|
||||||
|
|
||||||
|
const transaction = await flow.run("t-id")
|
||||||
|
|
||||||
|
expect(handlers.get("foo").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(1)
|
||||||
|
expect(additionalHandler.invoke).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(transaction.getState()).toBe(TransactionState.DONE)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
WorkflowManager.getWorkflow("create-product")?.handlers_.has("xor")
|
||||||
|
).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1 +1,3 @@
|
|||||||
export * from "./joiner"
|
export * from "./joiner"
|
||||||
|
export * from "./transaction"
|
||||||
|
export * from "./workflow"
|
||||||
File renamed without changes.
+1
@@ -2,3 +2,4 @@ export * from "./types"
|
|||||||
export * from "./transaction-orchestrator"
|
export * from "./transaction-orchestrator"
|
||||||
export * from "./transaction-step"
|
export * from "./transaction-step"
|
||||||
export * from "./distributed-transaction"
|
export * from "./distributed-transaction"
|
||||||
|
export * from "./orchestrator-builder"
|
||||||
+27
-18
@@ -1,9 +1,5 @@
|
|||||||
import { TransactionStepsDefinition } from "./types"
|
import { TransactionStepsDefinition } from "./types"
|
||||||
|
|
||||||
export type ActionHandler = {
|
|
||||||
[type: string]: (data: any, context: any) => Promise<any>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InternalStep extends TransactionStepsDefinition {
|
interface InternalStep extends TransactionStepsDefinition {
|
||||||
next?: InternalStep | InternalStep[]
|
next?: InternalStep | InternalStep[]
|
||||||
depth: number
|
depth: number
|
||||||
@@ -11,7 +7,12 @@ interface InternalStep extends TransactionStepsDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class OrchestratorBuilder {
|
export class OrchestratorBuilder {
|
||||||
private steps: InternalStep
|
protected steps: InternalStep
|
||||||
|
protected hasChanges_ = false
|
||||||
|
|
||||||
|
get hasChanges() {
|
||||||
|
return this.hasChanges_
|
||||||
|
}
|
||||||
|
|
||||||
constructor(steps?: TransactionStepsDefinition) {
|
constructor(steps?: TransactionStepsDefinition) {
|
||||||
this.load(steps)
|
this.load(steps)
|
||||||
@@ -42,6 +43,7 @@ export class OrchestratorBuilder {
|
|||||||
} as InternalStep
|
} as InternalStep
|
||||||
|
|
||||||
step.next = newAction
|
step.next = newAction
|
||||||
|
this.hasChanges_ = true
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,7 @@ export class OrchestratorBuilder {
|
|||||||
|
|
||||||
Object.assign(step, options)
|
Object.assign(step, options)
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +95,7 @@ export class OrchestratorBuilder {
|
|||||||
this.updateDepths(oldNext as InternalStep, parentStep)
|
this.updateDepths(oldNext as InternalStep, parentStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +116,12 @@ export class OrchestratorBuilder {
|
|||||||
} as InternalStep
|
} as InternalStep
|
||||||
|
|
||||||
this.updateDepths(oldNext as InternalStep, step.next)
|
this.updateDepths(oldNext as InternalStep, step.next)
|
||||||
|
this.hasChanges_ = true
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private appendTo(step: InternalStep | string, newStep: InternalStep) {
|
protected appendTo(step: InternalStep | string, newStep: InternalStep) {
|
||||||
if (typeof step === "string") {
|
if (typeof step === "string") {
|
||||||
step = this.findOrThrowStepByAction(step)
|
step = this.findOrThrowStepByAction(step)
|
||||||
}
|
}
|
||||||
@@ -127,6 +132,7 @@ export class OrchestratorBuilder {
|
|||||||
parent: step.action,
|
parent: step.action,
|
||||||
} as InternalStep
|
} as InternalStep
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +152,7 @@ export class OrchestratorBuilder {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private move(
|
protected move(
|
||||||
actionToMove: string,
|
actionToMove: string,
|
||||||
targetAction: string,
|
targetAction: string,
|
||||||
{
|
{
|
||||||
@@ -226,22 +232,20 @@ export class OrchestratorBuilder {
|
|||||||
parentTargetActionStep.depth
|
parentTargetActionStep.depth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAction(actionToMove: string, targetAction: string): OrchestratorBuilder {
|
moveAction(actionToMove: string, targetAction: string): OrchestratorBuilder {
|
||||||
this.move(actionToMove, targetAction)
|
return this.move(actionToMove, targetAction)
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAndMergeNextAction(
|
moveAndMergeNextAction(
|
||||||
actionToMove: string,
|
actionToMove: string,
|
||||||
targetAction: string
|
targetAction: string
|
||||||
): OrchestratorBuilder {
|
): OrchestratorBuilder {
|
||||||
this.move(actionToMove, targetAction, { mergeNext: true })
|
return this.move(actionToMove, targetAction, { mergeNext: true })
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeActions(where: string, ...actions: string[]) {
|
mergeActions(where: string, ...actions: string[]) {
|
||||||
@@ -284,6 +288,8 @@ export class OrchestratorBuilder {
|
|||||||
parentStep.depth
|
parentStep.depth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,10 +306,11 @@ export class OrchestratorBuilder {
|
|||||||
delete parentStep.next
|
delete parentStep.next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasChanges_ = true
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private findStepByAction(
|
protected findStepByAction(
|
||||||
action: string,
|
action: string,
|
||||||
step: InternalStep = this.steps
|
step: InternalStep = this.steps
|
||||||
): InternalStep | undefined {
|
): InternalStep | undefined {
|
||||||
@@ -325,7 +332,7 @@ export class OrchestratorBuilder {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
private findOrThrowStepByAction(
|
protected findOrThrowStepByAction(
|
||||||
action: string,
|
action: string,
|
||||||
steps: InternalStep = this.steps
|
steps: InternalStep = this.steps
|
||||||
): InternalStep {
|
): InternalStep {
|
||||||
@@ -337,7 +344,7 @@ export class OrchestratorBuilder {
|
|||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|
||||||
private findParentStepByAction(
|
protected findParentStepByAction(
|
||||||
action: string,
|
action: string,
|
||||||
step: InternalStep = this.steps
|
step: InternalStep = this.steps
|
||||||
): InternalStep | undefined {
|
): InternalStep | undefined {
|
||||||
@@ -365,7 +372,7 @@ export class OrchestratorBuilder {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
private findLastStep(steps: InternalStep = this.steps): InternalStep {
|
protected findLastStep(steps: InternalStep = this.steps): InternalStep {
|
||||||
let step = steps as InternalStep
|
let step = steps as InternalStep
|
||||||
while (step.next) {
|
while (step.next) {
|
||||||
step = Array.isArray(step.next)
|
step = Array.isArray(step.next)
|
||||||
@@ -376,7 +383,7 @@ export class OrchestratorBuilder {
|
|||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateDepths(
|
protected updateDepths(
|
||||||
startingStep: InternalStep,
|
startingStep: InternalStep,
|
||||||
parent,
|
parent,
|
||||||
incr = 1,
|
incr = 1,
|
||||||
@@ -417,6 +424,8 @@ export class OrchestratorBuilder {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.hasChanges_ = false
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-8
@@ -1,18 +1,19 @@
|
|||||||
import { EventEmitter } from "events"
|
|
||||||
import {
|
|
||||||
TransactionHandlerType,
|
|
||||||
TransactionStepsDefinition,
|
|
||||||
TransactionStepStatus,
|
|
||||||
TransactionState,
|
|
||||||
TransactionModel,
|
|
||||||
} from "./types"
|
|
||||||
import {
|
import {
|
||||||
DistributedTransaction,
|
DistributedTransaction,
|
||||||
TransactionCheckpoint,
|
TransactionCheckpoint,
|
||||||
TransactionPayload,
|
TransactionPayload,
|
||||||
} from "./distributed-transaction"
|
} from "./distributed-transaction"
|
||||||
|
import {
|
||||||
|
TransactionHandlerType,
|
||||||
|
TransactionModel,
|
||||||
|
TransactionState,
|
||||||
|
TransactionStepStatus,
|
||||||
|
TransactionStepsDefinition,
|
||||||
|
} from "./types"
|
||||||
import { TransactionStep, TransactionStepHandler } from "./transaction-step"
|
import { TransactionStep, TransactionStepHandler } from "./transaction-step"
|
||||||
|
|
||||||
|
import { EventEmitter } from "events"
|
||||||
|
|
||||||
export type TransactionFlow = {
|
export type TransactionFlow = {
|
||||||
modelId: string
|
modelId: string
|
||||||
definition: TransactionStepsDefinition
|
definition: TransactionStepsDefinition
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,94 @@
|
|||||||
|
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||||
|
import { WorkflowDefinition, WorkflowManager } from "./workflow-manager"
|
||||||
|
|
||||||
|
import { DistributedTransaction } from "../transaction"
|
||||||
|
import { asValue } from "awilix"
|
||||||
|
import { createMedusaContainer } from "@medusajs/utils"
|
||||||
|
|
||||||
|
export class GlobalWorkflow extends WorkflowManager {
|
||||||
|
protected static workflows: Map<string, WorkflowDefinition> = new Map()
|
||||||
|
protected container: MedusaContainer
|
||||||
|
protected context: Context
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
modulesLoaded?: LoadedModule[] | MedusaContainer,
|
||||||
|
context?: Context
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
const container = createMedusaContainer()
|
||||||
|
|
||||||
|
// Medusa container
|
||||||
|
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||||
|
const cradle = modulesLoaded.cradle
|
||||||
|
for (const key in cradle) {
|
||||||
|
container.register(key, asValue(cradle[key]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Array of modules
|
||||||
|
else if (modulesLoaded?.length) {
|
||||||
|
for (const mod of modulesLoaded) {
|
||||||
|
const registrationName = mod.__definition.registrationName
|
||||||
|
container.register(registrationName, asValue(mod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container = container
|
||||||
|
this.context = context ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(workflowId: string, uniqueTransactionId: string, input?: unknown) {
|
||||||
|
if (!WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = WorkflowManager.workflows.get(workflowId)!
|
||||||
|
|
||||||
|
const orchestrator = workflow.orchestrator
|
||||||
|
|
||||||
|
const transaction = await orchestrator.beginTransaction(
|
||||||
|
uniqueTransactionId,
|
||||||
|
workflow.handler(this.container, this.context),
|
||||||
|
input
|
||||||
|
)
|
||||||
|
|
||||||
|
await orchestrator.resume(transaction)
|
||||||
|
|
||||||
|
return transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerStepSuccess(
|
||||||
|
workflowId: string,
|
||||||
|
idempotencyKey: string,
|
||||||
|
response?: unknown
|
||||||
|
): Promise<DistributedTransaction> {
|
||||||
|
if (!WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = WorkflowManager.workflows.get(workflowId)!
|
||||||
|
return await workflow.orchestrator.registerStepSuccess(
|
||||||
|
idempotencyKey,
|
||||||
|
workflow.handler(this.container, this.context),
|
||||||
|
undefined,
|
||||||
|
response
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerStepFailure(
|
||||||
|
workflowId: string,
|
||||||
|
idempotencyKey: string,
|
||||||
|
error?: Error | any
|
||||||
|
): Promise<DistributedTransaction> {
|
||||||
|
if (!WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = WorkflowManager.workflows.get(workflowId)!
|
||||||
|
return await workflow.orchestrator.registerStepFailure(
|
||||||
|
idempotencyKey,
|
||||||
|
error,
|
||||||
|
workflow.handler(this.container, this.context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./workflow-manager"
|
||||||
|
export * from "./local-workflow"
|
||||||
|
export * from "./global-workflow"
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
DistributedTransaction,
|
||||||
|
TransactionOrchestrator,
|
||||||
|
TransactionStepsDefinition,
|
||||||
|
} from "../transaction"
|
||||||
|
import {
|
||||||
|
WorkflowDefinition,
|
||||||
|
WorkflowManager,
|
||||||
|
WorkflowStepHandler,
|
||||||
|
} from "./workflow-manager"
|
||||||
|
|
||||||
|
import { OrchestratorBuilder } from "../transaction/orchestrator-builder"
|
||||||
|
import { asValue } from "awilix"
|
||||||
|
import { createMedusaContainer } from "@medusajs/utils"
|
||||||
|
|
||||||
|
type StepHandler = {
|
||||||
|
invoke: WorkflowStepHandler
|
||||||
|
compensate?: WorkflowStepHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocalWorkflow {
|
||||||
|
protected container: MedusaContainer
|
||||||
|
protected workflowId: string
|
||||||
|
protected flow: OrchestratorBuilder
|
||||||
|
protected workflow: WorkflowDefinition
|
||||||
|
protected handlers: Map<string, StepHandler>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
workflowId: string,
|
||||||
|
modulesLoaded?: LoadedModule[] | MedusaContainer
|
||||||
|
) {
|
||||||
|
const globalWorkflow = WorkflowManager.getWorkflow(workflowId)
|
||||||
|
if (!globalWorkflow) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flow = new OrchestratorBuilder(globalWorkflow.flow_)
|
||||||
|
this.workflowId = workflowId
|
||||||
|
this.workflow = globalWorkflow
|
||||||
|
this.handlers = new Map(globalWorkflow.handlers_)
|
||||||
|
|
||||||
|
const container = createMedusaContainer()
|
||||||
|
|
||||||
|
// Medusa container
|
||||||
|
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||||
|
const cradle = modulesLoaded.cradle
|
||||||
|
for (const key in cradle) {
|
||||||
|
container.register(key, asValue(cradle[key]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Array of modules
|
||||||
|
else if (modulesLoaded?.length) {
|
||||||
|
for (const mod of modulesLoaded) {
|
||||||
|
const registrationName = mod.__definition.registrationName
|
||||||
|
container.register(registrationName, asValue(mod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container = container
|
||||||
|
}
|
||||||
|
|
||||||
|
protected commit() {
|
||||||
|
const finalFlow = this.flow.build()
|
||||||
|
|
||||||
|
this.workflow = {
|
||||||
|
id: this.workflowId,
|
||||||
|
flow_: finalFlow,
|
||||||
|
orchestrator: new TransactionOrchestrator(this.workflowId, finalFlow),
|
||||||
|
handler: WorkflowManager.buildHandlers(this.handlers),
|
||||||
|
handlers_: this.handlers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(uniqueTransactionId: string, input?: unknown, context?: Context) {
|
||||||
|
if (this.flow.hasChanges) {
|
||||||
|
this.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { handler, orchestrator } = this.workflow
|
||||||
|
|
||||||
|
const transaction = await orchestrator.beginTransaction(
|
||||||
|
uniqueTransactionId,
|
||||||
|
handler(this.container, context),
|
||||||
|
input
|
||||||
|
)
|
||||||
|
|
||||||
|
await orchestrator.resume(transaction)
|
||||||
|
|
||||||
|
return transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerStepSuccess(
|
||||||
|
idempotencyKey: string,
|
||||||
|
response?: unknown,
|
||||||
|
context?: Context
|
||||||
|
): Promise<DistributedTransaction> {
|
||||||
|
const { handler, orchestrator } = this.workflow
|
||||||
|
return await orchestrator.registerStepSuccess(
|
||||||
|
idempotencyKey,
|
||||||
|
handler(this.container, context),
|
||||||
|
undefined,
|
||||||
|
response
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerStepFailure(
|
||||||
|
idempotencyKey: string,
|
||||||
|
error?: Error | any,
|
||||||
|
context?: Context
|
||||||
|
): Promise<DistributedTransaction> {
|
||||||
|
const { handler, orchestrator } = this.workflow
|
||||||
|
return await orchestrator.registerStepFailure(
|
||||||
|
idempotencyKey,
|
||||||
|
error,
|
||||||
|
handler(this.container, context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(
|
||||||
|
action: string,
|
||||||
|
handler: StepHandler,
|
||||||
|
options: Partial<TransactionStepsDefinition> = {}
|
||||||
|
) {
|
||||||
|
this.assertHandler(handler, action)
|
||||||
|
this.handlers.set(action, handler)
|
||||||
|
|
||||||
|
return this.flow.addAction(action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceAction(
|
||||||
|
existingAction: string,
|
||||||
|
action: string,
|
||||||
|
handler: StepHandler,
|
||||||
|
options: Partial<TransactionStepsDefinition> = {}
|
||||||
|
) {
|
||||||
|
this.assertHandler(handler, action)
|
||||||
|
this.handlers.set(action, handler)
|
||||||
|
|
||||||
|
return this.flow.replaceAction(existingAction, action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertActionBefore(
|
||||||
|
existingAction: string,
|
||||||
|
action: string,
|
||||||
|
handler: StepHandler,
|
||||||
|
options: Partial<TransactionStepsDefinition> = {}
|
||||||
|
) {
|
||||||
|
this.assertHandler(handler, action)
|
||||||
|
this.handlers.set(action, handler)
|
||||||
|
|
||||||
|
return this.flow.insertActionBefore(existingAction, action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertActionAfter(
|
||||||
|
existingAction: string,
|
||||||
|
action: string,
|
||||||
|
handler: StepHandler,
|
||||||
|
options: Partial<TransactionStepsDefinition> = {}
|
||||||
|
) {
|
||||||
|
this.assertHandler(handler, action)
|
||||||
|
this.handlers.set(action, handler)
|
||||||
|
|
||||||
|
return this.flow.insertActionAfter(existingAction, action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendAction(
|
||||||
|
action: string,
|
||||||
|
to: string,
|
||||||
|
handler: StepHandler,
|
||||||
|
options: Partial<TransactionStepsDefinition> = {}
|
||||||
|
) {
|
||||||
|
this.assertHandler(handler, action)
|
||||||
|
this.handlers.set(action, handler)
|
||||||
|
|
||||||
|
return this.flow.appendAction(action, to, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAction(actionToMove: string, targetAction: string): OrchestratorBuilder {
|
||||||
|
return this.flow.moveAction(actionToMove, targetAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAndMergeNextAction(
|
||||||
|
actionToMove: string,
|
||||||
|
targetAction: string
|
||||||
|
): OrchestratorBuilder {
|
||||||
|
return this.flow.moveAndMergeNextAction(actionToMove, targetAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeActions(where: string, ...actions: string[]) {
|
||||||
|
return this.flow.mergeActions(where, ...actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAction(action: string, parentSteps?) {
|
||||||
|
return this.flow.deleteAction(action, parentSteps)
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneAction(action: string) {
|
||||||
|
return this.flow.pruneAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected assertHandler(handler: StepHandler, action: string): void | never {
|
||||||
|
if (!handler?.invoke) {
|
||||||
|
throw new Error(
|
||||||
|
`Handler for action "${action}" is missing invoke function.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { Context, MedusaContainer } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
OrchestratorBuilder,
|
||||||
|
TransactionHandlerType,
|
||||||
|
TransactionMetadata,
|
||||||
|
TransactionOrchestrator,
|
||||||
|
TransactionStepHandler,
|
||||||
|
TransactionStepsDefinition,
|
||||||
|
} from "../transaction"
|
||||||
|
|
||||||
|
export interface WorkflowDefinition {
|
||||||
|
id: string
|
||||||
|
handler: (
|
||||||
|
container: MedusaContainer,
|
||||||
|
context?: Context
|
||||||
|
) => TransactionStepHandler
|
||||||
|
orchestrator: TransactionOrchestrator
|
||||||
|
flow_: TransactionStepsDefinition
|
||||||
|
handlers_: Map<
|
||||||
|
string,
|
||||||
|
{ invoke: WorkflowStepHandler; compensate?: WorkflowStepHandler }
|
||||||
|
>
|
||||||
|
requiredModules?: Set<string>
|
||||||
|
optionalModules?: Set<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowHandler = Map<
|
||||||
|
string,
|
||||||
|
{ invoke: WorkflowStepHandler; compensate?: WorkflowStepHandler }
|
||||||
|
>
|
||||||
|
|
||||||
|
export type WorkflowStepHandler = (args: {
|
||||||
|
container: MedusaContainer
|
||||||
|
payload: unknown
|
||||||
|
invoke: { [actions: string]: unknown }
|
||||||
|
compensate: { [actions: string]: unknown }
|
||||||
|
metadata: TransactionMetadata
|
||||||
|
context?: Context
|
||||||
|
}) => unknown
|
||||||
|
|
||||||
|
export class WorkflowManager {
|
||||||
|
protected static workflows: Map<string, WorkflowDefinition> = new Map()
|
||||||
|
|
||||||
|
static unregister(workflowId: string) {
|
||||||
|
WorkflowManager.workflows.delete(workflowId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static unregisterAll() {
|
||||||
|
WorkflowManager.workflows.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
static getWorkflows() {
|
||||||
|
return WorkflowManager.workflows
|
||||||
|
}
|
||||||
|
|
||||||
|
static getWorkflow(workflowId: string) {
|
||||||
|
return WorkflowManager.workflows.get(workflowId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTransactionDefinition(workflowId): OrchestratorBuilder {
|
||||||
|
if (!WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = WorkflowManager.workflows.get(workflowId)!
|
||||||
|
return new OrchestratorBuilder(workflow.flow_)
|
||||||
|
}
|
||||||
|
|
||||||
|
static register(
|
||||||
|
workflowId: string,
|
||||||
|
flow: TransactionStepsDefinition | OrchestratorBuilder,
|
||||||
|
handlers: WorkflowHandler,
|
||||||
|
requiredModules?: Set<string>,
|
||||||
|
optionalModules?: Set<string>
|
||||||
|
) {
|
||||||
|
if (WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" is already defined.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
|
||||||
|
|
||||||
|
WorkflowManager.workflows.set(workflowId, {
|
||||||
|
id: workflowId,
|
||||||
|
flow_: finalFlow,
|
||||||
|
orchestrator: new TransactionOrchestrator(workflowId, finalFlow),
|
||||||
|
handler: WorkflowManager.buildHandlers(handlers),
|
||||||
|
handlers_: handlers,
|
||||||
|
requiredModules,
|
||||||
|
optionalModules,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static update(
|
||||||
|
workflowId: string,
|
||||||
|
flow: TransactionStepsDefinition | OrchestratorBuilder,
|
||||||
|
handlers: Map<
|
||||||
|
string,
|
||||||
|
{ invoke: WorkflowStepHandler; compensate?: WorkflowStepHandler }
|
||||||
|
>,
|
||||||
|
requiredModules?: Set<string>,
|
||||||
|
optionalModules?: Set<string>
|
||||||
|
) {
|
||||||
|
if (!WorkflowManager.workflows.has(workflowId)) {
|
||||||
|
throw new Error(`Workflow with id "${workflowId}" not found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = WorkflowManager.workflows.get(workflowId)!
|
||||||
|
|
||||||
|
for (const [key, value] of handlers.entries()) {
|
||||||
|
workflow.handlers_.set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
|
||||||
|
|
||||||
|
WorkflowManager.workflows.set(workflowId, {
|
||||||
|
id: workflowId,
|
||||||
|
flow_: finalFlow,
|
||||||
|
orchestrator: new TransactionOrchestrator(workflowId, finalFlow),
|
||||||
|
handler: WorkflowManager.buildHandlers(workflow.handlers_),
|
||||||
|
handlers_: workflow.handlers_,
|
||||||
|
requiredModules,
|
||||||
|
optionalModules,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static buildHandlers(
|
||||||
|
handlers: Map<
|
||||||
|
string,
|
||||||
|
{ invoke: WorkflowStepHandler; compensate?: WorkflowStepHandler }
|
||||||
|
>
|
||||||
|
): (container: MedusaContainer, context?: Context) => TransactionStepHandler {
|
||||||
|
return (
|
||||||
|
container: MedusaContainer,
|
||||||
|
context?: Context
|
||||||
|
): TransactionStepHandler => {
|
||||||
|
return async (
|
||||||
|
actionId: string,
|
||||||
|
handlerType: TransactionHandlerType,
|
||||||
|
payload?: any
|
||||||
|
) => {
|
||||||
|
const command = handlers.get(actionId)
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
throw new Error(`Handler for action "${actionId}" not found.`)
|
||||||
|
} else if (!command[handlerType]) {
|
||||||
|
throw new Error(
|
||||||
|
`"${handlerType}" handler for action "${actionId}" not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { invoke, compensate, payload: input } = payload.context
|
||||||
|
const { metadata } = payload
|
||||||
|
|
||||||
|
return await command[handlerType]!({
|
||||||
|
container,
|
||||||
|
payload: input,
|
||||||
|
invoke,
|
||||||
|
compensate,
|
||||||
|
metadata,
|
||||||
|
context,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.WorkflowManager ??= WorkflowManager
|
||||||
|
exports.WorkflowManager = global.WorkflowManager
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["es5", "es6", "es2019"],
|
"lib": ["es2020"],
|
||||||
"target": "es5",
|
"target": "es2020",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { MedusaContainer } from "../common"
|
import { JoinerServiceConfig } from "../joiner"
|
||||||
import { Logger } from "../logger"
|
import { Logger } from "../logger"
|
||||||
|
import { MedusaContainer } from "../common"
|
||||||
import { RepositoryService } from "../dal"
|
import { RepositoryService } from "../dal"
|
||||||
|
|
||||||
export type Constructor<T> = new (...args: any[]) => T
|
export type Constructor<T> = new (...args: any[]) => T
|
||||||
@@ -69,6 +70,11 @@ export type ModuleDefinition = {
|
|||||||
| ExternalModuleDeclaration
|
| ExternalModuleDeclaration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LoadedModule = unknown & {
|
||||||
|
__joinerConfig: JoinerServiceConfig
|
||||||
|
__definition: ModuleDefinition
|
||||||
|
}
|
||||||
|
|
||||||
export type LoaderOptions<TOptions = Record<string, unknown>> = {
|
export type LoaderOptions<TOptions = Record<string, unknown>> = {
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
options?: TOptions
|
options?: TOptions
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ export type Context<TManager = unknown> = {
|
|||||||
transactionManager?: TManager
|
transactionManager?: TManager
|
||||||
isolationLevel?: string
|
isolationLevel?: string
|
||||||
enableNestedTransactions?: boolean
|
enableNestedTransactions?: boolean
|
||||||
|
transactionId?: string
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
"ts-jest": {
|
||||||
|
tsConfig: "tsconfig.json",
|
||||||
|
isolatedModules: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
"^.+\\.[jt]s?$": "ts-jest",
|
||||||
|
},
|
||||||
|
testEnvironment: `node`,
|
||||||
|
moduleFileExtensions: [`js`, `ts`],
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@medusajs/workflows",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Set of workflows for Medusa",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa",
|
||||||
|
"directory": "packages/workflows"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"author": "Medusa",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@medusajs/types": "^1.8.10",
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"jest": "^25.5.4",
|
||||||
|
"rimraf": "^5.0.1",
|
||||||
|
"ts-jest": "^25.5.1",
|
||||||
|
"typescript": "^4.4.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@medusajs/modules-sdk": "^1.8.8",
|
||||||
|
"@medusajs/orchestration": "^0.0.2",
|
||||||
|
"@medusajs/utils": "^1.9.2",
|
||||||
|
"awilix": "^8.0.1",
|
||||||
|
"ulid": "^2.3.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||||
|
"build": "rimraf dist && tsc --build",
|
||||||
|
"watch": "tsc --build --watch",
|
||||||
|
"test": "jest --passWithNoTests"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { InputAlias, Workflows } from "../definitions"
|
||||||
|
import {
|
||||||
|
TransactionStepsDefinition,
|
||||||
|
WorkflowManager,
|
||||||
|
} from "@medusajs/orchestration"
|
||||||
|
import {
|
||||||
|
createProducts as createProductsHandler,
|
||||||
|
removeProducts,
|
||||||
|
} from "../handlers"
|
||||||
|
import { exportWorkflow, pipe } from "../helper"
|
||||||
|
|
||||||
|
import { ProductTypes } from "@medusajs/types"
|
||||||
|
|
||||||
|
enum Actions {
|
||||||
|
createProducts = "createProducts",
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowSteps: TransactionStepsDefinition = {
|
||||||
|
next: {
|
||||||
|
action: Actions.createProducts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = new Map([
|
||||||
|
[
|
||||||
|
Actions.createProducts,
|
||||||
|
{
|
||||||
|
invoke: pipe(
|
||||||
|
{
|
||||||
|
inputAlias: InputAlias.Products,
|
||||||
|
invoke: {
|
||||||
|
from: InputAlias.Products,
|
||||||
|
alias: InputAlias.Products,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createProductsHandler
|
||||||
|
),
|
||||||
|
compensate: pipe(
|
||||||
|
{
|
||||||
|
invoke: {
|
||||||
|
from: Actions.createProducts,
|
||||||
|
alias: InputAlias.Products,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
removeProducts
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
WorkflowManager.register(Workflows.CreateProducts, workflowSteps, handlers)
|
||||||
|
|
||||||
|
export const createProducts = exportWorkflow<
|
||||||
|
ProductTypes.CreateProductDTO[],
|
||||||
|
ProductTypes.ProductDTO[]
|
||||||
|
>(Workflows.CreateProducts, Actions.createProducts)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-products"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export enum Workflows {
|
||||||
|
CreateProducts = "create-products",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum InputAlias {
|
||||||
|
Products = "products",
|
||||||
|
RemovedProducts = "removedProducts",
|
||||||
|
|
||||||
|
InventoryItems = "inventoryItems",
|
||||||
|
RemovedInventoryItems = "removedInventoryItems",
|
||||||
|
|
||||||
|
AttachedInventoryItems = "attachedInventoryItems",
|
||||||
|
DetachedInventoryItems = "detachedInventoryItems",
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { InventoryItemDTO, ProductTypes } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
import { WorkflowArguments } from "../helper"
|
||||||
|
|
||||||
|
export async function attachInventoryItems({
|
||||||
|
container,
|
||||||
|
data,
|
||||||
|
}: WorkflowArguments & {
|
||||||
|
data: {
|
||||||
|
variant: ProductTypes.ProductVariantDTO
|
||||||
|
[InputAlias.InventoryItems]: InventoryItemDTO
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const manager = container.resolve("manager")
|
||||||
|
const productVariantInventoryService = container
|
||||||
|
.resolve("productVariantInventoryService")
|
||||||
|
.withTransaction(manager)
|
||||||
|
|
||||||
|
const value = await Promise.all(
|
||||||
|
data
|
||||||
|
.filter((d) => d)
|
||||||
|
.map(async ({ variant, [InputAlias.InventoryItems]: inventoryItem }) => {
|
||||||
|
return await productVariantInventoryService.attachInventoryItem(
|
||||||
|
variant.id,
|
||||||
|
inventoryItem.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: InputAlias.AttachedInventoryItems,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { IInventoryService, ProductTypes } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
import { WorkflowArguments } from "../helper"
|
||||||
|
|
||||||
|
export async function createInventoryItems({
|
||||||
|
container,
|
||||||
|
data,
|
||||||
|
}: WorkflowArguments & {
|
||||||
|
data: {
|
||||||
|
[InputAlias.Products]: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const manager = container.resolve("manager")
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
container.resolve("inventoryService")
|
||||||
|
const context = { transactionManager: manager }
|
||||||
|
|
||||||
|
const products = data[InputAlias.Products]
|
||||||
|
const variants = products.reduce(
|
||||||
|
(
|
||||||
|
acc: ProductTypes.ProductVariantDTO[],
|
||||||
|
product: ProductTypes.ProductDTO
|
||||||
|
) => {
|
||||||
|
return acc.concat(product.variants)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const value = await Promise.all(
|
||||||
|
variants.map(async (variant) => {
|
||||||
|
if (!variant.manage_inventory) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService!.createInventoryItem(
|
||||||
|
{
|
||||||
|
sku: variant.sku!,
|
||||||
|
origin_country: variant.origin_country!,
|
||||||
|
hs_code: variant.hs_code!,
|
||||||
|
mid_code: variant.mid_code!,
|
||||||
|
material: variant.material!,
|
||||||
|
weight: variant.weight!,
|
||||||
|
length: variant.length!,
|
||||||
|
height: variant.height!,
|
||||||
|
width: variant.width!,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
return { variant, inventoryItem }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: InputAlias.InventoryItems,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
import { ProductTypes } from "@medusajs/types"
|
||||||
|
import { WorkflowArguments } from "../helper"
|
||||||
|
|
||||||
|
export async function createProducts({
|
||||||
|
container,
|
||||||
|
context,
|
||||||
|
data,
|
||||||
|
}: WorkflowArguments & {
|
||||||
|
data: { [InputAlias.Products]: ProductTypes.CreateProductDTO[] }
|
||||||
|
}) {
|
||||||
|
const productModuleService = container.resolve("productModuleService")
|
||||||
|
|
||||||
|
const value = await productModuleService.create(
|
||||||
|
data[InputAlias.Products],
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: InputAlias.Products,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from "./remove-products"
|
||||||
|
export * from "./create-products"
|
||||||
|
export * from "./create-inventory-items"
|
||||||
|
export * from "./remove-inventory-items"
|
||||||
|
export * from "./attach-inventory-items"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { InventoryItemDTO, MedusaContainer } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
import { WorkflowArguments } from "../helper"
|
||||||
|
|
||||||
|
export async function removeInventoryItems({
|
||||||
|
container,
|
||||||
|
data,
|
||||||
|
}: WorkflowArguments & {
|
||||||
|
data: {
|
||||||
|
[InputAlias.InventoryItems]: InventoryItemDTO
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const manager = container.resolve("manager")
|
||||||
|
const inventoryService = container.resolve("inventoryService")
|
||||||
|
const context = { transactionManager: manager }
|
||||||
|
|
||||||
|
const value = await Promise.all(
|
||||||
|
data.map(async ({ [InputAlias.InventoryItems]: inventoryItem }) => {
|
||||||
|
return await inventoryService!.deleteInventoryItem(
|
||||||
|
inventoryItem.id,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: InputAlias.RemovedInventoryItems,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
import { ProductTypes } from "@medusajs/types"
|
||||||
|
import { WorkflowArguments } from "../helper"
|
||||||
|
|
||||||
|
export async function removeProducts({
|
||||||
|
container,
|
||||||
|
data,
|
||||||
|
}: WorkflowArguments & {
|
||||||
|
data: {
|
||||||
|
[InputAlias.Products]: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const productModuleService = container.resolve("productModuleService")
|
||||||
|
const value = await productModuleService.softDelete(
|
||||||
|
data[InputAlias.Products].map((p) => p.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: InputAlias.RemovedProducts,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const emptyHandler: any = () => {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./empty-handler"
|
||||||
|
export * from "./pipe"
|
||||||
|
export * from "./workflow-export"
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { Context, MedusaContainer, SharedContext } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
TransactionMetadata,
|
||||||
|
WorkflowStepHandler,
|
||||||
|
} from "@medusajs/orchestration"
|
||||||
|
|
||||||
|
import { InputAlias } from "../definitions"
|
||||||
|
|
||||||
|
type WorkflowStepReturn = {
|
||||||
|
alias: string
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowStepInput = {
|
||||||
|
from: string
|
||||||
|
alias: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineInput {
|
||||||
|
inputAlias?: InputAlias | string
|
||||||
|
invoke?: WorkflowStepInput | WorkflowStepInput[]
|
||||||
|
compensate?: WorkflowStepInput | WorkflowStepInput[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowArguments = {
|
||||||
|
container: MedusaContainer
|
||||||
|
payload: unknown
|
||||||
|
data: any
|
||||||
|
metadata: TransactionMetadata
|
||||||
|
context: Context | SharedContext
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PipelineHandler = (
|
||||||
|
args: WorkflowArguments
|
||||||
|
) => Promise<WorkflowStepReturn | WorkflowStepReturn[]>
|
||||||
|
|
||||||
|
export function pipe(
|
||||||
|
input: PipelineInput,
|
||||||
|
...functions: PipelineHandler[]
|
||||||
|
): WorkflowStepHandler {
|
||||||
|
return async ({
|
||||||
|
container,
|
||||||
|
payload,
|
||||||
|
invoke,
|
||||||
|
compensate,
|
||||||
|
metadata,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
const data = {}
|
||||||
|
|
||||||
|
const original = {
|
||||||
|
invoke: invoke ?? {},
|
||||||
|
compensate: compensate ?? {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.inputAlias) {
|
||||||
|
Object.assign(original.invoke, { [input.inputAlias]: payload })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in input) {
|
||||||
|
if (!input[key]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(input[key])) {
|
||||||
|
input[key] = [input[key]]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const action of input[key]) {
|
||||||
|
if (action?.alias) {
|
||||||
|
data[action.alias] = original[key][action.from]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return functions.reduce(async (_, fn) => {
|
||||||
|
let result = await fn({
|
||||||
|
container,
|
||||||
|
payload,
|
||||||
|
data,
|
||||||
|
metadata,
|
||||||
|
context: context as Context,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
for (const action of result) {
|
||||||
|
if (action?.alias) {
|
||||||
|
data[action.alias] = action.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (result?.alias) {
|
||||||
|
data[result.alias] = result.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
DistributedTransaction,
|
||||||
|
LocalWorkflow,
|
||||||
|
TransactionState,
|
||||||
|
TransactionStepError,
|
||||||
|
} from "@medusajs/orchestration"
|
||||||
|
|
||||||
|
import { EOL } from "os"
|
||||||
|
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||||
|
import { Workflows } from "../definitions"
|
||||||
|
import { ulid } from "ulid"
|
||||||
|
|
||||||
|
export type FlowRunOptions<TData = unknown> = {
|
||||||
|
input?: TData
|
||||||
|
context?: Context
|
||||||
|
resultFrom?: string | string[]
|
||||||
|
throwOnError?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowResult<TResult = unknown> = {
|
||||||
|
errors: TransactionStepError[]
|
||||||
|
transaction: DistributedTransaction
|
||||||
|
result: TResult
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
||||||
|
workflowId: Workflows,
|
||||||
|
defaultResult?: string
|
||||||
|
) => {
|
||||||
|
return function <TDataOverride = undefined, TResultOverride = undefined>(
|
||||||
|
container?: LoadedModule[] | MedusaContainer
|
||||||
|
): Omit<LocalWorkflow, "run"> & {
|
||||||
|
run: (
|
||||||
|
args?: FlowRunOptions<
|
||||||
|
TDataOverride extends undefined ? TData : TDataOverride
|
||||||
|
>
|
||||||
|
) => Promise<
|
||||||
|
WorkflowResult<
|
||||||
|
TResultOverride extends undefined ? TResult : TResultOverride
|
||||||
|
>
|
||||||
|
>
|
||||||
|
} {
|
||||||
|
if (!container) {
|
||||||
|
container = MedusaModule.getLoadedModules().map(
|
||||||
|
(mod) => Object.values(mod)[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const flow = new LocalWorkflow(workflowId, container)
|
||||||
|
|
||||||
|
const originalRun = flow.run.bind(flow)
|
||||||
|
const newRun = async (
|
||||||
|
{ input, context, throwOnError, resultFrom }: FlowRunOptions = {
|
||||||
|
throwOnError: true,
|
||||||
|
resultFrom: defaultResult,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const transaction = await originalRun(
|
||||||
|
context?.transactionId ?? ulid(),
|
||||||
|
input,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
const errors = transaction.getErrors()
|
||||||
|
|
||||||
|
const failedStatus = [TransactionState.FAILED, TransactionState.REVERTED]
|
||||||
|
if (failedStatus.includes(transaction.getState()) && throwOnError) {
|
||||||
|
const errorMessage = errors
|
||||||
|
?.map((err) => `${err.error?.message}${EOL}${err.error?.stack}`)
|
||||||
|
?.join(`${EOL}`)
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: any = undefined
|
||||||
|
|
||||||
|
if (resultFrom) {
|
||||||
|
if (Array.isArray(resultFrom)) {
|
||||||
|
result = resultFrom.map(
|
||||||
|
(from) => transaction.getContext().invoke?.[from]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result = transaction.getContext().invoke?.[resultFrom]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
errors,
|
||||||
|
transaction,
|
||||||
|
result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flow.run = newRun as any
|
||||||
|
|
||||||
|
return flow as unknown as LocalWorkflow & {
|
||||||
|
run: (
|
||||||
|
args?: FlowRunOptions<
|
||||||
|
TDataOverride extends undefined ? TData : TDataOverride
|
||||||
|
>
|
||||||
|
) => Promise<
|
||||||
|
WorkflowResult<
|
||||||
|
TResultOverride extends undefined ? TResult : TResultOverride
|
||||||
|
>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./definition"
|
||||||
|
export * from "./definitions"
|
||||||
|
export * as Handlers from "./handlers"
|
||||||
|
export * from "./helper"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"target": "es2020",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"declaration": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"downlevelIteration": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"./src/**/__tests__",
|
||||||
|
"./src/**/__mocks__",
|
||||||
|
"./src/**/__fixtures__",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -6343,6 +6343,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@medusajs/medusa-cli": ^1.3.17
|
"@medusajs/medusa-cli": ^1.3.17
|
||||||
"@medusajs/modules-sdk": ^1.8.8
|
"@medusajs/modules-sdk": ^1.8.8
|
||||||
|
"@medusajs/orchestration": ^0.0.2
|
||||||
"@medusajs/types": ^1.8.11
|
"@medusajs/types": ^1.8.11
|
||||||
"@medusajs/utils": ^1.9.2
|
"@medusajs/utils": ^1.9.2
|
||||||
"@types/express": ^4.17.17
|
"@types/express": ^4.17.17
|
||||||
@@ -6564,6 +6565,24 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@medusajs/workflows@workspace:packages/workflows":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@medusajs/workflows@workspace:packages/workflows"
|
||||||
|
dependencies:
|
||||||
|
"@medusajs/modules-sdk": ^1.8.8
|
||||||
|
"@medusajs/orchestration": ^0.0.2
|
||||||
|
"@medusajs/types": ^1.8.10
|
||||||
|
"@medusajs/utils": ^1.9.2
|
||||||
|
awilix: ^8.0.1
|
||||||
|
cross-env: ^5.2.1
|
||||||
|
jest: ^25.5.4
|
||||||
|
rimraf: ^5.0.1
|
||||||
|
ts-jest: ^25.5.1
|
||||||
|
typescript: ^4.4.4
|
||||||
|
ulid: ^2.3.0
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@microsoft/fetch-event-source@npm:2.0.1":
|
"@microsoft/fetch-event-source@npm:2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "@microsoft/fetch-event-source@npm:2.0.1"
|
resolution: "@microsoft/fetch-event-source@npm:2.0.1"
|
||||||
|
|||||||
Reference in new issue
Block a user