All files / src/builder hashes.ts

65% Statements 26/40
23.07% Branches 3/13
42.85% Functions 3/7
65% Lines 26/40

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 1103x                   3x   3x     15x     15x 15x 15x   643x 643x 643x       15x     9x     9x   9x 5x 5x             9x     10x         10x   10x       10x 10x                       10x       10x 10x       10x                                                        
import * as crypto from "crypto";
 
import type {
  FeatureKey,
  Feature,
  SegmentKey,
  Segment,
  DatafileContent,
} from "@featurevisor/types";
 
import { extractSegmentsFromFeature } from "../utils";
 
const base62chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
function generateHashFromString(str: string, length = 10): string {
  const hashBuffer = crypto.createHash("sha256").update(str).digest();
 
  // Convert buffer to base62 (alphanumeric)
  let num = BigInt("0x" + hashBuffer.toString("hex"));
  let base62 = "";
  while (num > 0) {
    // Convert the remainder to a number for indexing
    const remainder = Number(num % 62n);
    base62 = base62chars[remainder] + base62;
    num = num / 62n;
  }
 
  // Return first 10 chars for a short hash (adjust length as needed)
  return base62.slice(0, length);
}
 
export function getSegmentHashes(
  segments: Record<SegmentKey, Segment>,
): Record<SegmentKey, string> {
  const result: Record<SegmentKey, string> = {};
 
  for (const segmentKey of Object.keys(segments)) {
    const segment = segments[segmentKey];
    result[segmentKey] = generateHashFromString(
      JSON.stringify({
        conditions: segment.conditions,
      }),
    );
  }
 
  return result;
}
 
export function generateHashForFeature(
  featureKey: FeatureKey,
  features: Record<FeatureKey, Feature>,
  segmentHashes: Record<SegmentKey, string>,
): string {
  const feature = features[featureKey];
 
  Iif (!feature) {
    return "";
  }
 
  const requiredFeatureKeys: string[] = [];
  Iif (feature.required) {
    for (const r of feature.required) {
      if (typeof r === "string") {
        requiredFeatureKeys.push(r);
      }
 
      if (typeof r === "object" && r.key) {
        requiredFeatureKeys.push(r.key);
      }
    }
  }
 
  const requiredFeatureHashes = requiredFeatureKeys.map((key) =>
    generateHashForFeature(key, features, segmentHashes),
  );
 
  const usedSegments = extractSegmentsFromFeature(feature);
  const usedSegmentHashes = Object.keys(usedSegments).map(
    (segmentKey) => segmentHashes[segmentKey],
  );
 
  return generateHashFromString(
    JSON.stringify({
      featureKey,
      feature,
      requiredFeatureHashes,
      usedSegmentHashes,
    }),
  );
}
 
export function generateHashForDatafile(datafileContent: DatafileContent): string {
  const featureHashes = Object.keys(datafileContent.features).reduce(
    (acc, featureKey) => {
      acc[featureKey] = datafileContent.features[featureKey].hash || "";
      return acc;
    },
    {} as Record<FeatureKey, string>,
  );
 
  const hash = generateHashFromString(
    JSON.stringify({
      schemaVersion: datafileContent.schemaVersion,
      featureHashes,
    }),
  );
 
  return hash;
}