Factory class for querying all suppliers.
Value to query for
Maximum number of results for each supplier
Fetch controller (can be used to terminate the query)
Array of suppliers to query (empty is the same as querying all)
Whether to read from / write to the supplier caches. Defaults to true.
OptionalfuzzScorerOverride: stringOptional fuzz scorer name (from FUZZ_SCORERS)
that overrides each supplier's default fuzzScorer. Omit or pass
undefined to respect per-supplier defaults.
When true, suppliers that return zero results for the query will skip writing a cache entry. Defaults to false.
Maximum age (in minutes) of a query cache entry before it is evicted on read. 0 disables TTL expiration. Defaults to 0.
StaticsupplierGet the list of available supplier module names. Use these names when specifying which suppliers to query in the constructor.
Array of supplier class names that can be queried
const suppliers = SupplierFactory.supplierList();
// Returns: ["SupplierCarolina", "SupplierLaballey", "SupplierBioFuranChem", ...]
// Use these names to create a targeted factory
const factory = new SupplierFactory("acid", controller, suppliers);
public static supplierList(): Array<string> {
return Object.keys(suppliers);
}
StaticsupplierGet a map of supplier module names to their display names.
Record mapping supplier class names to their supplierName property
public static supplierDisplayNames(): Record<string, string> {
const controller = new AbortController();
return Object.fromEntries(
mapDefined(Object.entries(suppliers), ([key, SupplierClass]) => {
const ConcreteClass = SupplierClass as unknown as SupplierConstructor<Product>;
const instance = new ConcreteClass("", 1, controller);
return [key, instance.supplierName];
}),
);
}
StaticsupplierGet a map of supplier class names to their required host origins. Creates throwaway instances to read requiredHosts from each supplier.
Record mapping supplier class names to their requiredHosts arrays
public static supplierRequiredHosts(): Record<string, string[]> {
const controller = new AbortController();
return Object.fromEntries(
mapDefined(Object.entries(suppliers), ([key, SupplierClass]) => {
const ConcreteClass = SupplierClass as unknown as SupplierConstructor<Product>;
const instance = new ConcreteClass("", 1, controller);
return [key, instance.requiredHosts];
}),
);
}
PrivatefilterFilters supplier instances to only those whose required host permissions are already granted. Uses chrome.permissions.contains() which is a passive check — it does not prompt the user. Permission granting should be handled separately in a UI flow (e.g., a settings page).
Array of supplier instances to check
Filtered array of suppliers with granted permissions
private async filterByPermissions<P extends Product>(
instances: SupplierBase<unknown, P>[],
): Promise<SupplierBase<unknown, P>[]> {
const results = await Promise.all(
instances.map(async (instance) => {
if (instance.requiredHosts.length === 0) return { instance, granted: true };
try {
const granted = await chrome.permissions.contains({ origins: instance.requiredHosts });
if (!granted) {
this.logger.warn("Permission check failed for supplier", {
supplier: instance.supplierName,
requiredHosts: instance.requiredHosts,
});
}
return { instance, granted };
} catch (e) {
this.logger.error("Permission check failed for supplier", {
supplier: instance.supplierName,
error: e,
});
return { instance, granted: false };
}
}),
);
return results.filter((r) => r.granted).map((r) => r.instance);
}
Executes the execute() method on all selected suppliers in parallel using async-await-queue. Results are collected and flattened into a single array.
Maximum number of suppliers to process in parallel (default: 3)
Promise resolving to an array of all products from all suppliers
const factory = new SupplierFactory("acetone", 5, new AbortController());
const allProducts = await factory.executeAll(3); // 3 suppliers in parallel
console.log(allProducts);
public async executeAll(concurrency: number = 3): Promise<P[]> {
// 1. Instantiate supplier classes
const supplierInstances: SupplierBase<unknown, P>[] = mapDefined(
Object.entries(suppliers),
([supplierClassName, supplierClass]) => {
if (!(this.suppliers.length === 0 || this.suppliers.includes(supplierClassName))) return;
this.logger.debug("Initializing supplier class:", supplierClassName);
const ConcreteSupplierClass = supplierClass as unknown as SupplierConstructor<P>;
const instance = new ConcreteSupplierClass(this.query, this.limit, this.controller);
instance.initCache(this.caching, this.doNotCacheEmptyResults, this.cacheTtlMinutes);
instance.setFuzzScorerOverride(this.fuzzScorerOverride);
return instance;
},
);
// 2. Filter to only suppliers with granted host permissions
const permittedInstances = await this.filterByPermissions(supplierInstances);
// 3. Use async-await-queue for parallel execution
const queue = new Queue(concurrency, 100);
const allResults: P[] = [];
const errors: SupplierExecutionError<P>[] = [];
const tasks = permittedInstances.map((supplier) =>
queue.run(async () => {
try {
for await (const product of supplier.execute()) {
allResults.push(product);
}
} catch (e) {
this.logger.error("Error executing supplier", { error: e, supplier });
incrementParseError(supplier.supplierName);
errors.push({ error: e, supplier });
}
}),
);
await Promise.all(tasks);
// Optionally, you can return errors as well
// return { products: allResults, errors };
return allResults;
}
Streams products from all selected suppliers as soon as each supplier's execute() resolves. Uses async-await-queue for concurrency control and yields products as they are available.
Maximum number of suppliers to process in parallel (default: 3)
AsyncGenerator yielding products from all suppliers as soon as they are ready
for await (const product of factory.executeAllStream(3)) {
console.log(product);
}
public async *executeAllStream(concurrency: number = 3): AsyncGenerator<P, void, undefined> {
const supplierInstances: SupplierBase<unknown, P>[] = mapDefined(
Object.entries(suppliers),
([supplierClassName, supplierClass]) => {
if (!(this.suppliers.length === 0 || this.suppliers.includes(supplierClassName))) return;
this.logger.debug("Initializing supplier class", { supplierClassName });
const ConcreteSupplierClass = supplierClass as unknown as SupplierConstructor<P>;
console.log("Initializing supplier class...", { supplierClassName, ConcreteSupplierClass });
const instance = new ConcreteSupplierClass(this.query, this.limit, this.controller);
console.log("After initializing supplier class", { supplierClassName, instance });
instance.initCache(this.caching, this.doNotCacheEmptyResults, this.cacheTtlMinutes);
instance.setFuzzScorerOverride(this.fuzzScorerOverride);
return instance;
},
);
// Filter to only suppliers with granted host permissions
const permittedInstances = await this.filterByPermissions(supplierInstances);
console.log("After filtering by permissions", { permittedInstances });
const queue = new Queue(concurrency, 100);
const channel: P[] = [];
let doneCount = 0;
permittedInstances.forEach((supplier) => {
console.log("Running queue", { supplier });
queue.run(async () => {
try {
const iterator = supplier.execute();
for await (const product of iterator) {
channel.push(product);
}
} catch (e) {
this.logger.error("Error executing supplier", { error: e, supplier });
incrementParseError(supplier.supplierName);
} finally {
doneCount++;
}
});
});
// Yield results as they come in, until all suppliers are done and the channel is empty
while (doneCount < permittedInstances.length || channel.length > 0) {
if (channel.length > 0) {
yield channel.shift()!;
} else {
await new Promise((resolve) => setTimeout(resolve, 25));
}
}
}
PrivatequeryPrivatecontrollerPrivatesuppliersPrivatelimitPrivatecachingPrivatedoPrivatecachePrivate OptionalfuzzPrivatelogger
Factory class for querying multiple chemical suppliers simultaneously. This class provides a unified interface to search across multiple supplier implementations.
Example
Source