UNPKG

durable-functions

Version:

Durable Functions library for Node.js Azure Functions

295 lines 13.3 kB
"use strict"; 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskOrchestrationExecutor = void 0; const OrchestrationFailureError_1 = require("../error/OrchestrationFailureError"); const OrchestratorState_1 = require("./OrchestratorState"); const task_1 = require("../task"); const Utils_1 = require("../util/Utils"); const CallEntityAction_1 = require("../actions/CallEntityAction"); const WaitForExternalEventAction_1 = require("../actions/WaitForExternalEventAction"); const ResponseMessage_1 = require("../entities/ResponseMessage"); const HistoryEventType_1 = require("../history/HistoryEventType"); class TaskOrchestrationExecutor { constructor() { this.eventToTaskValuePayload = { [HistoryEventType_1.HistoryEventType.TaskCompleted]: [true, "TaskScheduledId"], [HistoryEventType_1.HistoryEventType.TimerFired]: [true, "TimerId"], [HistoryEventType_1.HistoryEventType.SubOrchestrationInstanceCompleted]: [true, "TaskScheduledId"], [HistoryEventType_1.HistoryEventType.EventRaised]: [true, "Name"], [HistoryEventType_1.HistoryEventType.TaskFailed]: [false, "TaskScheduledId"], [HistoryEventType_1.HistoryEventType.SubOrchestrationInstanceFailed]: [false, "TaskScheduledId"], }; this.initialize(); } initialize() { this.currentTask = new task_1.NoOpTask(); this.currentTask.setValue(false, undefined); this.sequenceNumber = 0; this.willContinueAsNew = false; this.openTasks = {}; this.openEvents = {}; this.actions = []; this.deferredTasks = {}; this.output = undefined; this.exception = undefined; this.orchestratorReturned = false; } execute(context, history, schemaVersion, fn) { var _a; return __awaiter(this, void 0, void 0, function* () { this.schemaVersion = schemaVersion; this.context = context.df; this.generator = fn(context); for (const historyEvent of history) { this.processEvent(historyEvent); if (this.isDoneExecuting()) { break; } } const actions = this.actions.length == 0 ? [[]] : [this.actions]; const orchestratorState = new OrchestratorState_1.OrchestratorState({ isDone: this.hasCompletedSuccessfully(), actions: actions, output: this.output, error: (_a = this.exception) === null || _a === void 0 ? void 0 : _a.message, customStatus: this.context.customStatus, schemaVersion: this.schemaVersion, }); if (this.exception !== undefined) { throw new OrchestrationFailureError_1.OrchestrationFailureError(this.orchestratorReturned, orchestratorState); } return orchestratorState; }); } isDoneExecuting() { return this.hasCompletedSuccessfully() || this.exception !== undefined; } hasCompletedSuccessfully() { return this.orchestratorReturned || this.willContinueAsNew; } processEvent(event) { const eventType = event.EventType; switch (eventType) { case HistoryEventType_1.HistoryEventType.OrchestratorStarted: { const timestamp = event.Timestamp; if (timestamp > this.context.currentUtcDateTime) { this.context.currentUtcDateTime = timestamp; } break; } case HistoryEventType_1.HistoryEventType.ContinueAsNew: { this.initialize(); break; } case HistoryEventType_1.HistoryEventType.ExecutionStarted: { this.tryResumingUserCode(); break; } case HistoryEventType_1.HistoryEventType.EventSent: { const key = event.EventId; const task = this.openTasks[key]; if (task !== undefined) { if (task.actionObj instanceof CallEntityAction_1.CallEntityAction) { const eventSent = event; const requestMessage = JSON.parse(eventSent.Input); const eventId = requestMessage.id; delete this.openTasks[key]; this.openTasks[eventId] = task; } } break; } default: if (eventType in this.eventToTaskValuePayload) { const [isSuccess, idKey] = this.eventToTaskValuePayload[eventType]; this.setTaskValue(event, isSuccess, idKey); this.tryResumingUserCode(); } break; } } setTaskValue(event, isSuccess, idKey) { function extractResult(completionEvent) { let taskResult; switch (completionEvent.EventType) { case HistoryEventType_1.HistoryEventType.SubOrchestrationInstanceCompleted: taskResult = JSON.parse(completionEvent.Result); break; case HistoryEventType_1.HistoryEventType.TaskCompleted: taskResult = JSON.parse(completionEvent.Result); break; case HistoryEventType_1.HistoryEventType.EventRaised: const eventRaised = completionEvent; taskResult = eventRaised && eventRaised.Input ? JSON.parse(eventRaised.Input) : undefined; break; default: break; } return taskResult; } let task; const key = event[idKey]; if (typeof key === "number" || typeof key === "string") { task = this.openTasks[key]; const taskList = this.openEvents[key]; if (task !== undefined) { delete this.openTasks[key]; } else if (taskList !== undefined) { task = taskList.pop(); if (taskList.length == 0) { delete this.openEvents[key]; } } else { const updateTask = function () { this.setTaskValue(event, isSuccess, idKey); return; }; this.deferredTasks[key] = updateTask.bind(this); return; } } else { throw Error(`Task with ID ${key} could not be retrieved from due to its ID-key being of type ${typeof key}. ` + `We expect ID-keys to be of type number or string. ` + `This is probably a replay failure, please file a bug report.`); } let taskResult; if (isSuccess) { taskResult = extractResult(event); const action = task.actionObj; if (action instanceof CallEntityAction_1.CallEntityAction) { const eventPayload = new ResponseMessage_1.ResponseMessage(taskResult); taskResult = eventPayload.result ? JSON.parse(eventPayload.result) : undefined; if (eventPayload.exceptionType !== undefined) { taskResult = Error(taskResult); isSuccess = false; } } } else { if (Utils_1.Utils.hasStringProperty(event, "Reason") && Utils_1.Utils.hasStringProperty(event, "Details")) { taskResult = new Error(`${event.Reason} \n ${event.Details}`); } else { throw Error(`Task with ID ${task.id} failed but we could not parse its exception data.` + `This is probably a replay failure, please file a bug report.`); } } task.isPlayed = event.IsPlayed; task.setValue(!isSuccess, taskResult, this); } tryResumingUserCode() { const currentTask = this.currentTask; this.context.isReplaying = currentTask.isPlayed; if (currentTask.stateObj === task_1.TaskState.Running) { return; } let newTask = undefined; try { const result = currentTask.result; const taskValue = result; const taskSucceeded = currentTask.stateObj === task_1.TaskState.Completed; const generatorResult = taskSucceeded ? this.generator.next(taskValue) : this.generator.throw(taskValue); if (generatorResult.done) { this.orchestratorReturned = true; this.output = generatorResult.value; return; } else if (generatorResult.value instanceof task_1.DFTask) { newTask = generatorResult.value; } else { let errorMsg = `Durable Functions programming constraint violation: Orchestration yielded data of type ${typeof generatorResult.value}.`; errorMsg += typeof generatorResult.value === "undefined" ? ' This is likely a result of yielding a "fire-and-forget API" such as signalEntity or continueAsNew.' + " These APIs should not be yielded as they are not blocking operations. Please remove the yield statement preceding those invocations." + " If you are not calling those APIs, p" : " Only Task types can be yielded. P"; errorMsg += "lease check your yield statements to make sure you only yield Task types resulting from calling Durable Functions APIs."; throw Error(errorMsg); } } catch (exception) { this.exception = exception; } if (newTask !== undefined) { this.currentTask = newTask; if (newTask.state !== task_1.TaskState.Running) { this.tryResumingUserCode(); } else { this.trackOpenTask(newTask); if (this.currentTask instanceof task_1.DFTask && !this.currentTask.alreadyScheduled) { this.markAsScheduled(this.currentTask); this.addToActions(this.currentTask.actionObj); } } } } addToActions(action) { if (!this.willContinueAsNew) { this.actions.push(action); } } recordFireAndForgetAction(action) { if (!this.willContinueAsNew) { this.addToActions(action); this.sequenceNumber++; } } trackOpenTask(task) { if (task instanceof task_1.CompoundTask) { for (const child of task.children) { this.trackOpenTask(child); } } else { if (task.id === false) { task.id = this.sequenceNumber++; this.openTasks[task.id] = task; } else if (task.actionObj instanceof WaitForExternalEventAction_1.WaitForExternalEventAction) { const candidateEventList = this.openEvents[task.id]; const eventList = candidateEventList !== undefined ? candidateEventList : []; eventList.push(task); this.openEvents[task.id] = eventList; } if (this.deferredTasks.hasOwnProperty(task.id)) { const taskUpdateAction = this.deferredTasks[task.id]; taskUpdateAction(); } } } markAsScheduled(task) { if (task instanceof task_1.CompoundTask) { task.alreadyScheduled = true; for (const child of task.children) { this.markAsScheduled(child); } } else if (task instanceof task_1.DFTask) { task.alreadyScheduled = true; } } } exports.TaskOrchestrationExecutor = TaskOrchestrationExecutor; //# sourceMappingURL=TaskOrchestrationExecutor.js.map