UNPKG

@medusajs/workflow-engine-inmemory

Version:

Medusa Workflow Orchestrator module

463 lines • 19.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowOrchestratorService = void 0; const orchestration_1 = require("@medusajs/framework/orchestration"); const utils_1 = require("@medusajs/framework/utils"); const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk"); const ulid_1 = require("ulid"); const AnySubscriber = "any"; class WorkflowOrchestratorService { constructor({ inMemoryDistributedTransactionStorage, sharedContainer, }) { this.subscribers = new Map(); this.container_ = sharedContainer; this.inMemoryDistributedTransactionStorage_ = inMemoryDistributedTransactionStorage; inMemoryDistributedTransactionStorage.setWorkflowOrchestratorService(this); orchestration_1.DistributedTransaction.setStorage(inMemoryDistributedTransactionStorage); orchestration_1.WorkflowScheduler.setStorage(inMemoryDistributedTransactionStorage); } async onApplicationStart() { await this.inMemoryDistributedTransactionStorage_.onApplicationStart(); } async onApplicationShutdown() { await this.inMemoryDistributedTransactionStorage_.onApplicationShutdown(); } async triggerParentStep(transaction, result) { const metadata = transaction.flow.metadata; const { parentStepIdempotencyKey } = metadata ?? {}; if (parentStepIdempotencyKey) { const hasFailed = [ utils_1.TransactionState.REVERTED, utils_1.TransactionState.FAILED, ].includes(transaction.flow.state); if (hasFailed) { await this.setStepFailure({ idempotencyKey: parentStepIdempotencyKey, stepResponse: result, }); } else { await this.setStepSuccess({ idempotencyKey: parentStepIdempotencyKey, stepResponse: result, }); } } } async run(workflowIdOrWorkflow, options) { const { input, transactionId, resultFrom, logOnError, events: eventHandlers, container, } = options ?? {}; let { throwOnError, context } = options ?? {}; throwOnError ??= true; context ??= {}; context.transactionId = transactionId ?? "auto-" + (0, ulid_1.ulid)(); const workflowId = (0, utils_1.isString)(workflowIdOrWorkflow) ? workflowIdOrWorkflow : workflowIdOrWorkflow.getName(); if (!workflowId) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Workflow ID is required`); } const events = this.buildWorkflowEvents({ customEventHandlers: eventHandlers, workflowId, transactionId: context.transactionId, }); const exportedWorkflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId); if (!exportedWorkflow) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Workflow with id "${workflowId}" not found.`); } const ret = await exportedWorkflow.run({ input, throwOnError: false, logOnError, resultFrom, context, events, container: container ?? this.container_, }); const hasFinished = ret.transaction.hasFinished(); const metadata = ret.transaction.getFlow().metadata; const { parentStepIdempotencyKey } = metadata ?? {}; const hasFailed = [ utils_1.TransactionState.REVERTED, utils_1.TransactionState.FAILED, ].includes(ret.transaction.getFlow().state); const acknowledgement = { transactionId: context.transactionId, workflowId: workflowId, parentStepIdempotencyKey, hasFinished, hasFailed, }; if (ret.transaction.hasFinished()) { const { result, errors } = ret; this.notify({ eventType: "onFinish", workflowId, transactionId: context.transactionId, result, errors, }); await this.triggerParentStep(ret.transaction, result); } if (throwOnError && (ret.thrownError || ret.errors?.length)) { if (ret.thrownError) { throw ret.thrownError; } throw ret.errors[0].error; } return { acknowledgement, ...ret }; } async cancel(workflowIdOrWorkflow, options) { const { transactionId, logOnError, events: eventHandlers, container, } = options ?? {}; let { throwOnError, context } = options ?? {}; throwOnError ??= true; context ??= {}; const workflowId = (0, utils_1.isString)(workflowIdOrWorkflow) ? workflowIdOrWorkflow : workflowIdOrWorkflow.getName(); if (!workflowId) { throw new Error("Workflow ID is required"); } if (!transactionId) { throw new Error("Transaction ID is required"); } const events = this.buildWorkflowEvents({ customEventHandlers: eventHandlers, workflowId, transactionId: transactionId, }); const exportedWorkflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId); if (!exportedWorkflow) { throw new Error(`Workflow with id "${workflowId}" not found.`); } const originalOnFinishHandler = events.onFinish; delete events.onFinish; const transaction = await this.getRunningTransaction(workflowId, transactionId, { ...options, isCancelling: true, }); if (!transaction) { if (!throwOnError) { return { acknowledgement: { transactionId, workflowId, exists: false, }, }; } throw new Error("Transaction not found"); } const ret = await exportedWorkflow.cancel({ transaction, throwOnError: false, logOnError, context, events, container: container ?? this.container_, }); const hasFinished = ret.transaction.hasFinished(); const metadata = ret.transaction.getFlow().metadata; const { parentStepIdempotencyKey } = metadata ?? {}; const hasFailed = [utils_1.TransactionState.FAILED].includes(ret.transaction.getFlow().state); const acknowledgement = { transactionId: context.transactionId, workflowId: workflowId, parentStepIdempotencyKey, hasFinished, hasFailed, exists: true, }; if (hasFinished) { const { result, errors } = ret; await originalOnFinishHandler({ transaction: ret.transaction, result, errors, }); await this.triggerParentStep(ret.transaction, result); } if (throwOnError && (ret.thrownError || ret.errors?.length)) { if (ret.thrownError) { throw ret.thrownError; } throw ret.errors[0].error; } return { acknowledgement, ...ret }; } async getRunningTransaction(workflowId, transactionId, context) { if (!workflowId) { throw new Error("Workflow ID is required"); } if (!transactionId) { throw new Error("TransactionId ID is required"); } context ??= {}; context.transactionId ??= transactionId; const exportedWorkflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId); if (!exportedWorkflow) { throw new Error(`Workflow with id "${workflowId}" not found.`); } const flow = exportedWorkflow(); const transaction = await flow.getRunningTransaction(transactionId, context); return transaction; } async setStepSuccess({ idempotencyKey, stepResponse, options, }) { const { context, logOnError, resultFrom, container, events: eventHandlers, } = options ?? {}; let { throwOnError } = options ?? {}; throwOnError ??= true; const [idempotencyKey_, { workflowId, transactionId }] = this.buildIdempotencyKeyAndParts(idempotencyKey); const exportedWorkflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId); if (!exportedWorkflow) { throw new Error(`Workflow with id "${workflowId}" not found.`); } const events = this.buildWorkflowEvents({ customEventHandlers: eventHandlers, transactionId, workflowId, }); const ret = await exportedWorkflow.registerStepSuccess({ idempotencyKey: idempotencyKey_, context, resultFrom, throwOnError: false, logOnError, events, response: stepResponse, container: container ?? this.container_, }); if (ret.transaction.hasFinished()) { const { result, errors } = ret; this.notify({ eventType: "onFinish", workflowId, transactionId, result, errors, }); await this.triggerParentStep(ret.transaction, result); } if (throwOnError && (ret.thrownError || ret.errors?.length)) { if (ret.thrownError) { throw ret.thrownError; } throw ret.errors[0].error; } return ret; } async setStepFailure({ idempotencyKey, stepResponse, options, }) { const { context, logOnError, resultFrom, container, events: eventHandlers, } = options ?? {}; let { throwOnError } = options ?? {}; throwOnError ??= true; const [idempotencyKey_, { workflowId, transactionId }] = this.buildIdempotencyKeyAndParts(idempotencyKey); const exportedWorkflow = workflows_sdk_1.MedusaWorkflow.getWorkflow(workflowId); if (!exportedWorkflow) { throw new Error(`Workflow with id "${workflowId}" not found.`); } const events = this.buildWorkflowEvents({ customEventHandlers: eventHandlers, transactionId, workflowId, }); const ret = await exportedWorkflow.registerStepFailure({ idempotencyKey: idempotencyKey_, context, resultFrom, throwOnError: false, logOnError, events, response: stepResponse, container: container ?? this.container_, }); if (ret.transaction.hasFinished()) { const { result, errors } = ret; this.notify({ eventType: "onFinish", workflowId, transactionId, result, errors, }); await this.triggerParentStep(ret.transaction, result); } if (throwOnError && (ret.thrownError || ret.errors?.length)) { if (ret.thrownError) { throw ret.thrownError; } throw ret.errors[0].error; } return ret; } subscribe({ workflowId, transactionId, subscriber, subscriberId, }) { subscriber._id = subscriberId; const subscribers = this.subscribers.get(workflowId) ?? new Map(); const handlerIndex = (handlers) => { return handlers.indexOf((s) => s === subscriber || s._id === subscriberId); }; if (transactionId) { const transactionSubscribers = subscribers.get(transactionId) ?? []; const subscriberIndex = handlerIndex(transactionSubscribers); if (subscriberIndex !== -1) { transactionSubscribers.slice(subscriberIndex, 1); } transactionSubscribers.push(subscriber); subscribers.set(transactionId, transactionSubscribers); this.subscribers.set(workflowId, subscribers); return; } const workflowSubscribers = subscribers.get(AnySubscriber) ?? []; const subscriberIndex = handlerIndex(workflowSubscribers); if (subscriberIndex !== -1) { workflowSubscribers.slice(subscriberIndex, 1); } workflowSubscribers.push(subscriber); subscribers.set(AnySubscriber, workflowSubscribers); this.subscribers.set(workflowId, subscribers); } unsubscribe({ workflowId, transactionId, subscriberOrId, }) { const subscribers = this.subscribers.get(workflowId) ?? new Map(); const filterSubscribers = (handlers) => { return handlers.filter((handler) => { return handler._id ? handler._id !== subscriberOrId : handler !== subscriberOrId; }); }; if (transactionId) { const transactionSubscribers = subscribers.get(transactionId) ?? []; const newTransactionSubscribers = filterSubscribers(transactionSubscribers); subscribers.set(transactionId, newTransactionSubscribers); this.subscribers.set(workflowId, subscribers); return; } const workflowSubscribers = subscribers.get(AnySubscriber) ?? []; const newWorkflowSubscribers = filterSubscribers(workflowSubscribers); subscribers.set(AnySubscriber, newWorkflowSubscribers); this.subscribers.set(workflowId, subscribers); } notify(options) { const { eventType, workflowId, transactionId, errors, result, step, response, } = options; const subscribers = this.subscribers.get(workflowId) ?? new Map(); const notifySubscribers = (handlers) => { handlers.forEach((handler) => { handler({ eventType, workflowId, transactionId, step, response, result, errors, }); }); }; if (transactionId) { const transactionSubscribers = subscribers.get(transactionId) ?? []; notifySubscribers(transactionSubscribers); } const workflowSubscribers = subscribers.get(AnySubscriber) ?? []; notifySubscribers(workflowSubscribers); } buildWorkflowEvents({ customEventHandlers, workflowId, transactionId, }) { const notify = ({ eventType, step, result, response, errors, }) => { this.notify({ workflowId, transactionId, eventType, response, step, result, errors, }); }; return { onTimeout: ({ transaction }) => { customEventHandlers?.onTimeout?.({ transaction }); notify({ eventType: "onTimeout" }); }, onBegin: ({ transaction }) => { customEventHandlers?.onBegin?.({ transaction }); notify({ eventType: "onBegin" }); }, onResume: ({ transaction }) => { customEventHandlers?.onResume?.({ transaction }); notify({ eventType: "onResume" }); }, onCompensateBegin: ({ transaction }) => { customEventHandlers?.onCompensateBegin?.({ transaction }); notify({ eventType: "onCompensateBegin" }); }, onFinish: ({ transaction, result, errors }) => { // TODO: unsubscribe transaction handlers on finish customEventHandlers?.onFinish?.({ transaction, result, errors }); }, onStepBegin: ({ step, transaction }) => { customEventHandlers?.onStepBegin?.({ step, transaction }); notify({ eventType: "onStepBegin", step }); }, onStepSuccess: async ({ step, transaction }) => { const stepName = step.definition.action; const response = await (0, workflows_sdk_1.resolveValue)(transaction.getContext().invoke[stepName], transaction); customEventHandlers?.onStepSuccess?.({ step, transaction, response }); notify({ eventType: "onStepSuccess", step, response }); }, onStepFailure: ({ step, transaction }) => { const stepName = step.definition.action; const errors = transaction .getErrors(orchestration_1.TransactionHandlerType.INVOKE) .filter((err) => err.action === stepName); customEventHandlers?.onStepFailure?.({ step, transaction, errors }); notify({ eventType: "onStepFailure", step, errors }); }, onStepAwaiting: ({ step, transaction }) => { customEventHandlers?.onStepAwaiting?.({ step, transaction }); notify({ eventType: "onStepAwaiting", step }); }, onCompensateStepSuccess: ({ step, transaction }) => { const stepName = step.definition.action; const response = transaction.getContext().compensate[stepName]; customEventHandlers?.onCompensateStepSuccess?.({ step, transaction, response, }); notify({ eventType: "onCompensateStepSuccess", step, response }); }, onCompensateStepFailure: ({ step, transaction }) => { const stepName = step.definition.action; const errors = transaction .getErrors(orchestration_1.TransactionHandlerType.COMPENSATE) .filter((err) => err.action === stepName); customEventHandlers?.onStepFailure?.({ step, transaction, errors }); notify({ eventType: "onCompensateStepFailure", step, errors }); }, }; } buildIdempotencyKeyAndParts(idempotencyKey) { const parts = { workflowId: "", transactionId: "", stepId: "", action: "invoke", }; let idempotencyKey_ = idempotencyKey; const setParts = (workflowId, transactionId, stepId, action) => { parts.workflowId = workflowId; parts.transactionId = transactionId; parts.stepId = stepId; parts.action = action; }; if (!(0, utils_1.isString)(idempotencyKey)) { const { workflowId, transactionId, stepId, action } = idempotencyKey; idempotencyKey_ = [workflowId, transactionId, stepId, action].join(":"); setParts(workflowId, transactionId, stepId, action); } else { const [workflowId, transactionId, stepId, action] = idempotencyKey_.split(":"); setParts(workflowId, transactionId, stepId, action); } return [idempotencyKey_, parts]; } } exports.WorkflowOrchestratorService = WorkflowOrchestratorService; //# sourceMappingURL=workflow-orchestrator.js.map