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 parent
59ab66a773
commit
0c2a460751
44 files changed
+1410
-65
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/workflow-engine-inmemory": patch
|
||||||
|
"@medusajs/workflow-engine-redis": patch
|
||||||
|
"@medusajs/workflows-sdk": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Workflow engine API
|
||||||
@@ -25,5 +25,6 @@ module.exports = {
|
|||||||
resolve: "@medusajs/cache-inmemory",
|
resolve: "@medusajs/cache-inmemory",
|
||||||
options: { ttl: cacheTTL },
|
options: { ttl: cacheTTL },
|
||||||
},
|
},
|
||||||
|
workflows: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@medusajs/cache-inmemory": "workspace:*",
|
"@medusajs/cache-inmemory": "workspace:*",
|
||||||
"@medusajs/event-bus-local": "workspace:*",
|
"@medusajs/event-bus-local": "workspace:*",
|
||||||
"@medusajs/medusa": "workspace:*",
|
"@medusajs/medusa": "workspace:*",
|
||||||
|
"@medusajs/workflow-engine-inmemory": "workspace:*",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-interfaces": "workspace:*",
|
"medusa-interfaces": "workspace:*",
|
||||||
"pg": "^8.11.0",
|
"pg": "^8.11.0",
|
||||||
|
|||||||
@@ -88,7 +88,12 @@ const DbTestUtil = {
|
|||||||
const instance = DbTestUtil
|
const instance = DbTestUtil
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initDb: async function ({ cwd, database_extra, env }) {
|
initDb: async function ({
|
||||||
|
cwd,
|
||||||
|
database_extra,
|
||||||
|
env,
|
||||||
|
force_modules_migration,
|
||||||
|
}) {
|
||||||
if (isObject(env)) {
|
if (isObject(env)) {
|
||||||
Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
|
Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
|
||||||
}
|
}
|
||||||
@@ -149,7 +154,10 @@ module.exports = {
|
|||||||
|
|
||||||
instance.setDb(dbDataSource)
|
instance.setDb(dbDataSource)
|
||||||
|
|
||||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
if (
|
||||||
|
force_modules_migration ||
|
||||||
|
featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)
|
||||||
|
) {
|
||||||
const pgConnectionLoader =
|
const pgConnectionLoader =
|
||||||
require("@medusajs/medusa/dist/loaders/pg-connection").default
|
require("@medusajs/medusa/dist/loaders/pg-connection").default
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { workflowEngineTestSuite } from "./tests"
|
||||||
|
|
||||||
|
jest.setTimeout(5000000)
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
MEDUSA_FF_MEDUSA_V2: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowEngineTestSuite(env, { force_modules_migration: true })
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { workflowEngineTestSuite } from "./tests"
|
||||||
|
|
||||||
|
jest.setTimeout(5000000)
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
MEDUSA_FF_MEDUSA_V2: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowEngineTestSuite(env)
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
import { useApi } from "../../../environment-helpers/use-api"
|
||||||
|
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||||
|
|
||||||
|
import {
|
||||||
|
StepResponse,
|
||||||
|
WorkflowData,
|
||||||
|
createStep,
|
||||||
|
createWorkflow,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { AxiosInstance } from "axios"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
||||||
|
import { getContainer } from "../../../environment-helpers/use-container"
|
||||||
|
import adminSeeder from "../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
export const workflowEngineTestSuite = (env, extraParams = {}) => {
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: {
|
||||||
|
"x-medusa-access-token": "test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Workflow Engine API", () => {
|
||||||
|
let medusaContainer
|
||||||
|
let dbConnection
|
||||||
|
let shutdownServer
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env, ...extraParams } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
medusaContainer = getContainer()
|
||||||
|
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("running workflows", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const step1 = createStep(
|
||||||
|
{
|
||||||
|
name: "my-step",
|
||||||
|
},
|
||||||
|
async (input: { initial: string }) => {
|
||||||
|
return new StepResponse({
|
||||||
|
result: input.initial,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const step2 = createStep(
|
||||||
|
{
|
||||||
|
name: "my-step-async",
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
async () => {}
|
||||||
|
)
|
||||||
|
|
||||||
|
createWorkflow(
|
||||||
|
{
|
||||||
|
name: "my-workflow-name",
|
||||||
|
retentionTime: 1000,
|
||||||
|
},
|
||||||
|
function (input: WorkflowData<{ initial: string }>) {
|
||||||
|
step1(input)
|
||||||
|
return step2()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should list all workflows in execution or completed and retrieve them by id", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
for (let i = 3; i--; ) {
|
||||||
|
await api.post(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/run`,
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
initial: "abc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const executions = await api.get(
|
||||||
|
`/admin/workflows-executions`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(executions.data.count).toEqual(3)
|
||||||
|
expect(executions.data.workflow_executions.length).toEqual(3)
|
||||||
|
expect(executions.data.workflow_executions[0]).toEqual({
|
||||||
|
workflow_id: "my-workflow-name",
|
||||||
|
transaction_id: expect.any(String),
|
||||||
|
id: expect.any(String),
|
||||||
|
state: "invoking",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const retrivedById = await api.get(
|
||||||
|
`/admin/workflows-executions/` +
|
||||||
|
executions.data.workflow_executions[0].id,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
expect(retrivedById.data.workflow_execution).toEqual(
|
||||||
|
expect.objectContaining(executions.data.workflow_executions[0])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should list all workflows matching the filters", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
for (let i = 3; i--; ) {
|
||||||
|
await api.post(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/run`,
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
initial: "abc",
|
||||||
|
},
|
||||||
|
transaction_id: "transaction_" + (i + 1),
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const executions = await api.get(
|
||||||
|
`/admin/workflows-executions?transaction_id[]=transaction_1&transaction_id[]=transaction_2`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(executions.data.count).toEqual(2)
|
||||||
|
expect(executions.data.workflow_executions.length).toEqual(2)
|
||||||
|
expect(executions.data.workflow_executions[0]).toEqual({
|
||||||
|
workflow_id: "my-workflow-name",
|
||||||
|
transaction_id: expect.stringMatching(/transaction_1|transaction_2/),
|
||||||
|
id: expect.any(String),
|
||||||
|
state: "invoking",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
})
|
||||||
|
expect(executions.data.workflow_executions[1]).toEqual({
|
||||||
|
workflow_id: "my-workflow-name",
|
||||||
|
transaction_id: expect.stringMatching(/transaction_1|transaction_2/),
|
||||||
|
id: expect.any(String),
|
||||||
|
state: "invoking",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should execute a workflow and retrieve its execution while running and when it is completed", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
const wf = await api.post(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/run`,
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
initial: "abc",
|
||||||
|
},
|
||||||
|
transaction_id: "trx_123",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(wf.data).toEqual({
|
||||||
|
acknowledgement: {
|
||||||
|
transactionId: "trx_123",
|
||||||
|
workflowId: "my-workflow-name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const execution = await api.get(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/trx_123`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(execution.data).toEqual({
|
||||||
|
workflow_execution: expect.objectContaining({
|
||||||
|
workflow_id: "my-workflow-name",
|
||||||
|
transaction_id: "trx_123",
|
||||||
|
id: expect.any(String),
|
||||||
|
state: "invoking",
|
||||||
|
execution: expect.objectContaining({
|
||||||
|
hasAsyncSteps: true,
|
||||||
|
hasFailedSteps: false,
|
||||||
|
hasSkippedSteps: false,
|
||||||
|
hasWaitingSteps: false,
|
||||||
|
hasRevertedSteps: false,
|
||||||
|
}),
|
||||||
|
context: expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
invoke: {
|
||||||
|
"my-step": {
|
||||||
|
__type: "WorkflowWorkflowData",
|
||||||
|
output: {
|
||||||
|
__type: "WorkflowStepResponse",
|
||||||
|
output: {
|
||||||
|
result: "abc",
|
||||||
|
},
|
||||||
|
compensateInput: {
|
||||||
|
result: "abc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
initial: "abc",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const respondAsync = await api.post(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/steps/success`,
|
||||||
|
{
|
||||||
|
transaction_id: "trx_123",
|
||||||
|
step_id: "my-step-async",
|
||||||
|
response: {
|
||||||
|
all: "good",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(respondAsync.data.success).toEqual(true)
|
||||||
|
|
||||||
|
const completed = await api.get(
|
||||||
|
`/admin/workflows-executions/my-workflow-name/trx_123`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(completed.data).toEqual({
|
||||||
|
workflow_execution: expect.objectContaining({
|
||||||
|
workflow_id: "my-workflow-name",
|
||||||
|
transaction_id: "trx_123",
|
||||||
|
state: "done",
|
||||||
|
context: expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
invoke: expect.objectContaining({
|
||||||
|
"my-step-async": {
|
||||||
|
__type: "WorkflowStepResponse",
|
||||||
|
output: {
|
||||||
|
all: "good",
|
||||||
|
},
|
||||||
|
compensateInput: {
|
||||||
|
all: "good",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Noop test", () => {
|
||||||
|
it("noop check", async () => {
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -91,6 +91,7 @@ module.exports = {
|
|||||||
resources: "shared",
|
resources: "shared",
|
||||||
resolve: "@medusajs/cart",
|
resolve: "@medusajs/cart",
|
||||||
},
|
},
|
||||||
|
[Modules.WORKFLOW_ENGINE]: true,
|
||||||
[Modules.REGION]: {
|
[Modules.REGION]: {
|
||||||
scope: "internal",
|
scope: "internal",
|
||||||
resources: "shared",
|
resources: "shared",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@medusajs/promotion": "workspace:^",
|
"@medusajs/promotion": "workspace:^",
|
||||||
"@medusajs/region": "workspace:^",
|
"@medusajs/region": "workspace:^",
|
||||||
"@medusajs/utils": "workspace:^",
|
"@medusajs/utils": "workspace:^",
|
||||||
|
"@medusajs/workflow-engine-inmemory": "workspace:*",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-fulfillment-webshipper": "workspace:*",
|
"medusa-fulfillment-webshipper": "workspace:*",
|
||||||
"medusa-interfaces": "workspace:*",
|
"medusa-interfaces": "workspace:*",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
|
|
||||||
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const execution = await workflowEngineService.retrieveWorkflowExecution(id, {
|
||||||
|
select: req.retrieveConfig.select,
|
||||||
|
relations: req.retrieveConfig.relations,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
workflow_execution: execution,
|
||||||
|
})
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
|
import {
|
||||||
|
MedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../../../../types/routing"
|
||||||
|
|
||||||
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
|
|
||||||
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { workflow_id, transaction_id } = req.params
|
||||||
|
|
||||||
|
const execution = await workflowEngineService.retrieveWorkflowExecution(
|
||||||
|
{
|
||||||
|
workflow_id,
|
||||||
|
transaction_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: req.retrieveConfig.select,
|
||||||
|
relations: req.retrieveConfig.relations,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
workflow_execution: execution,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
IWorkflowEngineService,
|
||||||
|
WorkflowOrchestratorTypes,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
|
import { AdminPostWorkflowsRunReq } from "../../validators"
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { 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 })
|
||||||
|
}
|
||||||
+41
@@ -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 const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { 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 })
|
||||||
|
}
|
||||||
+41
@@ -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 const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { 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 })
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
|
|
||||||
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||||
|
)
|
||||||
|
|
||||||
|
const { workflow_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,
|
||||||
|
subscriberOrId: subscriberId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
req.on("error", (err: any) => {
|
||||||
|
if (err.code === "ECONNRESET") {
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
void workflowEngineService.subscribe({
|
||||||
|
workflowId: workflow_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,58 @@
|
|||||||
|
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||||
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
|
import * as QueryConfig from "./query-config"
|
||||||
|
import {
|
||||||
|
AdminGetWorkflowExecutionDetailsParams,
|
||||||
|
AdminGetWorkflowExecutionsParams,
|
||||||
|
AdminPostWorkflowsAsyncResponseReq,
|
||||||
|
AdminPostWorkflowsRunReq,
|
||||||
|
} from "./validators"
|
||||||
|
|
||||||
|
export const adminWorkflowsExecutionsMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/workflows-executions",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetWorkflowExecutionsParams,
|
||||||
|
QueryConfig.listTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/workflows-executions/:id",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetWorkflowExecutionDetailsParams,
|
||||||
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/workflows-executions/:workflow_id/:transaction_id",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetWorkflowExecutionDetailsParams,
|
||||||
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/workflows-executions/:id/run",
|
||||||
|
middlewares: [transformBody(AdminPostWorkflowsRunReq)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
|
||||||
|
matcher: "/admin/workflows-executions/:id/steps/success",
|
||||||
|
middlewares: [transformBody(AdminPostWorkflowsAsyncResponseReq)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/workflows-executions/:id/steps/failure",
|
||||||
|
middlewares: [transformBody(AdminPostWorkflowsAsyncResponseReq)],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const retrieveTransformQueryConfig = {
|
||||||
|
defaultFields: defaultAdminWorkflowExecutionDetailFields,
|
||||||
|
defaultRelations: defaultAdminWorkflowExecutionsRelations,
|
||||||
|
allowedRelations: allowedAdminWorkflowExecutionsRelations,
|
||||||
|
isList: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listTransformQueryConfig = {
|
||||||
|
...retrieveTransformQueryConfig,
|
||||||
|
defaultFields: defaultAdminWorkflowExecutionsFields,
|
||||||
|
isList: true,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||||
|
|
||||||
|
export const GET = 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,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
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/mid
|
|||||||
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||||
|
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
|
||||||
import { authRoutesMiddlewares } from "./auth/middlewares"
|
import { authRoutesMiddlewares } from "./auth/middlewares"
|
||||||
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
||||||
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
||||||
@@ -18,6 +19,7 @@ export const config: MiddlewaresConfig = {
|
|||||||
...storeCustomerRoutesMiddlewares,
|
...storeCustomerRoutesMiddlewares,
|
||||||
...storeCartRoutesMiddlewares,
|
...storeCartRoutesMiddlewares,
|
||||||
...authRoutesMiddlewares,
|
...authRoutesMiddlewares,
|
||||||
|
...adminWorkflowsExecutionsMiddlewares,
|
||||||
...storeRegionRoutesMiddlewares,
|
...storeRegionRoutesMiddlewares,
|
||||||
...adminRegionRoutesMiddlewares,
|
...adminRegionRoutesMiddlewares,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Router } from "express"
|
|
||||||
import errorHandler from "./middlewares/error-handler"
|
|
||||||
import compression from "compression"
|
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 admin from "./routes/admin"
|
||||||
import store from "./routes/store"
|
import store from "./routes/store"
|
||||||
import { shouldCompressResponse, compressionOptions } from "../utils/api"
|
|
||||||
|
|
||||||
// guaranteed to get dependencies
|
// guaranteed to get dependencies
|
||||||
export default (container, config) => {
|
export default (container, config) => {
|
||||||
@@ -53,9 +53,9 @@ export * from "./routes/admin/product-types"
|
|||||||
export * from "./routes/admin/products"
|
export * from "./routes/admin/products"
|
||||||
export * from "./routes/admin/publishable-api-keys"
|
export * from "./routes/admin/publishable-api-keys"
|
||||||
export * from "./routes/admin/regions"
|
export * from "./routes/admin/regions"
|
||||||
|
export * from "./routes/admin/reservations"
|
||||||
export * from "./routes/admin/return-reasons"
|
export * from "./routes/admin/return-reasons"
|
||||||
export * from "./routes/admin/returns"
|
export * from "./routes/admin/returns"
|
||||||
export * from "./routes/admin/reservations"
|
|
||||||
export * from "./routes/admin/sales-channels"
|
export * from "./routes/admin/sales-channels"
|
||||||
export * from "./routes/admin/shipping-options"
|
export * from "./routes/admin/shipping-options"
|
||||||
export * from "./routes/admin/shipping-profiles"
|
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/uploads"
|
||||||
export * from "./routes/admin/users"
|
export * from "./routes/admin/users"
|
||||||
export * from "./routes/admin/variants"
|
export * from "./routes/admin/variants"
|
||||||
|
export * from "./routes/admin/workflows-executions"
|
||||||
// Store
|
// Store
|
||||||
export * from "./routes/store/auth"
|
export * from "./routes/store/auth"
|
||||||
export * from "./routes/store/carts"
|
export * from "./routes/store/carts"
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import taxRateRoutes from "./tax-rates"
|
|||||||
import uploadRoutes from "./uploads"
|
import uploadRoutes from "./uploads"
|
||||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||||
import variantRoutes from "./variants"
|
import variantRoutes from "./variants"
|
||||||
|
import workflowRoutes from "./workflows-executions"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ export default (app, container, config) => {
|
|||||||
paymentCollectionRoutes(route)
|
paymentCollectionRoutes(route)
|
||||||
paymentRoutes(route)
|
paymentRoutes(route)
|
||||||
productCategoryRoutes(route)
|
productCategoryRoutes(route)
|
||||||
|
workflowRoutes(route)
|
||||||
|
|
||||||
return app
|
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
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MedusaApp } from "@medusajs/modules-sdk"
|
import { MedusaApp } from "@medusajs/modules-sdk"
|
||||||
import { RemoteJoinerQuery } from "@medusajs/types"
|
import { RemoteJoinerQuery } from "@medusajs/types"
|
||||||
import { TransactionHandlerType } from "@medusajs/utils"
|
import { TransactionHandlerType } from "@medusajs/utils"
|
||||||
import { IWorkflowsModuleService } from "@medusajs/workflows-sdk"
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
import { knex } from "knex"
|
import { knex } from "knex"
|
||||||
import { setTimeout } from "timers/promises"
|
import { setTimeout } from "timers/promises"
|
||||||
import "../__fixtures__"
|
import "../__fixtures__"
|
||||||
@@ -22,7 +22,7 @@ const afterEach_ = async () => {
|
|||||||
|
|
||||||
describe("Workflow Orchestrator module", function () {
|
describe("Workflow Orchestrator module", function () {
|
||||||
describe("Testing basic workflow", function () {
|
describe("Testing basic workflow", function () {
|
||||||
let workflowOrcModule: IWorkflowsModuleService
|
let workflowOrcModule: IWorkflowEngineService
|
||||||
let query: (
|
let query: (
|
||||||
query: string | RemoteJoinerQuery | object,
|
query: string | RemoteJoinerQuery | object,
|
||||||
variables?: Record<string, unknown>
|
variables?: Record<string, unknown>
|
||||||
@@ -52,8 +52,7 @@ describe("Workflow Orchestrator module", function () {
|
|||||||
|
|
||||||
await runMigrations()
|
await runMigrations()
|
||||||
|
|
||||||
workflowOrcModule =
|
workflowOrcModule = modules.workflows as unknown as IWorkflowEngineService
|
||||||
modules.workflows as unknown as IWorkflowsModuleService
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
MedusaModule,
|
|
||||||
MODULE_PACKAGE_NAMES,
|
MODULE_PACKAGE_NAMES,
|
||||||
|
MedusaModule,
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
import { ModulesSdkTypes } from "@medusajs/types"
|
import { ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { WorkflowOrchestratorTypes } from "@medusajs/workflows-sdk"
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
import { moduleDefinition } from "../module-definition"
|
import { moduleDefinition } from "../module-definition"
|
||||||
import { InitializeModuleInjectableDependencies } from "../types"
|
import { InitializeModuleInjectableDependencies } from "../types"
|
||||||
|
|
||||||
@@ -17,11 +17,10 @@ export const initialize = async (
|
|||||||
| ExternalModuleDeclaration
|
| ExternalModuleDeclaration
|
||||||
| InternalModuleDeclaration,
|
| InternalModuleDeclaration,
|
||||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||||
): Promise<WorkflowOrchestratorTypes.IWorkflowsModuleService> => {
|
): Promise<IWorkflowEngineService> => {
|
||||||
const loaded =
|
const loaded =
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
await MedusaModule.bootstrap<WorkflowOrchestratorTypes.IWorkflowsModuleService>(
|
await MedusaModule.bootstrap<IWorkflowEngineService>({
|
||||||
{
|
|
||||||
moduleKey: Modules.WORKFLOW_ENGINE,
|
moduleKey: Modules.WORKFLOW_ENGINE,
|
||||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE],
|
defaultPath: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE],
|
||||||
declaration: options as
|
declaration: options as
|
||||||
@@ -29,8 +28,7 @@ export const initialize = async (
|
|||||||
| ExternalModuleDeclaration,
|
| ExternalModuleDeclaration,
|
||||||
injectedDependencies,
|
injectedDependencies,
|
||||||
moduleExports: moduleDefinition,
|
moduleExports: moduleDefinition,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return loaded[Modules.WORKFLOW_ENGINE]
|
return loaded[Modules.WORKFLOW_ENGINE]
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,11 @@ import {
|
|||||||
InjectManager,
|
InjectManager,
|
||||||
InjectSharedContext,
|
InjectSharedContext,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
|
MedusaError,
|
||||||
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import type {
|
import type {
|
||||||
|
IWorkflowEngineService,
|
||||||
ReturnWorkflow,
|
ReturnWorkflow,
|
||||||
UnwrapWorkflowInputDataType,
|
UnwrapWorkflowInputDataType,
|
||||||
WorkflowOrchestratorTypes,
|
WorkflowOrchestratorTypes,
|
||||||
@@ -25,9 +28,7 @@ type InjectedDependencies = {
|
|||||||
workflowOrchestratorService: WorkflowOrchestratorService
|
workflowOrchestratorService: WorkflowOrchestratorService
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowsModuleService
|
export class WorkflowsModuleService implements IWorkflowEngineService {
|
||||||
implements WorkflowOrchestratorTypes.IWorkflowsModuleService
|
|
||||||
{
|
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
protected workflowExecutionService_: ModulesSdkTypes.InternalModuleService<any>
|
protected workflowExecutionService_: ModulesSdkTypes.InternalModuleService<any>
|
||||||
protected workflowOrchestratorService_: WorkflowOrchestratorService
|
protected workflowOrchestratorService_: WorkflowOrchestratorService
|
||||||
@@ -49,12 +50,70 @@ export class WorkflowsModuleService
|
|||||||
return joinerConfig
|
return joinerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async retrieveWorkflowExecution(
|
||||||
|
idOrObject:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
workflow_id: string
|
||||||
|
transaction_id: string
|
||||||
|
},
|
||||||
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO> {
|
||||||
|
const objValue = isString(idOrObject)
|
||||||
|
? { id: idOrObject }
|
||||||
|
: {
|
||||||
|
workflow_id: idOrObject.workflow_id,
|
||||||
|
transaction_id: idOrObject.transaction_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wfExecution = await this.workflowExecutionService_.list(
|
||||||
|
objValue,
|
||||||
|
config,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (wfExecution.length === 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`WorkflowExecution with ${Object.keys(objValue).join(
|
||||||
|
", "
|
||||||
|
)}: ${Object.values(objValue).join(", ")} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
return await this.baseRepository_.serialize<WorkflowOrchestratorTypes.WorkflowExecutionDTO>(
|
||||||
|
wfExecution[0],
|
||||||
|
{
|
||||||
|
populate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async listWorkflowExecution(
|
async listWorkflowExecution(
|
||||||
filters: WorkflowOrchestratorTypes.FilterableWorkflowExecutionProps = {},
|
filters: WorkflowOrchestratorTypes.FilterableWorkflowExecutionProps = {},
|
||||||
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO[]> {
|
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO[]> {
|
||||||
|
if (filters.transaction_id) {
|
||||||
|
if (Array.isArray(filters.transaction_id)) {
|
||||||
|
filters.transaction_id = {
|
||||||
|
$in: filters.transaction_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.workflow_id) {
|
||||||
|
if (Array.isArray(filters.workflow_id)) {
|
||||||
|
filters.workflow_id = {
|
||||||
|
$in: filters.workflow_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const wfExecutions = await this.workflowExecutionService_.list(
|
const wfExecutions = await this.workflowExecutionService_.list(
|
||||||
filters,
|
filters,
|
||||||
config,
|
config,
|
||||||
@@ -74,6 +133,22 @@ export class WorkflowsModuleService
|
|||||||
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<[WorkflowOrchestratorTypes.WorkflowExecutionDTO[], number]> {
|
): Promise<[WorkflowOrchestratorTypes.WorkflowExecutionDTO[], number]> {
|
||||||
|
if (filters.transaction_id) {
|
||||||
|
if (Array.isArray(filters.transaction_id)) {
|
||||||
|
filters.transaction_id = {
|
||||||
|
$in: filters.transaction_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.workflow_id) {
|
||||||
|
if (Array.isArray(filters.workflow_id)) {
|
||||||
|
filters.workflow_id = {
|
||||||
|
$in: filters.workflow_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [wfExecutions, count] =
|
const [wfExecutions, count] =
|
||||||
await this.workflowExecutionService_.listAndCount(
|
await this.workflowExecutionService_.listAndCount(
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
TransactionCheckpoint,
|
TransactionCheckpoint,
|
||||||
TransactionStep,
|
TransactionStep,
|
||||||
} from "@medusajs/orchestration"
|
} from "@medusajs/orchestration"
|
||||||
|
import { ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { TransactionState } from "@medusajs/utils"
|
import { TransactionState } from "@medusajs/utils"
|
||||||
import { WorkflowOrchestratorService } from "@services"
|
import { WorkflowOrchestratorService } from "@services"
|
||||||
import { ModulesSdkTypes } from "@medusajs/types"
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export class InMemoryDistributedTransactionStorage extends DistributedTransactionStorage {
|
export class InMemoryDistributedTransactionStorage extends DistributedTransactionStorage {
|
||||||
@@ -55,6 +55,22 @@ export class InMemoryDistributedTransactionStorage extends DistributedTransactio
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private stringifyWithSymbol(key, value) {
|
||||||
|
if (key === "__type" && typeof value === "symbol") {
|
||||||
|
return Symbol.keyFor(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
private jsonWithSymbol(key, value) {
|
||||||
|
if (key === "__type" && typeof value === "string") {
|
||||||
|
return Symbol.for(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<TransactionCheckpoint | undefined> {
|
async get(key: string): Promise<TransactionCheckpoint | undefined> {
|
||||||
return this.storage.get(key)
|
return this.storage.get(key)
|
||||||
}
|
}
|
||||||
@@ -89,10 +105,13 @@ export class InMemoryDistributedTransactionStorage extends DistributedTransactio
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifiedData = JSON.stringify(data, this.stringifyWithSymbol)
|
||||||
|
const parsedData = JSON.parse(stringifiedData)
|
||||||
|
|
||||||
if (hasFinished && !retentionTime) {
|
if (hasFinished && !retentionTime) {
|
||||||
await this.deleteFromDb(data)
|
await this.deleteFromDb(parsedData)
|
||||||
} else {
|
} else {
|
||||||
await this.saveToDb(data)
|
await this.saveToDb(parsedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFinished) {
|
if (hasFinished) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from "@medusajs/orchestration"
|
} from "@medusajs/orchestration"
|
||||||
import { RemoteJoinerQuery } from "@medusajs/types"
|
import { RemoteJoinerQuery } from "@medusajs/types"
|
||||||
import { TransactionHandlerType } from "@medusajs/utils"
|
import { TransactionHandlerType } from "@medusajs/utils"
|
||||||
import { IWorkflowsModuleService } from "@medusajs/workflows-sdk"
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
import { knex } from "knex"
|
import { knex } from "knex"
|
||||||
import { setTimeout } from "timers/promises"
|
import { setTimeout } from "timers/promises"
|
||||||
import "../__fixtures__"
|
import "../__fixtures__"
|
||||||
@@ -26,7 +26,7 @@ const afterEach_ = async () => {
|
|||||||
|
|
||||||
describe("Workflow Orchestrator module", function () {
|
describe("Workflow Orchestrator module", function () {
|
||||||
describe("Testing basic workflow", function () {
|
describe("Testing basic workflow", function () {
|
||||||
let workflowOrcModule: IWorkflowsModuleService
|
let workflowOrcModule: IWorkflowEngineService
|
||||||
let query: (
|
let query: (
|
||||||
query: string | RemoteJoinerQuery | object,
|
query: string | RemoteJoinerQuery | object,
|
||||||
variables?: Record<string, unknown>
|
variables?: Record<string, unknown>
|
||||||
@@ -61,8 +61,7 @@ describe("Workflow Orchestrator module", function () {
|
|||||||
|
|
||||||
await runMigrations()
|
await runMigrations()
|
||||||
|
|
||||||
workflowOrcModule =
|
workflowOrcModule = modules.workflows as unknown as IWorkflowEngineService
|
||||||
modules.workflows as unknown as IWorkflowsModuleService
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
MedusaModule,
|
|
||||||
MODULE_PACKAGE_NAMES,
|
MODULE_PACKAGE_NAMES,
|
||||||
|
MedusaModule,
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
import { ModulesSdkTypes } from "@medusajs/types"
|
import { ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { WorkflowOrchestratorTypes } from "@medusajs/workflows-sdk"
|
import { IWorkflowEngineService } from "@medusajs/workflows-sdk"
|
||||||
import { moduleDefinition } from "../module-definition"
|
import { moduleDefinition } from "../module-definition"
|
||||||
import { InitializeModuleInjectableDependencies } from "../types"
|
import { InitializeModuleInjectableDependencies } from "../types"
|
||||||
|
|
||||||
@@ -17,11 +18,10 @@ export const initialize = async (
|
|||||||
| ExternalModuleDeclaration
|
| ExternalModuleDeclaration
|
||||||
| InternalModuleDeclaration,
|
| InternalModuleDeclaration,
|
||||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||||
): Promise<WorkflowOrchestratorTypes.IWorkflowsModuleService> => {
|
): Promise<IWorkflowEngineService> => {
|
||||||
const loaded =
|
const loaded =
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
await MedusaModule.bootstrap<WorkflowOrchestratorTypes.IWorkflowsModuleService>(
|
await MedusaModule.bootstrap<IWorkflowEngineService>({
|
||||||
{
|
|
||||||
moduleKey: Modules.WORKFLOW_ENGINE,
|
moduleKey: Modules.WORKFLOW_ENGINE,
|
||||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE],
|
defaultPath: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE],
|
||||||
declaration: options as
|
declaration: options as
|
||||||
@@ -29,8 +29,7 @@ export const initialize = async (
|
|||||||
| ExternalModuleDeclaration,
|
| ExternalModuleDeclaration,
|
||||||
injectedDependencies,
|
injectedDependencies,
|
||||||
moduleExports: moduleDefinition,
|
moduleExports: moduleDefinition,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return loaded[Modules.WORKFLOW_ENGINE]
|
return loaded[Modules.WORKFLOW_ENGINE]
|
||||||
}
|
}
|
||||||
@@ -10,14 +10,17 @@ import {
|
|||||||
InjectManager,
|
InjectManager,
|
||||||
InjectSharedContext,
|
InjectSharedContext,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
|
MedusaError,
|
||||||
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import type {
|
import type {
|
||||||
|
IWorkflowEngineService,
|
||||||
ReturnWorkflow,
|
ReturnWorkflow,
|
||||||
UnwrapWorkflowInputDataType,
|
UnwrapWorkflowInputDataType,
|
||||||
WorkflowOrchestratorTypes,
|
WorkflowOrchestratorTypes,
|
||||||
} from "@medusajs/workflows-sdk"
|
} from "@medusajs/workflows-sdk"
|
||||||
import {WorkflowOrchestratorService} from "@services"
|
import { WorkflowOrchestratorService } from "@services"
|
||||||
import {joinerConfig} from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
@@ -25,9 +28,7 @@ type InjectedDependencies = {
|
|||||||
workflowOrchestratorService: WorkflowOrchestratorService
|
workflowOrchestratorService: WorkflowOrchestratorService
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowsModuleService
|
export class WorkflowsModuleService implements IWorkflowEngineService {
|
||||||
implements WorkflowOrchestratorTypes.IWorkflowsModuleService
|
|
||||||
{
|
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
protected workflowExecutionService_: ModulesSdkTypes.InternalModuleService<any>
|
protected workflowExecutionService_: ModulesSdkTypes.InternalModuleService<any>
|
||||||
protected workflowOrchestratorService_: WorkflowOrchestratorService
|
protected workflowOrchestratorService_: WorkflowOrchestratorService
|
||||||
@@ -49,12 +50,70 @@ export class WorkflowsModuleService
|
|||||||
return joinerConfig
|
return joinerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async retrieveWorkflowExecution(
|
||||||
|
idOrObject:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
workflow_id: string
|
||||||
|
transaction_id: string
|
||||||
|
},
|
||||||
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO> {
|
||||||
|
const objValue = isString(idOrObject)
|
||||||
|
? { id: idOrObject }
|
||||||
|
: {
|
||||||
|
workflow_id: idOrObject.workflow_id,
|
||||||
|
transaction_id: idOrObject.transaction_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wfExecution = await this.workflowExecutionService_.list(
|
||||||
|
objValue,
|
||||||
|
config,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (wfExecution.length === 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`WorkflowExecution with ${Object.keys(objValue).join(
|
||||||
|
", "
|
||||||
|
)}: ${Object.values(objValue).join(", ")} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
return await this.baseRepository_.serialize<WorkflowOrchestratorTypes.WorkflowExecutionDTO>(
|
||||||
|
wfExecution[0],
|
||||||
|
{
|
||||||
|
populate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async listWorkflowExecution(
|
async listWorkflowExecution(
|
||||||
filters: WorkflowOrchestratorTypes.FilterableWorkflowExecutionProps = {},
|
filters: WorkflowOrchestratorTypes.FilterableWorkflowExecutionProps = {},
|
||||||
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO[]> {
|
): Promise<WorkflowOrchestratorTypes.WorkflowExecutionDTO[]> {
|
||||||
|
if (filters.transaction_id) {
|
||||||
|
if (Array.isArray(filters.transaction_id)) {
|
||||||
|
filters.transaction_id = {
|
||||||
|
$in: filters.transaction_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.workflow_id) {
|
||||||
|
if (Array.isArray(filters.workflow_id)) {
|
||||||
|
filters.workflow_id = {
|
||||||
|
$in: filters.workflow_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const wfExecutions = await this.workflowExecutionService_.list(
|
const wfExecutions = await this.workflowExecutionService_.list(
|
||||||
filters,
|
filters,
|
||||||
config,
|
config,
|
||||||
@@ -74,6 +133,22 @@ export class WorkflowsModuleService
|
|||||||
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
config: FindConfig<WorkflowOrchestratorTypes.WorkflowExecutionDTO> = {},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<[WorkflowOrchestratorTypes.WorkflowExecutionDTO[], number]> {
|
): Promise<[WorkflowOrchestratorTypes.WorkflowExecutionDTO[], number]> {
|
||||||
|
if (filters.transaction_id) {
|
||||||
|
if (Array.isArray(filters.transaction_id)) {
|
||||||
|
filters.transaction_id = {
|
||||||
|
$in: filters.transaction_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.workflow_id) {
|
||||||
|
if (Array.isArray(filters.workflow_id)) {
|
||||||
|
filters.workflow_id = {
|
||||||
|
$in: filters.workflow_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [wfExecutions, count] =
|
const [wfExecutions, count] =
|
||||||
await this.workflowExecutionService_.listAndCount(
|
await this.workflowExecutionService_.listAndCount(
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import {
|
|||||||
TransactionCheckpoint,
|
TransactionCheckpoint,
|
||||||
TransactionStep,
|
TransactionStep,
|
||||||
} from "@medusajs/orchestration"
|
} from "@medusajs/orchestration"
|
||||||
import { TransactionState } from "@medusajs/utils"
|
|
||||||
import { ModulesSdkTypes } from "@medusajs/types"
|
import { ModulesSdkTypes } from "@medusajs/types"
|
||||||
import {
|
import { TransactionState } from "@medusajs/utils"
|
||||||
WorkflowOrchestratorService,
|
import { WorkflowOrchestratorService } from "@services"
|
||||||
} from "@services"
|
|
||||||
import { Queue, Worker } from "bullmq"
|
import { Queue, Worker } from "bullmq"
|
||||||
import Redis from "ioredis"
|
import Redis from "ioredis"
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ export class RedisDistributedTransactionStorage extends DistributedTransactionSt
|
|||||||
redisWorkerConnection,
|
redisWorkerConnection,
|
||||||
redisQueueName,
|
redisQueueName,
|
||||||
}: {
|
}: {
|
||||||
workflowExecutionService: ModulesSdkTypes.InternalModuleService<any>,
|
workflowExecutionService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
redisConnection: Redis
|
redisConnection: Redis
|
||||||
redisWorkerConnection: Redis
|
redisWorkerConnection: Redis
|
||||||
redisQueueName: string
|
redisQueueName: string
|
||||||
@@ -161,33 +159,28 @@ export class RedisDistributedTransactionStorage extends DistributedTransactionSt
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifiedData = JSON.stringify(data, this.stringifyWithSymbol)
|
||||||
|
const parsedData = JSON.parse(stringifiedData)
|
||||||
|
|
||||||
if (!hasFinished) {
|
if (!hasFinished) {
|
||||||
if (ttl) {
|
if (ttl) {
|
||||||
await this.redisClient.set(
|
await this.redisClient.set(key, stringifiedData, "EX", ttl)
|
||||||
key,
|
|
||||||
JSON.stringify(data, this.stringifyWithSymbol),
|
|
||||||
"EX",
|
|
||||||
ttl
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
await this.redisClient.set(
|
await this.redisClient.set(key, stringifiedData)
|
||||||
key,
|
|
||||||
JSON.stringify(data, this.stringifyWithSymbol)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFinished && !retentionTime) {
|
if (hasFinished && !retentionTime) {
|
||||||
await this.deleteFromDb(data)
|
await this.deleteFromDb(parsedData)
|
||||||
} else {
|
} else {
|
||||||
await this.saveToDb(data)
|
await this.saveToDb(parsedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFinished) {
|
if (hasFinished) {
|
||||||
// await this.redisClient.del(key)
|
// await this.redisClient.del(key)
|
||||||
await this.redisClient.set(
|
await this.redisClient.set(
|
||||||
key,
|
key,
|
||||||
JSON.stringify(data, this.stringifyWithSymbol),
|
stringifiedData,
|
||||||
"EX",
|
"EX",
|
||||||
RedisDistributedTransactionStorage.TTL_AFTER_COMPLETED
|
RedisDistributedTransactionStorage.TTL_AFTER_COMPLETED
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from "./helper"
|
export * from "./helper"
|
||||||
export * from "./medusa-workflow"
|
export * from "./medusa-workflow"
|
||||||
export * as WorkflowOrchestratorTypes from "./types"
|
export * as WorkflowOrchestratorTypes from "./types"
|
||||||
|
export { IWorkflowEngineService } from "./types/service"
|
||||||
export * from "./utils/composer"
|
export * from "./utils/composer"
|
||||||
export * as Composer from "./utils/composer"
|
export * as Composer from "./utils/composer"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BaseFilterable } from "@medusajs/types"
|
import { BaseFilterable, OperatorMap } from "@medusajs/types"
|
||||||
|
|
||||||
export interface WorkflowExecutionDTO {
|
export interface WorkflowExecutionDTO {
|
||||||
id: string
|
id: string
|
||||||
@@ -14,8 +14,8 @@ export interface WorkflowExecutionDTO {
|
|||||||
|
|
||||||
export interface FilterableWorkflowExecutionProps
|
export interface FilterableWorkflowExecutionProps
|
||||||
extends BaseFilterable<FilterableWorkflowExecutionProps> {
|
extends BaseFilterable<FilterableWorkflowExecutionProps> {
|
||||||
id?: string[]
|
id?: string | string[] | OperatorMap<string>
|
||||||
workflow_id?: string[]
|
workflow_id?: string | string[] | OperatorMap<string>
|
||||||
transaction_id?: string[]
|
transaction_id?: string | string[] | OperatorMap<string>
|
||||||
state?: any[]
|
state?: string | string[] | OperatorMap<string>
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,18 @@ export type IdempotencyKeyParts = {
|
|||||||
action: "invoke" | "compensate"
|
action: "invoke" | "compensate"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowsModuleService extends IModuleService {
|
export interface IWorkflowEngineService extends IModuleService {
|
||||||
|
retrieveWorkflowExecution(
|
||||||
|
idOrObject:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
workflow_id: string
|
||||||
|
transaction_id: string
|
||||||
|
},
|
||||||
|
config?: FindConfig<WorkflowExecutionDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<WorkflowExecutionDTO>
|
||||||
|
|
||||||
listWorkflowExecution(
|
listWorkflowExecution(
|
||||||
filters?: FilterableWorkflowExecutionProps,
|
filters?: FilterableWorkflowExecutionProps,
|
||||||
config?: FindConfig<WorkflowExecutionDTO>,
|
config?: FindConfig<WorkflowExecutionDTO>,
|
||||||
@@ -88,7 +99,7 @@ export interface IWorkflowsModuleService extends IModuleService {
|
|||||||
stepResponse,
|
stepResponse,
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
idempotencyKey: string | object
|
idempotencyKey: string | IdempotencyKeyParts
|
||||||
stepResponse: unknown
|
stepResponse: unknown
|
||||||
options?: Record<string, any>
|
options?: Record<string, any>
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8876,7 +8876,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/workflow-engine-inmemory@workspace:packages/workflow-engine-inmemory":
|
"@medusajs/workflow-engine-inmemory@workspace:*, @medusajs/workflow-engine-inmemory@workspace:packages/workflow-engine-inmemory":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/workflow-engine-inmemory@workspace:packages/workflow-engine-inmemory"
|
resolution: "@medusajs/workflow-engine-inmemory@workspace:packages/workflow-engine-inmemory"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -31546,6 +31546,7 @@ __metadata:
|
|||||||
"@medusajs/cache-inmemory": "workspace:*"
|
"@medusajs/cache-inmemory": "workspace:*"
|
||||||
"@medusajs/event-bus-local": "workspace:*"
|
"@medusajs/event-bus-local": "workspace:*"
|
||||||
"@medusajs/medusa": "workspace:*"
|
"@medusajs/medusa": "workspace:*"
|
||||||
|
"@medusajs/workflow-engine-inmemory": "workspace:*"
|
||||||
babel-preset-medusa-package: "*"
|
babel-preset-medusa-package: "*"
|
||||||
faker: ^5.5.3
|
faker: ^5.5.3
|
||||||
jest: ^26.6.3
|
jest: ^26.6.3
|
||||||
@@ -31576,6 +31577,7 @@ __metadata:
|
|||||||
"@medusajs/region": "workspace:^"
|
"@medusajs/region": "workspace:^"
|
||||||
"@medusajs/types": "workspace:^"
|
"@medusajs/types": "workspace:^"
|
||||||
"@medusajs/utils": "workspace:^"
|
"@medusajs/utils": "workspace:^"
|
||||||
|
"@medusajs/workflow-engine-inmemory": "workspace:*"
|
||||||
babel-preset-medusa-package: "*"
|
babel-preset-medusa-package: "*"
|
||||||
faker: ^5.5.3
|
faker: ^5.5.3
|
||||||
jest: ^26.6.3
|
jest: ^26.6.3
|
||||||
|
|||||||
Reference in new issue
Block a user