All files helpers.ts

97.87% Statements 92/94
94.73% Branches 18/19
100% Functions 4/4
97.87% Lines 92/94

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 951x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 308x 308x 308x 308x 308x 308x 268x     268x 268x 268x 308x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 40x 40x 40x 40x 40x 40x 40x 201x 201x 201x 67x 201x 134x 181x 181x 134x 201x 40x 40x 40x 40x 40x 40x 40x 238x 3x 3x 3x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 238x 37x 1x 1x 1x 1x 1x 1x 1x 40x 712x 712x 712x 712x 712x 712x 712x 40x 40x 40x  
/**
 * Copyright (c) OpenLens Authors. All rights reserved.
 * Licensed under MIT License. See LICENSE in root directory for more information.
 */
import type { DiContainerForInjection, InjectionInstanceWithMeta } from "@ogre-tools/injectable";
import { getOrInsertSetFor, isDefined } from "@k8slens/utilities";
import * as uuid from "uuid";
import assert from "assert";
import type { Runnable, RunnableSync, RunnableSyncWithId, RunnableWithId } from "./types";
 
const computedNextEdge = (traversed: string[], graph: Map<string, Set<string>>, currentId: string, seenIds: Set<string>) => {
  seenIds.add(currentId);
  const currentNode = graph.get(currentId);
 
  assert(currentNode, `Runnable graph does not contain node with id="${currentId}"`);
 
  for (const nextId of currentNode.values()) {
    if (traversed.includes(nextId)) {
      throw new Error(`Cycle in runnable graph: "${traversed.join(`" -> "`)}" -> "${nextId}"`);
    }
 
    computedNextEdge([...traversed, nextId], graph, nextId, seenIds);
  }
};
 
/**
 * Verifies that the graph produces by `runnables`'s `runAfter`'s is acyclic. Namely that it
 * produces a Directed Acyclic Graph.
 * @param tokenId The ID of the injectionToken that was `injectManyWithMeta()`-ed. Used for error messages
 * @param runnables The list of runnables to check.
 */
export function verifyRunnablesAreDAG<Param>(tokenId: string, runnables: RunnableWithId<Param>[]): void;
export function verifyRunnablesAreDAG<Param>(tokenId: string, runnables: RunnableSyncWithId<Param>[]): void;
 
export function verifyRunnablesAreDAG<Param>(tokenId: string, runnables: (RunnableWithId<Param>[]) | (RunnableSyncWithId<Param>[])): void {
  const rootId = uuid.v4();
  const runnableGraph = new Map<string, Set<string>>();
  const seenIds = new Set<string>();
  const addRunnableId = getOrInsertSetFor(runnableGraph);
 
  // Build the Directed graph
  for (const runnable of runnables) {
    addRunnableId(runnable.id);
 
    if (runnable.runAfter.length === 0) {
      addRunnableId(rootId).add(runnable.id);
    } else {
      for (const parentRunnable of runnable.runAfter) {
        addRunnableId(parentRunnable.id).add(runnable.id);
      }
    }
  }
 
  addRunnableId(rootId);
 
  // Do a DFS to find any cycles
  computedNextEdge([], runnableGraph, rootId, seenIds);
 
  for (const id of runnableGraph.keys()) {
    if (!seenIds.has(id)) {
      const runnable = runnables.find(runnable => runnable.id === id);
 
      if (!runnable) {
        throw new Error(`Runnable "${id}" is not part of the injection token "${tokenId}"`);
      }
 
      const runAfters = [runnable.runAfter]
        .flat()
        .filter(isDefined)
        .map(runnable => runnable.id)
        .join('", "');
 
      throw new Error(`Runnable "${id}" is unreachable for injection token "${tokenId}": run afters "${runAfters}" are a part of different injection tokens.`);
    }
  }
}
 
export interface ConvertToWithId {
  <Param>(src: InjectionInstanceWithMeta<Runnable<Param>>): RunnableWithId<Param>;
  <Param>(src: InjectionInstanceWithMeta<RunnableSync<Param>>): RunnableSyncWithId<Param>;
}
 
export const convertToWithIdWith = (di: DiContainerForInjection) => {
  const convert = <Param>(meta: { id: string }, instance: Runnable<Param>): RunnableWithId<Param> => ({
    id: meta.id,
    run: instance.run,
    runAfter: [instance.runAfter]
      .flat()
      .filter(isDefined)
      .map((injectable) => convert(injectable, di.inject(injectable))),
  });
 
  return ((src) => convert(src.meta, src.instance)) as ConvertToWithId;
};