All files / src/impl Repository.impl.ts

94.54% Statements 52/55
85.71% Branches 12/14
88.23% Functions 15/17
96.29% Lines 52/54

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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 15725x   25x   25x   25x     25x     25x 25x               25x 183x                                         185x             1171x 1171x 1171x   1171x 1x     1170x   1170x 9x     1169x   1169x 19x 5x 5x                 1166x             185x 2x 1x                 2x   2x             1x       183x 183x 183x           181x 1160x   181x 181x 156x         156x 156x 988x   156x 156x 988x     156x         183x     25x   183x 183x   183x 181x   183x        
import { AutoClose, AutoCloseType, inlineAutoClose } from "@jonloucks/contracts-ts/api/AutoClose";
import { AutoOpen } from "@jonloucks/contracts-ts/api/AutoOpen";
import { BindStrategy, resolveBindStrategy } from "@jonloucks/contracts-ts/api/BindStrategy";
import { Contract } from "@jonloucks/contracts-ts/api/Contract";
import { ContractException } from "@jonloucks/contracts-ts/api/ContractException";
import { Contracts } from "@jonloucks/contracts-ts/api/Contracts";
import { Promisor, PromisorType, typeToPromisor } from "@jonloucks/contracts-ts/api/Promisor";
import { Config, Repository } from "@jonloucks/contracts-ts/api/Repository";
import { RequiredType, OptionalType } from "@jonloucks/contracts-ts/api/Types";
import { contractCheck, contractsCheck } from "@jonloucks/contracts-ts/auxiliary/Checks";
import { Idempotent } from "@jonloucks/contracts-ts/auxiliary/Idempotent";
 
import { create as createIdempotent } from "./Idempotent.impl";
import { StorageImpl } from "./Storage.impl";
 
/**
 * Factory method to create Repository instance.
 * 
 * @param contracts the Contracts instance to be used by the Repository
 * @returns the Repository implementation
 */
export function create(config?: Config): RequiredType<Repository> {
  return RepositoryImpl.internalCreate(config);
}
 
// ---- Implementation details below ----
 
/**
 * Implementation for Repository
 */
class RepositoryImpl implements Repository, AutoOpen {
 
  /**
   * AutoOpen.autoOpen override.
   */
  autoOpen(): AutoClose {
    return this.open();
  }
 
  /**
   * Open.open
   */
  open(): AutoClose {
    return this.idempotent.open();
  }
 
  /**
   * Repository.store override.
   */
  store<T>(contract: Contract<T>, promisor: PromisorType<T>, bindStrategy?: BindStrategy | undefined | null): AutoClose {
    const validContract: Contract<T> = contractCheck(contract);
    const validPromisor: Promisor<T> = typeToPromisor(promisor);
    const validBindStrategy: BindStrategy = resolveBindStrategy(bindStrategy);
 
    if (this.#storedContracts.has(validContract) && this.idempotent.isOpen()) {
      throw new ContractException("The contract " + validContract + "  is already stored.");
    }
 
    const storage: StorageImpl<T | null> = new StorageImpl<T | null>(this.contracts, validContract, validPromisor, validBindStrategy);
 
    if (this.idempotent.isOpen()) {
      storage.bind();
    }
 
    this.#storedContracts.set(validContract, storage);
 
    return inlineAutoClose(() => {
      if (this.#storedContracts.get(validContract) === storage) {
        this.#storedContracts.delete(validContract);
        storage.close();
      }
    });
  }
 
  /**
   * Repository.keep override.
   */
  keep<T>(contract: Contract<T>, promisor: PromisorType<T>, bindStrategy?: OptionalType<BindStrategy>): void {
    this.store(contract, promisor, resolveBindStrategy(bindStrategy));
  }
 
  /**
   * Repository.check override.
   */
  check(): void {
    this.#requiredContracts.forEach((contract) => {
      if (!this.contracts.isBound(contract)) {
        throw new ContractException("The contract " + contract + " is required.");
      }
    });
  }
 
  /**
   * Repository.require override.
   */
  require<T>(contract: Contract<T>): void {
    const validContract: Contract<T> = contractCheck(contract);
 
    this.#requiredContracts.add(validContract);
  }
 
  /**
   * Object.toString override.
   */
  toString(): string {
    return `Repository(id=${this.id}, size=${this.#storedContracts.size})`;
  }
 
  private constructor(config?: Config) {
    const validConfig: Config = config ?? {};
    this.contracts = contractsCheck(validConfig.contracts);
    Iif (validConfig.requiredContracts) {
      validConfig.requiredContracts.forEach((contract) => this.require(contract));
    }
  }
 
  private firstOpen(): AutoCloseType {
    for (const storage of this.#storedContracts.values()) {
      storage.bind();
    }
    this.check();
    return () => {
      this.reverseCloseStorage();
    }
  }
 
  private reverseCloseStorage(): void {
    const storageStack: StorageImpl<unknown>[] = [];
    for (const storage of this.#storedContracts.values()) {
      storageStack.push(storage);
    }
    try {
      while (storageStack.length > 0) {
        storageStack.pop()!.close();
      }
    } finally {
      this.#storedContracts.clear();
    }
  }
 
  static internalCreate(config?: Config): RequiredType<Repository> {
    return new RepositoryImpl(config);
  }
 
  private static ID_GENERATOR: number = 1;
 
  private readonly id: number = RepositoryImpl.ID_GENERATOR++;
  readonly #storedContracts = new Map<Contract<unknown>, StorageImpl<unknown>>();
  private readonly contracts: Contracts;
  private readonly idempotent: Idempotent = createIdempotent({
    open: () => this.firstOpen()
  });
  readonly #requiredContracts: Set<Contract<unknown>> = new Set<Contract<unknown>>();
}