@karmai-cloud/rest.temporalio
Version:
Express middleware for calling Temporal.io using REST Api Calls
205 lines (182 loc) • 5.35 kB
text/typescript
import { WorkflowClient } from "@temporalio/client";
import {
QueryDefinition,
SignalDefinition,
UpdateDefinition,
Workflow,
} from "@temporalio/common";
import express, { Router } from "express";
import { v4 } from "uuid";
const signalValidators = new WeakMap<SignalDefinition<any[]>, Function>();
export function useValidator(signal: SignalDefinition, fn: Function): void {
signalValidators.set(signal, fn);
}
export function temporalioMiddleware(
workflows: any,
client: WorkflowClient,
taskQueue: string,
router?: Router
) {
if (router === undefined) {
router = Router();
}
for (const key of Object.keys(workflows)) {
const value: any = workflows[key];
if (typeof value === "function") {
// Workflow
createWorkflowEndpoint(router, client, key, value as Workflow, taskQueue);
cancelWorkflowEndpoint(router, client);
} else if (typeof value === "object" && value != null) {
if (value["type"] === "signal") {
// Signal
createSignalEndpoint(
router,
client,
value as SignalDefinition<unknown[]>
);
} else if (value["type"] === "query") {
// Query
createQueryEndpoint(
router,
client,
value as QueryDefinition<unknown, unknown[]>
);
} else if (value["type"] === "update") {
// Query
createUpdateEndpoint(
router,
client,
value as UpdateDefinition<unknown, []>
);
}
}
}
return router;
}
function createWorkflowEndpoint(
router: Router,
client: WorkflowClient,
name: string,
fn: Workflow,
taskQueue: string
) {
router.post(
`/workflow/${name}`,
express.json(),
function (req: express.Request, res: express.Response) {
const workflowId = req.body?.workflowId || `${name}_${v4()}`;
const opts = {
taskQueue,
workflowId,
args: [req.body],
};
client.start(fn, opts).then(() => res.status(201).json({ workflowId }));
}
);
router.post(
`/workflow/${name}/sync`,
express.json(),
function (req: express.Request, res: express.Response) {
const workflowId = req.body?.workflowId || `${name}_${v4()}`;
const opts = {
taskQueue,
workflowId,
args: [req.body],
};
client
.execute(fn, opts)
.then((response) => res.status(201).send(response));
}
);
router.post(
`/workflow/${name}/:workflowId`,
express.json(),
function (req: express.Request, res: express.Response) {
const workflowId = req.params.workflowId;
const opts = {
taskQueue,
workflowId,
args: [req.body],
};
client
.start(fn, opts)
.then(() => res.json({ workflowId }))
.catch(defaultErrorHandlingMiddleware(req, res));
}
);
}
function createSignalEndpoint(
router: Router,
client: WorkflowClient,
signal: SignalDefinition<any[]>
) {
router.put(
`/signal/${signal.name}/:workflowId`,
express.json(),
function (req: express.Request, res: express.Response) {
let data = req.body;
let fn: Function | undefined = signalValidators.get(signal);
if (fn != null) {
data = fn(data);
}
const handle = client.getHandle(req.params.workflowId);
handle
.signal(signal, req.body)
.then(() => res.json({ received: true }))
.catch(defaultErrorHandlingMiddleware(req, res));
}
);
}
function createQueryEndpoint(
router: Router,
client: WorkflowClient,
query: QueryDefinition<any, any[]>
) {
router.get(`/query/${query.name}/:workflowId`, function (req, res) {
const handle = client.getHandle(req.params.workflowId);
handle
.query(query, req.query)
.then((result) => res.json({ result }))
.catch(defaultErrorHandlingMiddleware(req, res));
});
}
function createUpdateEndpoint(
router: Router,
client: WorkflowClient,
update: UpdateDefinition<any, []>
) {
router.patch(
`/update/${update.name}/:workflowId`,
express.json(),
function (req: express.Request, res: express.Response) {
let data = req.body;
const handle = client.getHandle(req.params.workflowId);
handle
.executeUpdate(update, data)
.then((result) => res.send(result))
.catch(defaultErrorHandlingMiddleware(req, res));
// TODO: Implement custom logic to handle errors
}
);
}
function cancelWorkflowEndpoint(router: Router, client: WorkflowClient) {
router.delete(`/workflow/:workflowId`, function (req, res) {
const handle = client.getHandle(req.params.workflowId);
handle
.cancel()
.then(() => res.status(204).send())
.catch(defaultErrorHandlingMiddleware(req, res));
});
}
function defaultErrorHandlingMiddleware(
req: express.Request,
res: express.Response,
next?: express.NextFunction
) {
return (err: any) => {
console.error(err);
return res
.status(err?.code > 200 && err?.code < 600 ? err.code : 500)
.json({ message: err.message, issues: err?.issues });
};
}