UNPKG

@golemcloud/golem-ts

Version:

A library that help writing Golem programs by providing higher level wrappers for Golem's runtime APIs, including functions for defining and performing operations transactionally.

163 lines (162 loc) 5.67 kB
// Copyright 2024-2025 Golem Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { executeWithDrop, markAtomicOperation } from "./guard"; import { getOplogIndex, setOplogIndex } from "./hostapi"; import { Result } from "./result"; /** * Creates an Operation from the provided execute and compensate functions. * @param execute - The function to execute the operation. * @param compensate - The function to compensate the operation in case of failure. * @returns The created Operation. */ export function operation(execute, compensate) { return new OperationImpl(execute, compensate); } class OperationImpl { constructor(execute, compensate) { this.execute = execute; this.compensate = compensate; } } class InfallibleTransaction { constructor(beginOplogIndex) { this.beginOplogIndex = beginOplogIndex; this.compensations = []; } /** * Executes an operation within the infallible transaction. * @param operation - The operation to execute. * @param input - The input to the operation. * @returns The result of the operation. */ execute(operation, input) { const result = operation.execute(input); if (result.isOk()) { this.compensations.push( // Compensations cannot fail in infallible transactions. () => { const compensationResult = operation.compensate(input, result.val); if (compensationResult.isErr()) { throw new Error("Compensation action failed"); } }); return result.val; } else { this.retry(); throw new Error("Unreachable code"); } } retry() { // Rollback all the compensations in reverse order for (let i = this.compensations.length - 1; i >= 0; i--) { this.compensations[i](); } setOplogIndex(this.beginOplogIndex); } } class FallibleTransaction { constructor() { this.compensations = []; } /** * Executes an operation within the fallible transaction. * @param operation - The operation to execute. * @param input - The input to the operation. * @returns The result of the operation. */ execute(operation, input) { const result = operation.execute(input); if (result.isOk()) { this.compensations.push(() => { return operation.compensate(input, result.val); }); return result; } else { return result; } } /** * Handles the failure of the fallible transaction. * @param error - The error that caused the failure. * @returns The transaction failure result. */ onFailure(error) { for (let i = this.compensations.length - 1; i >= 0; i--) { const compensationResult = this.compensations[i](); if (compensationResult.isErr()) { return { type: "FailedAndRolledBackPartially", error, compensationFailure: compensationResult.val, }; } } return { type: "FailedAndRolledBackCompletely", error, }; } } /** * Executes an infallible transaction. * * InfallibleTransaction is a sequence of operations that are executed in a way that if any of the * operations or the underlying Golem executor fails, the whole transaction is going to * be retried. * * In addition to that, **user level failures** (represented by the `Result::Err` value * of an operation) lead to performing the compensation actions of each already performed operation * in reverse order. * * Fatal errors (panic) and external executor failures currently cannot perform the * rollback actions. * * @param f - The function that defines the transaction. * @returns The result of the transaction. */ export function infallibleTransaction(f) { const guard = markAtomicOperation(); const beginOplogIndex = getOplogIndex(); const tx = new InfallibleTransaction(beginOplogIndex); return executeWithDrop([guard], () => f(tx)); } /** * Executes a fallible transaction. * * FallibleTransaction is a sequence of operations that are executed in a way that if any of the * operations fails, all the already performed operation's compensation actions get executed in * reverse order. * * In case of fatal errors (panic) and external executor failures, it does not perform the * compensation actions and the whole transaction gets retried. * * @param f - The function that defines the transaction. * @returns The result of the transaction. */ export function fallibleTransaction(f) { const guard = markAtomicOperation(); const tx = new FallibleTransaction(); const execute = () => { const result = f(tx); if (result.isOk()) { return Result.ok(result.val); } else { return Result.err(tx.onFailure(result.val)); } }; return executeWithDrop([guard], execute); }