All files / src/constants suppliers.ts

100% Statements 24/24
100% Branches 2/2
5% Functions 2/40
100% Lines 24/24

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 1x 1x       1x   1x                   1x 3268x 3268x 3268x 3268x   1x   1x   1x                                   1x 1x 3277x 3277x   1x           1x 1x 1x  
/**
 * The active supplier class names — the value exports of the `@/suppliers` barrel,
 * minus everything under `src/suppliers/disabled/`.
 *
 * This module deliberately imports **no supplier implementation**. It derives the
 * names from a *lazy* `import.meta.glob` of the supplier files: only the glob's
 * keys (file paths) are read and the import thunks are never called, so none of the
 * supplier classes — nor their zod/fuzzball/liqe/jszip deps — are pulled into an
 * importing entry point's bundle. UI that needs supplier *names* (the settings
 * toggle list, the settings-validation schema) reads them here instead of calling
 * `SupplierFactory.supplierList()`, which would drag the whole supplier layer in
 * and, for `typeGuards/common`, form an import cycle.
 *
 * Filenames match their exported class name one-to-one, so a class name is just the
 * file's basename. Non-supplier modules are excluded by pattern — the `SupplierBase*`
 * platform bases, `SupplierFactory`, and the `SupplierFoobar` template — and disabled
 * suppliers are excluded by living in `./disabled/` (glob `*` never crosses `/`).
 *
 * A unit test re-checks the result against the barrel, so adding a supplier file
 * without exporting it from the barrel (or vice versa) fails the suite.
 *
 * @category Constants
 * @group Suppliers
 * @source
 */
 
// Lazy glob (thunks never invoked) — reading only the keys keeps every supplier
// implementation out of the importing bundle. Mirrors the messages.json glob in
// helpers/i18n.ts.
const liveSupplierModules = import.meta.glob([
  '/src/suppliers/Supplier*.ts',
  '!/src/suppliers/SupplierBase*.ts',
  '!/src/suppliers/SupplierFactory.ts',
]);
 
/**
 * Extracts a supplier's class name from its module path — the file's basename
 * without extension, which equals the exported class name one-to-one.
 * @param path - A glob key, e.g. `"/src/suppliers/SupplierCarolina.ts"`.
 * @returns The class name, e.g. `"SupplierCarolina"`.
 * @example
 * ```ts
 * supplierClassNameFromPath('/src/suppliers/SupplierCarolina.ts'); // => "SupplierCarolina"
 * ```
 * @source
 */
function supplierClassNameFromPath(path: string): string {
  const file = path.slice(path.lastIndexOf('/') + 1);
  return file.slice(0, file.lastIndexOf('.'));
}
 
const liveSupplierNames = Object.keys(liveSupplierModules).map(supplierClassNameFromPath).sort();
 
const liveSupplierNameSet = new Set(liveSupplierNames);
 
/**
 * Type guard narrowing an arbitrary string to a live {@link SupplierClassName},
 * checked against the glob-derived live-supplier set — no supplier implementation is
 * imported. Also the mechanism by which {@link SUPPLIER_CLASS_NAMES} is typed without
 * an assertion.
 *
 * Prefer this over `SupplierFactory.isSupplierClassName` anywhere that isn't already
 * running a search: the factory's copy is equivalent but reaching it pulls the whole
 * supplier layer into the caller's bundle.
 * @param value - Candidate string to test.
 * @returns True (and narrows `value`) when it names a live supplier.
 * @example
 * ```ts
 * isSupplierClassName('SupplierCarolina'); // => true
 * ```
 * @category Constants
 * @group Suppliers
 * @source
 */
export function isSupplierClassName(value: string): value is SupplierClassName {
  return liveSupplierNameSet.has(value);
}
 
/**
 * The active supplier class names, sorted, mirroring the value exports of the
 * `@/suppliers` barrel. See the module header for how it stays dependency-free.
 * @category Constants
 * @group Suppliers
 * @source
 */
export const SUPPLIER_CLASS_NAMES: readonly SupplierClassName[] =
  liveSupplierNames.filter(isSupplierClassName);