feat(medusa): workflow engine api (#6330)
What:
Workflow Engine API.
Endpoints for:
- List workflow executions
- Run a workflow
- Set async steps as success or failure
- Retrieve the details of a workflow run
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { Router } from "express"
|
||||
import errorHandler from "./middlewares/error-handler"
|
||||
import compression from "compression"
|
||||
import { Router } from "express"
|
||||
import { compressionOptions, shouldCompressResponse } from "../utils/api"
|
||||
import errorHandler from "./middlewares/error-handler"
|
||||
import admin from "./routes/admin"
|
||||
import store from "./routes/store"
|
||||
import { shouldCompressResponse, compressionOptions } from "../utils/api"
|
||||
|
||||
// guaranteed to get dependencies
|
||||
export default (container, config) => {
|
||||
@@ -53,9 +53,9 @@ export * from "./routes/admin/product-types"
|
||||
export * from "./routes/admin/products"
|
||||
export * from "./routes/admin/publishable-api-keys"
|
||||
export * from "./routes/admin/regions"
|
||||
export * from "./routes/admin/reservations"
|
||||
export * from "./routes/admin/return-reasons"
|
||||
export * from "./routes/admin/returns"
|
||||
export * from "./routes/admin/reservations"
|
||||
export * from "./routes/admin/sales-channels"
|
||||
export * from "./routes/admin/shipping-options"
|
||||
export * from "./routes/admin/shipping-profiles"
|
||||
@@ -66,6 +66,7 @@ export * from "./routes/admin/tax-rates"
|
||||
export * from "./routes/admin/uploads"
|
||||
export * from "./routes/admin/users"
|
||||
export * from "./routes/admin/variants"
|
||||
export * from "./routes/admin/workflows-executions"
|
||||
// Store
|
||||
export * from "./routes/store/auth"
|
||||
export * from "./routes/store/carts"
|
||||
|
||||
@@ -41,6 +41,7 @@ import taxRateRoutes from "./tax-rates"
|
||||
import uploadRoutes from "./uploads"
|
||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||
import variantRoutes from "./variants"
|
||||
import workflowRoutes from "./workflows-executions"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -115,6 +116,7 @@ export default (app, container, config) => {
|
||||
paymentCollectionRoutes(route)
|
||||
paymentRoutes(route)
|
||||
productCategoryRoutes(route)
|
||||
workflowRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const { id, workflow_id, transaction_id } = req.params
|
||||
|
||||
const execution = await workflowEngineService.retrieveWorkflowExecution(
|
||||
id ?? {
|
||||
workflow_id,
|
||||
transaction_id,
|
||||
},
|
||||
{
|
||||
select: req.retrieveConfig.select,
|
||||
relations: req.retrieveConfig.relations,
|
||||
}
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
workflow_execution: execution,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import middlewares, {
|
||||
transformBody,
|
||||
transformQuery,
|
||||
} from "../../../middlewares"
|
||||
|
||||
import { Router } from "express"
|
||||
import {
|
||||
allowedAdminWorkflowExecutionsRelations,
|
||||
defaultAdminWorkflowExecutionDetailFields,
|
||||
defaultAdminWorkflowExecutionsFields,
|
||||
defaultAdminWorkflowExecutionsRelations,
|
||||
} from "./query-config"
|
||||
import {
|
||||
AdminGetWorkflowExecutionDetailsParams,
|
||||
AdminGetWorkflowExecutionsParams,
|
||||
AdminPostWorkflowsAsyncResponseReq,
|
||||
AdminPostWorkflowsRunReq,
|
||||
} from "./validators"
|
||||
|
||||
const route = Router()
|
||||
|
||||
const retrieveTransformQueryConfig = {
|
||||
defaultFields: defaultAdminWorkflowExecutionDetailFields,
|
||||
defaultRelations: defaultAdminWorkflowExecutionsRelations,
|
||||
allowedRelations: allowedAdminWorkflowExecutionsRelations,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
defaultFields: defaultAdminWorkflowExecutionsFields,
|
||||
isList: true,
|
||||
}
|
||||
|
||||
export default (app) => {
|
||||
app.use("/workflows-executions", route)
|
||||
|
||||
route.get(
|
||||
"/",
|
||||
transformQuery(AdminGetWorkflowExecutionsParams, listTransformQueryConfig),
|
||||
middlewares.wrap(require("./list-execution").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:id",
|
||||
transformQuery(
|
||||
AdminGetWorkflowExecutionDetailsParams,
|
||||
retrieveTransformQueryConfig
|
||||
),
|
||||
middlewares.wrap(require("./get-execution").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:workflow_id/:transaction_id",
|
||||
transformQuery(
|
||||
AdminGetWorkflowExecutionDetailsParams,
|
||||
retrieveTransformQueryConfig
|
||||
),
|
||||
middlewares.wrap(require("./get-execution").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/steps/success",
|
||||
transformBody(AdminPostWorkflowsAsyncResponseReq),
|
||||
middlewares.wrap(require("./set-step-success").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/steps/failure",
|
||||
transformBody(AdminPostWorkflowsAsyncResponseReq),
|
||||
middlewares.wrap(require("./set-step-failure").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/run",
|
||||
transformBody(AdminPostWorkflowsRunReq),
|
||||
middlewares.wrap(require("./run-workflow").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export * from "./query-config"
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const listConfig = req.listConfig
|
||||
|
||||
const [workflow_executions, count] =
|
||||
await workflowEngineService.listAndCountWorkflowExecution(
|
||||
req.filterableFields,
|
||||
{
|
||||
select: req.listConfig.select,
|
||||
relations: req.listConfig.relations,
|
||||
skip: listConfig.skip,
|
||||
take: listConfig.take,
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
workflow_executions,
|
||||
count,
|
||||
offset: listConfig.skip,
|
||||
limit: listConfig.take,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export const defaultAdminWorkflowExecutionsRelations = []
|
||||
export const allowedAdminWorkflowExecutionsRelations = []
|
||||
export const defaultAdminWorkflowExecutionsFields = [
|
||||
"id",
|
||||
"workflow_id",
|
||||
"transaction_id",
|
||||
"state",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const defaultAdminWorkflowExecutionDetailFields = [
|
||||
"id",
|
||||
"workflow_id",
|
||||
"transaction_id",
|
||||
"context",
|
||||
"execution",
|
||||
"state",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IWorkflowEngineService,
|
||||
WorkflowOrchestratorTypes,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
import { AdminPostWorkflowsRunReq } from "./validators"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const { id: workflow_id } = req.params
|
||||
|
||||
const { transaction_id, input } =
|
||||
req.validatedBody as AdminPostWorkflowsRunReq
|
||||
|
||||
const options = {
|
||||
transactionId: transaction_id,
|
||||
input,
|
||||
context: {
|
||||
requestId: req.requestId,
|
||||
},
|
||||
throwOnError: false,
|
||||
} as WorkflowOrchestratorTypes.WorkflowOrchestratorRunDTO
|
||||
|
||||
const { acknowledgement } = await workflowEngineService.run(
|
||||
workflow_id,
|
||||
options
|
||||
)
|
||||
|
||||
return res.status(200).json({ acknowledgement })
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { TransactionHandlerType, isDefined } from "@medusajs/utils"
|
||||
import { IWorkflowEngineService, StepResponse } from "@medusajs/workflows-sdk"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
import { AdminPostWorkflowsAsyncResponseReq } from "./validators"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const { id: workflow_id } = req.params
|
||||
|
||||
const body = req.validatedBody as AdminPostWorkflowsAsyncResponseReq
|
||||
|
||||
const { transaction_id, step_id } = body
|
||||
|
||||
const compensateInput = body.compensate_input
|
||||
const stepResponse = isDefined(body.response)
|
||||
? new StepResponse(body.response, compensateInput)
|
||||
: undefined
|
||||
const stepAction = body.action || TransactionHandlerType.INVOKE
|
||||
|
||||
await workflowEngineService.setStepFailure({
|
||||
idempotencyKey: {
|
||||
action: stepAction,
|
||||
transactionId: transaction_id,
|
||||
stepId: step_id,
|
||||
workflowId: workflow_id,
|
||||
},
|
||||
stepResponse,
|
||||
options: {
|
||||
container: req.scope,
|
||||
context: {
|
||||
requestId: req.requestId,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return res.status(200).json({ success: true })
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { TransactionHandlerType, isDefined } from "@medusajs/utils"
|
||||
import { IWorkflowEngineService, StepResponse } from "@medusajs/workflows-sdk"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
import { AdminPostWorkflowsAsyncResponseReq } from "./validators"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const { id: workflow_id } = req.params
|
||||
|
||||
const body = req.validatedBody as AdminPostWorkflowsAsyncResponseReq
|
||||
|
||||
const { transaction_id, step_id } = body
|
||||
|
||||
const compensateInput = body.compensate_input
|
||||
const stepResponse = isDefined(body.response)
|
||||
? new StepResponse(body.response, compensateInput)
|
||||
: undefined
|
||||
const stepAction = body.action || TransactionHandlerType.INVOKE
|
||||
|
||||
await workflowEngineService.setStepSuccess({
|
||||
idempotencyKey: {
|
||||
action: stepAction,
|
||||
transactionId: transaction_id,
|
||||
stepId: step_id,
|
||||
workflowId: workflow_id,
|
||||
},
|
||||
stepResponse,
|
||||
options: {
|
||||
container: req.scope,
|
||||
context: {
|
||||
requestId: req.requestId,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return res.status(200).json({ success: true })
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
|
||||
export default async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const { id: workflow_id, transaction_id } = req.query as any
|
||||
|
||||
const subscriberId = "__sub__" + Math.random().toString(36).substring(2, 9)
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
})
|
||||
|
||||
req.on("close", () => {
|
||||
res.end()
|
||||
|
||||
void workflowEngineService.unsubscribe({
|
||||
workflowId: workflow_id,
|
||||
transactionId: transaction_id,
|
||||
subscriberOrId: subscriberId,
|
||||
})
|
||||
})
|
||||
|
||||
req.on("error", (err: any) => {
|
||||
if (err.code === "ECONNRESET") {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
void workflowEngineService.subscribe({
|
||||
workflowId: workflow_id,
|
||||
transactionId: transaction_id,
|
||||
subscriber: async (args) => {
|
||||
const {
|
||||
eventType,
|
||||
workflowId,
|
||||
transactionId,
|
||||
step,
|
||||
response,
|
||||
result,
|
||||
errors,
|
||||
} = args
|
||||
|
||||
const data = {
|
||||
event_type: eventType,
|
||||
workflow_id: workflowId,
|
||||
transaction_id: transactionId,
|
||||
step,
|
||||
response,
|
||||
result,
|
||||
errors,
|
||||
}
|
||||
res.write(`event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`)
|
||||
},
|
||||
subscriberId,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { TransactionHandlerType } from "@medusajs/utils"
|
||||
import { Transform } from "class-transformer"
|
||||
import { IsEnum, IsOptional, IsString } from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../../types/common"
|
||||
import { IsType } from "../../../../utils"
|
||||
|
||||
export class AdminGetWorkflowExecutionDetailsParams extends FindParams {}
|
||||
|
||||
export class AdminGetWorkflowExecutionsParams extends extendedFindParamsMixin({
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
}) {
|
||||
/**
|
||||
* transaction id(s) to filter workflow executions by transaction_id.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
transaction_id?: string | string[]
|
||||
|
||||
/**
|
||||
* workflow id(s) to filter workflow executions by workflow_id
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
workflow_id?: string | string[]
|
||||
}
|
||||
|
||||
export class AdminPostWorkflowsRunReq {
|
||||
@IsOptional()
|
||||
input?: unknown
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
transaction_id?: string
|
||||
}
|
||||
|
||||
export class AdminPostWorkflowsAsyncResponseReq {
|
||||
@IsString()
|
||||
transaction_id: string
|
||||
|
||||
@IsString()
|
||||
step_id: string
|
||||
|
||||
@IsOptional()
|
||||
response?: unknown
|
||||
|
||||
@IsOptional()
|
||||
compensate_input?: unknown
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => (value + "").toLowerCase())
|
||||
@IsEnum(TransactionHandlerType)
|
||||
action?: TransactionHandlerType
|
||||
}
|
||||
Reference in New Issue
Block a user