@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
177 lines (176 loc) • 7.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProxyInterruptPayload = exports.wrapActivity = exports.proxyActivities = void 0;
const common_1 = require("./common");
const context_1 = require("./context");
const didRun_1 = require("./didRun");
/**
* Constructs payload for spawning a proxyActivity job.
* @private
*/
function getProxyInterruptPayload(context, activityName, execIndex, args, options) {
const { workflowDimension, workflowId, originJobId, workflowTopic, expire } = context;
// Use explicitly provided taskQueue, otherwise derive from workflow (original behavior)
// This keeps backward compatibility while allowing explicit global/custom queues
const activityTopic = options?.taskQueue
? `${options.taskQueue}-activity`
: `${workflowTopic}-activity`;
const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
let maximumInterval;
if (options?.retryPolicy?.maximumInterval) {
maximumInterval = (0, common_1.s)(options.retryPolicy.maximumInterval);
}
return {
arguments: args,
workflowDimension,
index: execIndex,
originJobId: originJobId || workflowId,
parentWorkflowId: workflowId,
workflowId: activityJobId,
workflowTopic: activityTopic,
activityName,
expire: options?.expire ?? expire,
backoffCoefficient: options?.retryPolicy?.backoffCoefficient ?? undefined,
maximumAttempts: options?.retryPolicy?.maximumAttempts ?? undefined,
maximumInterval: maximumInterval ?? undefined,
};
}
exports.getProxyInterruptPayload = getProxyInterruptPayload;
/**
* Wraps a single activity in a proxy, orchestrating its execution and replay.
* @private
*/
function wrapActivity(activityName, options) {
return async function (...args) {
const [didRunAlready, execIndex, result] = await (0, didRun_1.didRun)('proxy');
if (didRunAlready) {
if (result?.$error) {
if (options?.retryPolicy?.throwOnError !== false) {
const code = result.$error.code;
const message = result.$error.message;
const stack = result.$error.stack;
if (code === common_1.HMSH_CODE_MEMFLOW_FATAL) {
throw new common_1.MemFlowFatalError(message, stack);
}
else if (code === common_1.HMSH_CODE_MEMFLOW_MAXED) {
throw new common_1.MemFlowMaxedError(message, stack);
}
else if (code === common_1.HMSH_CODE_MEMFLOW_TIMEOUT) {
throw new common_1.MemFlowTimeoutError(message, stack);
}
else {
// For any other error code, throw a MemFlowFatalError to stop the workflow
throw new common_1.MemFlowFatalError(message, stack);
}
}
return result.$error;
}
return result.data;
}
const context = (0, context_1.getContext)();
const { interruptionRegistry } = context;
const interruptionMessage = getProxyInterruptPayload(context, activityName, execIndex, args, options);
interruptionRegistry.push({
code: common_1.HMSH_CODE_MEMFLOW_PROXY,
type: 'MemFlowProxyError',
...interruptionMessage,
});
await (0, common_1.sleepImmediate)();
throw new common_1.MemFlowProxyError(interruptionMessage);
};
}
exports.wrapActivity = wrapActivity;
/**
* Create proxies for activity functions with automatic retry and deterministic replay.
* Activities execute via message queue, so they can run on different servers.
*
* Without `taskQueue`, activities use the workflow's task queue (e.g., `my-workflow-activity`).
* With `taskQueue`, activities use the specified queue (e.g., `payment-activity`).
*
* The `activities` parameter is optional. If activities are already registered via
* `registerActivityWorker()`, you can reference them by providing just the `taskQueue`
* and a TypeScript interface.
*
* @template ACT
* @param {ActivityConfig} [options] - Activity configuration
* @param {any} [options.activities] - (Optional) Activity functions to register inline
* @param {string} [options.taskQueue] - (Optional) Task queue name (without `-activity` suffix)
* @param {object} [options.retryPolicy] - Retry configuration
* @returns {ProxyType<ACT>} Proxy for calling activities with durability and retry
*
* @example
* ```typescript
* // Inline registration (activities in same codebase)
* const activities = MemFlow.workflow.proxyActivities<typeof activities>({
* activities: { processData, validateData },
* retryPolicy: { maximumAttempts: 3 }
* });
*
* await activities.processData('input');
* ```
*
* @example
* ```typescript
* // Reference pre-registered activities (can be on different server)
* interface PaymentActivities {
* processPayment: (amount: number) => Promise<string>;
* sendEmail: (to: string, subject: string) => Promise<void>;
* }
*
* const { processPayment, sendEmail } =
* MemFlow.workflow.proxyActivities<PaymentActivities>({
* taskQueue: 'payment',
* retryPolicy: { maximumAttempts: 3 }
* });
*
* const result = await processPayment(100.00);
* await sendEmail('user@example.com', 'Payment processed');
* ```
*
* @example
* ```typescript
* // Shared activities in interceptor
* const interceptor: WorkflowInterceptor = {
* async execute(ctx, next) {
* const { auditLog } = MemFlow.workflow.proxyActivities<{
* auditLog: (id: string, action: string) => Promise<void>;
* }>({
* taskQueue: 'shared',
* retryPolicy: { maximumAttempts: 3 }
* });
*
* await auditLog(ctx.get('workflowId'), 'started');
* const result = await next();
* await auditLog(ctx.get('workflowId'), 'completed');
* return result;
* }
* };
* ```
*
* @example
* ```typescript
* // Custom task queue for specific activities
* const highPriority = MemFlow.workflow.proxyActivities<typeof activities>({
* activities: { criticalProcess },
* taskQueue: 'high-priority',
* retryPolicy: { maximumAttempts: 5 }
* });
* ```
*/
function proxyActivities(options) {
// Register activities if provided (optional - may already be registered remotely)
if (options?.activities) {
common_1.WorkerService.registerActivities(options.activities);
}
// Create proxy for all registered activities
const proxy = {};
const keys = Object.keys(common_1.WorkerService.activityRegistry);
if (keys.length) {
keys.forEach((key) => {
const activityFunction = common_1.WorkerService.activityRegistry[key];
proxy[key] = wrapActivity(key, options);
});
}
return proxy;
}
exports.proxyActivities = proxyActivities;