UNPKG

arvo-event-handler

Version:

A complete set of orthogonal event handler and orchestration primitives for Arvo based applications, featuring declarative state machines (XState), imperative resumables for agentic workflows, contract-based routing, OpenTelemetry observability, and in-me

249 lines (248 loc) 18.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeWithOrchestrationWrapper = exports.returnEventsWithLogging = void 0; var api_1 = require("@opentelemetry/api"); var arvo_core_1 = require("arvo-core"); var types_1 = require("../../types"); var utils_1 = require("../../utils"); var handlerErrors_1 = require("../handlerErrors"); var orchestrationExecutionState_1 = require("../orchestrationExecutionState"); var acquireLockWithValidation_1 = require("./acquireLockWithValidation"); var validateAndParseSubject_1 = require("./validateAndParseSubject"); /** * Helper to log and return execution results. * @returns The same result after logging */ var returnEventsWithLogging = function (param, span) { var _a, _b; (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "Execution completed with issues and emitted ".concat((_b = (_a = param.events) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0, " events"), }, span); return param; }; exports.returnEventsWithLogging = returnEventsWithLogging; /** * Wraps orchestration execution with infrastructure concerns. * * Provides a complete execution wrapper that handles: * - OpenTelemetry span creation and management * - Event subject validation and parsing * - Lock acquisition for concurrent safety * - State retrieval and persistence * - Error handling with system error event generation * - Lock release in all scenarios * * This wrapper ensures consistent behavior across all orchestration handlers * while allowing custom core logic via the execution function parameter. * @returns Emitted events from successful execution or error handling */ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return __awaiter(void 0, [_a, coreExecutionFn_1], void 0, function (_b, coreExecutionFn) { var otelConfig; var event = _b.event, opentelemetry = _b.opentelemetry, spanOptions = _b.spanOptions, source = _b.source, syncEventResource = _b.syncEventResource, executionunits = _b.executionunits, systemErrorDomain = _b.systemErrorDomain, selfContract = _b.selfContract, _handlerType = _b._handlerType; return __generator(this, function (_c) { switch (_c.label) { case 0: otelConfig = (0, utils_1.createEventHandlerTelemetryConfig)(spanOptions.spanName({ selfContractUri: selfContract.uri, consumedEvent: event }), spanOptions, opentelemetry, event); return [4 /*yield*/, arvo_core_1.ArvoOpenTelemetry.getInstance().startActiveSpan(__assign(__assign({}, otelConfig), { fn: function (span) { return __awaiter(void 0, void 0, void 0, function () { var _i, _a, _b, key, value, otelHeaders, orchestrationParentSubject, initEventId, acquiredLock, machineMemoryMetadata, parsedEventSubject, state, _c, emittables, newState, i, _d, _e, _f, key, value, error_1, _g, errorToThrow, errorEvents; var _h, _j, _k, _l; return __generator(this, function (_m) { switch (_m.label) { case 0: span.setStatus({ code: api_1.SpanStatusCode.OK }); span.setAttribute('arvo.handler.execution.type', _handlerType); span.setAttribute('arvo.handler.execution.status', 'normal'); for (_i = 0, _a = Object.entries(event.otelAttributes); _i < _a.length; _i++) { _b = _a[_i], key = _b[0], value = _b[1]; span.setAttribute("consumable.0.".concat(key), value); } (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "Starting execution for ".concat(event.type, " on subject ").concat(event.subject), }, span); otelHeaders = (0, arvo_core_1.currentOpenTelemetryHeaders)(); orchestrationParentSubject = null; initEventId = null; acquiredLock = null; machineMemoryMetadata = { subject: event.subject, source: source, initiator: types_1.Materialized.pending(), parentSubject: types_1.Materialized.pending(), }; _m.label = 1; case 1: _m.trys.push([1, 8, 10, 12]); parsedEventSubject = (0, validateAndParseSubject_1.validateAndParseSubject)(event, source, syncEventResource, span, 'orchestrator'); if (!parsedEventSubject) { return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: [] }, span)]; } return [4 /*yield*/, (0, acquireLockWithValidation_1.acquireLockWithValidation)(syncEventResource, event, machineMemoryMetadata, span)]; case 2: // Lock acquisition acquiredLock = _m.sent(); return [4 /*yield*/, syncEventResource.acquireState(event, machineMemoryMetadata, span)]; case 3: state = _m.sent(); if ((state === null || state === void 0 ? void 0 : state.executionStatus) === orchestrationExecutionState_1.OrchestrationExecutionStatus.FAILURE || (state === null || state === void 0 ? void 0 : state.executionStatus) === orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE) { span.setAttribute('arvo.handler.execution.status', state.executionStatus); (0, arvo_core_1.logToSpan)({ level: 'WARNING', message: "The orchestration is in terminal state '".concat(state.executionStatus, "'. Ignoring event id: ").concat(event.id, " with event subject: ").concat(event.subject), }, span); span.setStatus({ code: state.executionStatus === orchestrationExecutionState_1.OrchestrationExecutionStatus.FAILURE ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK, message: "The orchestration is in terminal state '".concat(state.executionStatus, "'. Ignoring event id: ").concat(event.id, " with event subject: ").concat(event.subject), }); return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: [] }, span)]; } orchestrationParentSubject = (_h = state === null || state === void 0 ? void 0 : state.parentSubject) !== null && _h !== void 0 ? _h : null; initEventId = (_j = state === null || state === void 0 ? void 0 : state.initEventId) !== null && _j !== void 0 ? _j : null; machineMemoryMetadata = __assign(__assign({}, machineMemoryMetadata), { initiator: types_1.Materialized.resolved(parsedEventSubject.execution.initiator), parentSubject: types_1.Materialized.resolved(orchestrationParentSubject) }); if (!state) { (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "Initializing new execution state for subject: ".concat(event.subject), }); if (event.type !== source) { (0, arvo_core_1.logToSpan)({ level: 'WARNING', message: "Invalid initialization event detected. Expected type '".concat(source, "' but received '").concat(event.type, "'."), }); return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: [] }, span)]; } } else { (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "Resuming execution with existing state for subject: ".concat(event.subject), }); } // Extract parent subject from init event if applicable if (event.type === source) { orchestrationParentSubject = (_l = (_k = event === null || event === void 0 ? void 0 : event.data) === null || _k === void 0 ? void 0 : _k.parentSubject$$) !== null && _l !== void 0 ? _l : null; } return [4 /*yield*/, coreExecutionFn({ span: span, otelHeaders: otelHeaders, orchestrationParentSubject: orchestrationParentSubject, initEventId: initEventId !== null && initEventId !== void 0 ? initEventId : event.id, parsedEventSubject: parsedEventSubject, state: state, _handlerType: _handlerType, })]; case 4: _c = _m.sent(), emittables = _c.emittables, newState = _c.newState; // Add OpenTelemetry attributes for emitted events for (i = 0; i < emittables.length; i++) { for (_d = 0, _e = Object.entries(emittables[i].otelAttributes); _d < _e.length; _d++) { _f = _e[_d], key = _f[0], value = _f[1]; span.setAttribute("emittables.".concat(i, ".").concat(key), value); } } // Persist state return [4 /*yield*/, syncEventResource.persistState(event, newState, state, machineMemoryMetadata, span)]; case 5: // Persist state _m.sent(); if (!(newState.executionStatus === orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE)) return [3 /*break*/, 7]; return [4 /*yield*/, syncEventResource.cleanup(event, newState, state, machineMemoryMetadata, span)]; case 6: _m.sent(); _m.label = 7; case 7: (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "State update persisted in memory for subject ".concat(event.subject), }); (0, arvo_core_1.logToSpan)({ level: 'INFO', message: "Execution successfully completed and emitted ".concat(emittables.length, " events"), }); return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: emittables }, span)]; case 8: error_1 = _m.sent(); span.setAttribute('arvo.handler.execution.status', 'failure'); return [4 /*yield*/, (0, handlerErrors_1.handleOrchestrationErrors)(_handlerType, { error: error_1, event: event, otelHeaders: otelHeaders, orchestrationParentSubject: orchestrationParentSubject, initEventId: initEventId, selfContract: selfContract, systemErrorDomain: systemErrorDomain, executionunits: executionunits, source: source, syncEventResource: syncEventResource, handlerType: _handlerType, }, machineMemoryMetadata, span)]; case 9: _g = _m.sent(), errorToThrow = _g.errorToThrow, errorEvents = _g.events; if (errorToThrow) throw errorToThrow; return [2 /*return*/, { events: errorEvents, }]; case 10: return [4 /*yield*/, syncEventResource.releaseLock(event, acquiredLock, machineMemoryMetadata, span)]; case 11: _m.sent(); span.end(); return [7 /*endfinally*/]; case 12: return [2 /*return*/]; } }); }); } }))]; case 1: return [2 /*return*/, _c.sent()]; } }); }); }; exports.executeWithOrchestrationWrapper = executeWithOrchestrationWrapper;