durable-functions
Version:
Durable Functions library for Node.js Azure Functions
295 lines • 13.3 kB
JavaScript
"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