UNPKG

@hotmeshio/hotmesh

Version:

Serverless Workflow

177 lines (176 loc) 6.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowHandleService = void 0; const exporter_1 = require("./exporter"); /** * The WorkflowHandleService provides methods to interact with a running * workflow. This includes exporting the workflow, sending signals, and * querying the state of the workflow. It is instanced/accessed via the * MeshFlow.Client class. * * @example * ```typescript * import { Client } from '@hotmeshio/hotmesh'; * import { Client as Postgres } from 'pg'; * * const client = new Client({ connection: { * class: Postgres, * options: { connectionString: 'postgres://user:pass@localhost:5432/db' } * }}); * * const handle = await client.workflow.start({ * args: ['HotMesh'], * taskQueue: 'hello-world', * }); * * //perform actions like send a signal * await handle.signal('my-signal', { data: 'Hello' }); * ``` */ class WorkflowHandleService { /** * @private */ constructor(hotMesh, workflowTopic, workflowId) { this.workflowTopic = workflowTopic; this.workflowId = workflowId; this.hotMesh = hotMesh; this.exporter = new exporter_1.ExporterService(this.hotMesh.appId, this.hotMesh.engine.store, this.hotMesh.engine.logger); } /** * Exports the workflow state to a JSON object. */ async export(options) { return this.exporter.export(this.workflowId, options); } /** * Sends a signal to the workflow. This is a way to send * a message to a workflow that is paused due to having * executed `MeshFlow.workflow.waitFor`. The workflow * will awaken if no other signals are pending. */ async signal(signalId, data) { await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data, }); } /** * Returns the job state of the workflow. If the workflow has completed * this is also the job output. If the workflow is still running, this * is the current state of the job, but it may change depending upon * the activities that remain. */ async state(metadata = false) { const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId); if (!state.data && state.metadata.err) { throw new Error(JSON.parse(state.metadata.err)); } return metadata ? state : state.data; } /** * Returns the current search state of the workflow. This is * different than the job state or individual activity state. * Search state represents name/value pairs that were added * to the workflow. */ async queryState(fields) { return await this.hotMesh.getQueryState(this.workflowId, fields); } /** * Returns the current status of the workflow. This is a semaphore * value that represents the current state of the workflow, where * 0 is complete and a negative value represents that the flow was * interrupted. */ async status() { return await this.hotMesh.getStatus(this.workflowId); } /** * Interrupts a running workflow. Standard Job Completion tasks will * run. Subscribers will be notified and the job hash will be expired. */ async interrupt(options) { return await this.hotMesh.interrupt(`${this.hotMesh.appId}.execute`, this.workflowId, options); } /** * Waits for the workflow to complete and returns the result. If * the workflow response includes an error, this method will rethrow * the error, including the stack trace if available. * Wrap calls in a try/catch as necessary to avoid unhandled exceptions. */ async result(config) { const topic = `${this.hotMesh.appId}.executed.${this.workflowId}`; let isResolved = false; return new Promise(async (resolve, reject) => { /** * rejects/resolves the promise based on the `throwOnError` * default behavior is to throw if error */ const safeReject = (err) => { if (config?.throwOnError === false) { return resolve(err); } reject(err); }; /** * Common completion function that unsubscribes from the topic/returns */ const complete = async (response, err) => { if (isResolved) return; isResolved = true; if (err) { return safeReject(err); } else if (!response) { const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId); if (state.data?.done && !state.data?.$error) { return resolve(state.data.response); } else if (state.data?.$error) { return safeReject(state.data.$error); } else if (state.metadata.err) { return safeReject(JSON.parse(state.metadata.err)); } response = state.data?.response; } resolve(response); }; //more expensive; fetches the entire job, not just the `status` if (config?.state) { const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId); if (state?.data?.done && !state.data?.$error) { return complete(state.data.response); } else if (state.data?.$error) { return complete(null, state.data.$error); } else if (state.metadata.err) { return complete(null, JSON.parse(state.metadata.err)); } } //subscribe to 'done' topic this.hotMesh.sub(topic, async (_topic, state) => { this.hotMesh.unsub(topic); if (state.data.done && !state.data?.$error) { await complete(state.data?.response); } else if (state.data?.$error) { return complete(null, state.data.$error); } else if (state.metadata.err) { const error = JSON.parse(state.metadata.err); return await complete(null, error); } }); //check state in case completed during wiring const status = await this.hotMesh.getStatus(this.workflowId); if (status <= 0) { await complete(); } }); } } exports.WorkflowHandleService = WorkflowHandleService;