All files / src/impl Idempotent.impl.ts

100% Statements 38/38
100% Branches 4/4
100% Functions 11/11
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96    26x 26x 26x     26x               26x 551x         26x 26x           19x         551x 537x   14x           1182x       551x       537x 537x 537x 536x   1x 1x 1x   536x       537x       551x       458x       551x 549x 458x 456x 456x 456x   456x 456x           551x 551x 551x 551x 551x    
import { Idempotent, Config } from "@jonloucks/contracts-ts/auxiliary/Idempotent";
import { IdempotentState } from "@jonloucks/contracts-ts/auxiliary/IdempotenState";
import { AUTO_CLOSE_NONE, AutoClose, inlineAutoClose } from "@jonloucks/contracts-ts/api/AutoClose";
import { Open, typeToOpen } from "@jonloucks/contracts-ts/api/Open";
import { presentCheck } from "@jonloucks/contracts-ts/auxiliary/Checks";
import { AtomicBoolean } from "@jonloucks/contracts-ts/auxiliary/AtomicBoolean";
 
import { create as createAtomicBoolean } from "./AtomicBoolean.impl";
 
/** 
 * Create a new Idempotent
 *
 * @param config the idempotent configuration
 * @return the new Idempotent
 */
export function create(config: Config): Idempotent {
  return IdempotentImpl.internalCreate(config);
}
 
// ---- Implementation details below ----
 
const IS_CLOSED: boolean = false;
const IS_OPEN: boolean = true;
 
class IdempotentImpl implements Idempotent {
 
  // Idempotent.getState
  getState(): IdempotentState {
    return this.#idempotentState;
  }
 
  // Idempotent.open
  open(): AutoClose {
    if (this.transitionToOpen()) {
      return this.firstOpen();
    } else {
      return AUTO_CLOSE_NONE;
    }
  }
 
  // Idempotent.isOpen
  isOpen(): boolean {
    return this.#flag.get() === IS_OPEN;
  }
 
  static internalCreate(config: Config): Idempotent {
    return new IdempotentImpl(config);
  }
 
  private firstOpen(): AutoClose {
    this.#idempotentState = "OPENING";
    try {
      this.#closeDelegate = this.openDelegate();
      this.#idempotentState = "OPENED";
    } catch (thrown) {
      this.#flag.set(IS_CLOSED);
      this.#idempotentState = "OPENABLE";
      throw thrown;
    }
    return this.#firstClose;
  }
 
  private openDelegate(): AutoClose {
    return presentCheck(this.#delegate.open(), "Close must be present.");
  }
 
  private transitionToOpen(): boolean {
    return this.#flag.compareAndSet(IS_CLOSED, IS_OPEN);
  }
 
  private transitionToClosed(): boolean {
    return this.#flag.compareAndSet(IS_OPEN, IS_CLOSED);
  }
 
  private constructor(config: Config) {
    this.#delegate = typeToOpen(config.open);
    this.#firstClose = inlineAutoClose(() => {
      if (this.transitionToClosed()) {
        this.#idempotentState = "CLOSING";
        try {
             this.#closeDelegate?.close();
        } finally {
          this.#closeDelegate = undefined;
          this.#idempotentState = "CLOSED";
        }
      }
    });
  }
 
  readonly #delegate: Open;
  readonly #firstClose: AutoClose;
  readonly #flag: AtomicBoolean = createAtomicBoolean(IS_CLOSED);
  #closeDelegate: AutoClose | undefined = undefined;
  #idempotentState: IdempotentState = "OPENABLE";
}