All files / src/helpers productIdentity.ts

100% Statements 32/32
81.25% Branches 13/16
100% Functions 3/3
100% Lines 32/32

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 881x   1x                                             1x 1x 195x 195x   1x                               1x 1x 41x 41x 6x 6x 41x 41x 41x 41x   1x                               1x 1x 10x 10x 10x 25x 25x 23x 20x 20x 22x 22x 10x 10x  
import { md5 } from 'js-md5';
 
/**
 * Derive the stable per-product cache/exclusion key from a supplier-defined
 * identity and the supplier name. Both the product-detail cache
 * (`SupplierCache.getProductIdentityCacheKey`) and the exclusion store
 * (`addExcludedProduct`) route through this one function, so a product's cache
 * entry and its exclusion entry share the same key by construction.
 *
 * The `{ key, supplier }` shape is intentionally distinct from the legacy
 * URL-based product-data key (`md5({ url, params, supplier })`), so entries
 * written under the old scheme never alias a new identity key.
 *
 * @category Helpers
 * @param identity - The supplier's unique product identity (id, uuid, sku,
 *   gid, or href), as returned by `SupplierBase.getUniqueProductKey`.
 * @param supplierName - Supplier name, e.g. `"Loudwolf"`, mixed in so two
 *   suppliers that happen to share an identity string never collide.
 * @returns MD5 hex digest used as the cache/exclusion key.
 * @example
 * ```ts
 * getProductIdentityKey("FAM_889460", "Carolina");
 * // => "3f9c2a…" (stable for the same identity + supplier)
 * ```
 * @source
 */
export function getProductIdentityKey(identity: string, supplierName: string): string {
  return md5(JSON.stringify({ key: identity, supplier: supplierName }));
}
 
/**
 * Derive a supplier-scoped dedupe key for a product, used to drop the same
 * product appearing more than once in a single search's result set. Prefers the
 * supplier-stable `cacheKey`, then `id`, `uuid`, and finally `url`, all mixed
 * with the supplier name so two suppliers that share an identity string (e.g. a
 * numeric `id` like `6981`) never collide. Returns `undefined` when the product
 * carries no usable identity, so callers keep it rather than merging unknowns.
 * @category Helpers
 * @param product - The product to key.
 * @returns A stable dedupe key, or `undefined` when no identity is available.
 * @example
 * ```ts
 * getProductDedupeKey({ supplier: "Carolina Chemical", cacheKey: "6981" }); // "3f9c2a…"
 * getProductDedupeKey({ supplier: "ACME" }); // undefined (no identity)
 * ```
 * @source
 */
export function getProductDedupeKey(product: Product): string | undefined {
  const identity =
    product.cacheKey ??
    (product.id != null ? String(product.id) : undefined) ??
    (product.uuid != null ? String(product.uuid) : undefined) ??
    product.url;
  if (!identity) return undefined;
  return getProductIdentityKey(identity, product.supplier ?? '');
}
 
/**
 * Remove duplicate products from a result set, keeping the first occurrence of
 * each identity as determined by {@link getProductDedupeKey}. Products with no
 * usable identity are always kept (never merged with each other). Order is
 * preserved.
 * @category Helpers
 * @param products - The products to de-duplicate.
 * @returns A new array with later duplicates of each product identity removed.
 * @example
 * ```ts
 * dedupeProducts([
 *   { supplier: "Carolina Chemical", cacheKey: "6981" },
 *   { supplier: "Carolina Chemical", cacheKey: "6981" },
 * ]).length; // 1
 * ```
 * @source
 */
export function dedupeProducts(products: readonly Product[]): Product[] {
  const seen = new Set<string>();
  const result: Product[] = [];
  for (const product of products) {
    const key = getProductDedupeKey(product);
    if (key !== undefined) {
      if (seen.has(key)) continue;
      seen.add(key);
    }
    result.push(product);
  }
  return result;
}