All files / src/constants supplierMeta.ts

100% Statements 34/34
100% Branches 9/9
100% Functions 4/4
100% Lines 34/34

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 1351x 1x   1x                                           1x                                           1x 1x   1x                             1x 1x 1197x 34x   1197x 1197x   1x                     1x 1x 139x 139x   139x   1x                     1x 1x       3x 3x 114x 114x 114x   3x   1x                         1x 1x 2x 2x   2x  
import { shipsToCountry } from '@/helpers/shipping';
import { SUPPLIER_META } from './supplierMeta.generated';
 
/**
 * Accessors over the generated supplier metadata registry — supplier display names,
 * home countries and shipping scopes, readable **without loading a single supplier
 * implementation**.
 *
 * The supplier classes are the single source of truth; `SUPPLIER_META` is extracted
 * from their `static` fields at build time by `tools/generate-supplier-meta.js`
 * (`pnpm run generate`, which runs as `prebuild`). Nothing here is hand-maintained,
 * and adding a supplier means editing only its class and the barrel.
 *
 * The indirection exists because reading a class static means *evaluating* that
 * supplier module, and with it SupplierBase's zod/fuzzball/liqe/graphql stack — which
 * is what used to keep the whole supplier layer in the popup's startup bundle, for a
 * results table, column drawer and search hook that need metadata (never a search) at
 * mount. Runtime-derived metadata deliberately stays on the classes: `requiredHosts`
 * is computed from `baseURL`/`apiURL`, and for the Amazon-backed suppliers `baseURL`
 * depends on the user's locale, so it could not be extracted as a literal.
 *
 * @module supplierMeta
 * @category Constants
 * @group Suppliers
 * @source
 */
 
export { SUPPLIER_META };
 
/**
 * Display and shipping metadata for one supplier — the subset of a supplier class's
 * `static` fields the UI needs before any search has run.
 * @category Constants
 * @group Suppliers
 * @source
 */
export interface SupplierMetaEntry {
  /** Human-readable supplier name, mirroring the class's `static supplierName`. */
  readonly displayName: string;
  /** The supplier's home country (ISO 3166-1 alpha-2). */
  readonly country: CountryCode;
  /** The supplier's coarse shipping scope. */
  readonly shipping: ShippingRange;
  /** Explicit destination allowlist; overrides {@link SupplierMetaEntry.shipping} when set. */
  readonly shipsTo?: readonly CountryCode[];
}
 
/** Reverse index of {@link SUPPLIER_META}, display name to class name; built on first use. */
let classNameByDisplayName: ReadonlyMap<string, string> | undefined;
 
/**
 * Resolves a supplier's real class name from its `static supplierName`. Production builds
 * minify class names to single letters, so `constructor.name` can't identify a supplier
 * there; the generated registry is keyed by the real name and survives minification.
 * @param displayName - A supplier class's `static supplierName`
 * @returns The class name (e.g. `"SupplierCarolina"`), or `undefined` for a name the
 * registry doesn't list (disabled suppliers, test doubles)
 * @example
 * ```ts
 * supplierClassNameFor('Carolina'); // => "SupplierCarolina"
 * supplierClassNameFor('Nope');     // => undefined
 * ```
 * @category Constants
 * @group Suppliers
 * @source
 */
export function supplierClassNameFor(displayName: string): string | undefined {
  classNameByDisplayName ??= new Map(
    Object.entries(SUPPLIER_META).map(([className, meta]) => [meta.displayName, className]),
  );
  return classNameByDisplayName.get(displayName);
}
 
/**
 * Supplier class names mapped to their human-readable display names — the shape the
 * results-table filter and the stats panel want.
 * @returns Record mapping supplier class names to their display names.
 * @example
 * ```ts
 * supplierDisplayNames().SupplierCarolina; // => "Carolina"
 * ```
 * @category Constants
 * @group Suppliers
 * @source
 */
export function supplierDisplayNames(): Record<string, string> {
  return Object.fromEntries(
    Object.entries(SUPPLIER_META).map(([key, meta]) => [key, meta.displayName]),
  );
}
 
/**
 * Supplier class names mapped to their home country and shipping scope — the fields
 * the drawer's shipping/country filters reason about.
 * @returns Record mapping supplier class names to `{ country, shipping }`.
 * @example
 * ```ts
 * supplierShippingMeta().SupplierCarolina; // => { country: "US", shipping: "domestic" }
 * ```
 * @category Constants
 * @group Suppliers
 * @source
 */
export function supplierShippingMeta(): Record<
  string,
  { country: CountryCode; shipping: ShippingRange }
> {
  return Object.fromEntries(
    Object.entries(SUPPLIER_META).map(([key, meta]) => [
      key,
      { country: meta.country, shipping: meta.shipping },
    ]),
  );
}
 
/**
 * Supplier class names mapped to whether they ship to `location`, so the UI can grey
 * out suppliers that can't reach the user. Applies the same `shipsTo`/scope heuristic
 * as search time — both go through {@link shipsToCountry}.
 * @param location - The user's location as an ISO 3166-1 alpha-2 country code.
 * @returns Record mapping supplier class names to a ships-to boolean.
 * @example
 * ```ts
 * supplierShipsTo('US').SupplierWarchem; // => false
 * ```
 * @category Constants
 * @group Suppliers
 * @source
 */
export function supplierShipsTo(location: CountryCode): Record<string, boolean> {
  return Object.fromEntries(
    Object.entries(SUPPLIER_META).map(([key, meta]) => [key, shipsToCountry(meta, location)]),
  );
}