All files / builder hashes.ts

62.5% Statements 25/40
11.11% Branches 1/9
42.85% Functions 3/7
62.5% Lines 25/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 1101x                   1x   1x     2x     2x 2x 2x   86x 86x 86x       2x     1x     2x   2x                 2x     1x         2x   2x       2x 2x                       2x       2x 2x       2x                   1x                                    
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) {
      Iif (typeof r === "string") {
        requiredFeatureKeys.push(r);
      }
 
      Iif (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;
}