UNPKG

chrono-forge

Version:

A comprehensive framework for building resilient Temporal workflows, advanced state management, and real-time streaming activities in TypeScript. Designed for a seamless developer experience with powerful abstractions, dynamic orchestration, and full cont

551 lines (547 loc) 22.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Workflow = void 0; exports.Temporal = Temporal; require("reflect-metadata"); const workflow = __importStar(require("@temporalio/workflow")); const eventemitter3_1 = __importDefault(require("eventemitter3")); const decorators_1 = require("../decorators"); const mnemonist_1 = require("mnemonist"); function Temporal(options) { return function (constructor) { const { name: optionalName, taskQueue, tracerName = 'temporal_worker', ...extraOptions } = options || {}; const workflowName = optionalName ?? constructor.name; if (!(constructor.prototype instanceof Workflow)) { throw new Error(`Workflows must extend the Workflow class, any class that extends Workflow is supported. ${constructor.name} does not extend Workflow.`); } const construct = new Function('workflow', 'constructor', 'extraOptions', ` return async function ${workflowName}(...args) { const instance = new constructor(args[0], extraOptions); try { instance.workflowType = '${workflowName}'; await instance.bindEventHandlers(); await instance.emitAsync('setup'); await instance.emitAsync('hooks'); await instance.emitAsync('init'); const executionMethod = instance.continueAsNew ? 'executeWorkflow' : 'execute'; let result; try { result = await instance[executionMethod](...args); } finally { // Ensure cleanup happens in all cases: success, error, or continueAsNew if (typeof instance.cleanup === 'function') { try { instance.cleanup(); } catch (cleanupError) { workflow.log.error('Error during workflow cleanup: ' + (cleanupError instanceof Error ? cleanupError.message : String(cleanupError))); } } } return result; } catch (e) { if (workflow.isCancellation(e)) { await workflow.CancellationScope.nonCancellable(async () => { await workflow.condition(() => instance.status === 'cancelled'); }); } throw e; } } `)(workflow, constructor, extraOptions); return construct; }; } class Workflow extends eventemitter3_1.default { args; options; workflowType; handles = new mnemonist_1.LRUCacheWithDelete(2000); _eventsBound = false; _hooksBound = false; _signalsBound = false; _queriesBound = false; _propertiesBound = false; _stepsBound = false; log = { debug: (message, ...args) => workflow.log.debug(`[${this.constructor.name}]:[${workflow.workflowInfo().workflowId}]: ${message}`, ...args), info: (message, ...args) => workflow.log.info(`[${this.constructor.name}]:[${workflow.workflowInfo().workflowId}]: ${message}`, ...args), warn: (message, ...args) => workflow.log.warn(`[${this.constructor.name}]:[${workflow.workflowInfo().workflowId}]: ${message}`, ...args), error: (message, ...args) => workflow.log.error(`[${this.constructor.name}]:[${workflow.workflowInfo().workflowId}]: ${message}`, ...args), trace: (message, ...args) => workflow.log.trace(`[${this.constructor.name}]:[${workflow.workflowInfo().workflowId}]: ${message}`, ...args) }; result; queryHandlers = {}; signalHandlers = {}; stepHandlers = {}; continueAsNew = false; shouldContinueAsNew = false; maxIterations = 10000; iteration = 0; pendingIteration = false; status = 'running'; pendingUpdate = true; pause() { if (this.status !== 'paused') { this.log.info(`Pausing...`); this.status = 'paused'; workflow.upsertMemo({ status: this.status, lastUpdated: new Date().toISOString() }); this.forwardSignalToChildren('pause'); } else { this.log.info(`Already paused!`); } } resume() { if (this.status !== 'running') { this.log.info(`Resuming...`); this.status = 'running'; workflow.upsertMemo({ status: this.status, lastUpdated: new Date().toISOString() }); this.forwardSignalToChildren('resume'); } else { this.log.info(`Already running...`); } } cancel() { if (this.status !== 'cancelling') { this.log.info(`Cancelling...`); this.status = 'cancelling'; workflow.upsertMemo({ status: this.status, lastUpdated: new Date().toISOString() }); this.forwardSignalToChildren('cancel'); } else { this.log.info(`Already cancelling...`); } } constructor(args, options) { super(); this.args = args; this.options = options; this.args = args; this.options = options; if (args && typeof args === 'object') { this.status = this.args?.status ?? 'running'; } } conditionTimeout = undefined; isInTerminalState() { return ['complete', 'completed', 'cancel', 'cancelling', 'cancelled', 'error', 'erroring', 'errored'].includes(this.status); } async executeWorkflow(...args) { try { while (this.iteration <= this.maxIterations) { await workflow.condition(() => (typeof this.condition === 'function' && this.condition()) || this.pendingIteration || this.pendingUpdate || this.status !== 'running', !this.conditionTimeout ? undefined : this.conditionTimeout); if (this.status === 'paused') { await this.emitAsync('paused'); await this.forwardSignalToChildren('pause'); await workflow.condition(() => this.status !== 'paused'); } if (this.status === 'cancelled') { break; } else { this.result = await Promise.resolve().then(() => this.execute()); } if (this.isInTerminalState()) { if (this.status !== 'errored') { return this.result; } throw this.result; } if (++this.iteration >= this.maxIterations || (workflow.workflowInfo().continueAsNewSuggested && workflow.workflowInfo().historySize >= 41943040) || this.shouldContinueAsNew) { await this.handleMaxIterations(); break; } if (this.pendingUpdate) { this.pendingUpdate = false; } if (this.pendingIteration) { this.pendingIteration = false; } } return this.result; } catch (err) { await this.handleExecutionError(err, (error) => { throw error; }); throw err; } } async handleMaxIterations() { await workflow.condition(() => workflow.allHandlersFinished(), '30 seconds'); const continueFn = workflow.makeContinueAsNewFunc({ workflowType: String(this.workflowType), memo: workflow.workflowInfo().memo, searchAttributes: workflow.workflowInfo().searchAttributes }); if (typeof this.onContinue === 'function') { const customArgs = await this.onContinue(); await continueFn(customArgs || this.args); } else { throw new Error(`No onContinue method found in ${this.constructor.name}. Cannot continue as new.`); } } async handleExecutionError(err, reject) { this.log.error(`Error encountered: ${err?.message}: ${err?.stack}`); if (workflow.isCancellation(err)) { this.log.debug(`Cancelling...`); await workflow.CancellationScope.nonCancellable(async () => { this.status = 'cancelling'; await Promise.all(Array.from(this.handles.values()).flatMap((handles) => Object.values(handles).map((handle) => async () => { try { const extHandle = workflow.getExternalWorkflowHandle(handle.workflowId); await extHandle.cancel(); } catch (error) { this.log.error(`Failed to cancel child workflow:`, error); } }))); this.status = 'cancelled'; }); reject(err); } else if (!(err instanceof workflow.ContinueAsNew)) { this.log.error(`Handling non-cancellation error: ${err?.message} \n ${err.stack}`); reject(err); } } emit(event, ...args) { throw new Error('Workflows need to be deterministic, emitAsync should be used instead!'); } async emitAsync(event, ...args) { const listeners = this.listeners(event); for (const listener of listeners) { try { await Promise.resolve().then(() => listener(...args)); } catch (error) { console.error(`Error in listener for event '${event}':`, error); } } return listeners.length > 0; } cleanup() { try { this.removeAllListeners(); this._eventsBound = false; this._hooksBound = false; this._signalsBound = false; this._queriesBound = false; this._propertiesBound = false; this._stepsBound = false; } catch (error) { this.log.error(`Error cleaning up event listeners: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async forwardSignalToChildren(signalName, ...args) { for (const handle of this.handles.values()) { try { await Promise.resolve().then(() => handle.signal(signalName, ...args)); } catch (error) { this.log.error(`Failed to forward signal '${signalName}' to child workflow:`, error); } } } async bindEventHandlers() { if (this._eventsBound) { return; } const eventHandlers = this.collectMetadata(decorators_1.EVENTS_METADATA_KEY, this.constructor.prototype); eventHandlers.forEach((handler) => { this.on(handler.event, async (...args) => { const method = this[handler.method]; if (typeof method === 'function') { await Promise.resolve().then(() => method.apply(this, args)); } }); }); this._eventsBound = true; } async bindHooks() { if (this._hooksBound) { return; } const hooks = this.collectHookMetadata(this.constructor.prototype); if (!hooks) return; for (const methodName of Object.keys(hooks)) { const originalMethod = this[methodName]; if (typeof originalMethod === 'function') { this[methodName] = this.createHookedMethod(methodName, originalMethod, hooks[methodName]); } } this._hooksBound = true; } createHookedMethod(methodName, originalMethod, hookConfig) { return async (...args) => { const { before = [], after = [] } = hookConfig; for (const beforeHook of before) { if (typeof this[beforeHook] === 'function') { this.log.trace(`[HOOK]:(${beforeHook}):before(${methodName})`); await Promise.resolve().then(() => this[beforeHook].call(this, ...args)); } } let result; try { result = await Promise.resolve().then(() => originalMethod.call(this, ...args)); } catch (err) { throw err instanceof Error ? err : new Error(String(err)); } for (const afterHook of after) { if (typeof this[afterHook] === 'function') { this.log.trace(`[HOOK]:(${afterHook}):after(${methodName})`); await Promise.resolve().then(() => this[afterHook].call(this, ...args)); } } return result; }; } async bindProperties() { if (this._propertiesBound) { return; } const properties = this.collectMetadata(decorators_1.PROPERTY_METADATA_KEY, this.constructor.prototype); properties.forEach(({ propertyKey, get: g, set: s, queryName, signalName }) => { if (g) { const getter = () => this[propertyKey]; this.queryHandlers[queryName] = getter.bind(this); workflow.setHandler(workflow.defineQuery(typeof g === 'string' ? g : queryName), getter); } if (s) { const setter = (value) => (this[propertyKey] = value); this.signalHandlers[signalName] = setter.bind(this); workflow.setHandler(workflow.defineSignal(typeof g === 'string' ? s : signalName), setter); } }); this._propertiesBound = true; } bindQueries() { if (this._queriesBound) { return; } const queries = this.collectMetadata(decorators_1.QUERY_METADATA_KEY, this.constructor.prototype); for (const [queryName, queryMethod] of queries) { if (typeof this[queryMethod] === 'function') { const handler = this[queryMethod].bind(this); this.queryHandlers[queryName] = handler; workflow.setHandler(workflow.defineQuery(queryName), handler); } } this._queriesBound = true; } bindSignals() { if (this._signalsBound) { return; } const signals = this.collectMetadata(decorators_1.SIGNAL_METADATA_KEY, this.constructor.prototype); for (const [signalName, signalMethod] of signals) { if (typeof this[signalMethod] === 'function') { const handler = this[signalMethod].bind(this); this.signalHandlers[signalName] = handler; workflow.setHandler(workflow.defineSignal(signalName), handler); } } this._signalsBound = true; } bindSteps() { if (this._stepsBound) { return; } const steps = this.collectMetadata(decorators_1.STEP_METADATA_KEY, this.constructor.prototype); for (const step of steps) { if (typeof this[step.method] === 'function') { const handler = this[step.method].bind(this); this.stepHandlers[step.name] = handler; } } this._stepsBound = true; } getStepHandlers() { return this.stepHandlers; } collectMetadata(metadataKey, target) { const collectedMetadata = []; const seen = new Set(); let currentProto = target; while (currentProto && currentProto !== Object.prototype) { const metadata = Reflect.getMetadata(metadataKey, currentProto) || []; for (const item of metadata) { const itemString = JSON.stringify(item); if (!seen.has(itemString)) { seen.add(itemString); collectedMetadata.push(item); } } currentProto = Object.getPrototypeOf(currentProto); } return collectedMetadata; } collectHookMetadata(target) { const collectedMetadata = {}; const protoChain = []; let currentProto = target; while (currentProto && currentProto !== Workflow.prototype) { protoChain.unshift(currentProto); currentProto = Object.getPrototypeOf(currentProto); } for (const proto of protoChain) { const metadata = Reflect.getOwnMetadata(decorators_1.HOOKS_METADATA_KEY, proto) || {}; for (const key of Object.keys(metadata)) { if (!collectedMetadata[key]) { collectedMetadata[key] = { before: [], after: [] }; } collectedMetadata[key].before.push(...(metadata[key].before || [])); collectedMetadata[key].after.push(...(metadata[key].after || [])); } } return collectedMetadata; } } exports.Workflow = Workflow; __decorate([ (0, decorators_1.Property)({ set: false }), __metadata("design:type", Object) ], Workflow.prototype, "continueAsNew", void 0); __decorate([ (0, decorators_1.Property)(), __metadata("design:type", Object) ], Workflow.prototype, "shouldContinueAsNew", void 0); __decorate([ (0, decorators_1.Property)({ set: false }), __metadata("design:type", Object) ], Workflow.prototype, "maxIterations", void 0); __decorate([ (0, decorators_1.Property)({ set: false }), __metadata("design:type", Object) ], Workflow.prototype, "iteration", void 0); __decorate([ (0, decorators_1.Property)(), __metadata("design:type", Boolean) ], Workflow.prototype, "pendingIteration", void 0); __decorate([ (0, decorators_1.Property)(), __metadata("design:type", String) ], Workflow.prototype, "status", void 0); __decorate([ (0, decorators_1.Property)(), __metadata("design:type", Object) ], Workflow.prototype, "pendingUpdate", void 0); __decorate([ (0, decorators_1.Signal)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "pause", null); __decorate([ (0, decorators_1.Signal)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "resume", null); __decorate([ (0, decorators_1.Signal)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "cancel", null); __decorate([ (0, decorators_1.Property)(), __metadata("design:type", Object) ], Workflow.prototype, "conditionTimeout", void 0); __decorate([ (0, decorators_1.On)('hooks'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], Workflow.prototype, "bindHooks", null); __decorate([ (0, decorators_1.On)('init'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], Workflow.prototype, "bindProperties", null); __decorate([ (0, decorators_1.On)('init'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "bindQueries", null); __decorate([ (0, decorators_1.On)('init'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "bindSignals", null); __decorate([ (0, decorators_1.On)('init'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Workflow.prototype, "bindSteps", null);