Reads the persisted search_results row as a whole, returning both the product
data and the query that produced it. Use this (instead of getSearchResults)
when the caller also needs the originating query — e.g. to rehydrate the results
header label so it stays in sync with the persisted results.
Finds product identities that appear more than once, keyed by
productIdentity (ignoring the positional _id). This is detection, not
repair: the same product appearing twice means the search almost certainly ran
twice, so callers surface it rather than silently removing the duplicates.
Persists the current search results (and the query that produced them) to the
single "current" search_results row. Storing the query alongside data
keeps the two in lockstep, so the results header can rehydrate the query even
when the transient session copy is gone. Duplicate products are logged (not
removed); an empty result set dispatches IDB_SEARCH_RESULTS_CLEARED.
Clears all persisted search results from IndexedDB.
By default this dispatches IDB_SEARCH_RESULTS_CLEARED so listeners
(e.g. the App) can react to an explicit, user-initiated clear. Pass
{ notify: false } to clear silently — used when resetting the store at the
start of a new search, where firing the event would bounce the user off the
results panel.
Reads every search-history entry, newest first.
Appends a search-history entry, evicting the oldest rows once the store
exceeds maxHistoryEntries from config.json.
Backfills the result count on an existing history entry, which is written before the search finishes and so starts out without one. No-ops when the entry is gone (e.g. already evicted).
Removes every search-history entry.
Reads one supplier query-cache entry — a cached result set keyed by query+supplier — along with its cache metadata.
Writes a supplier query-cache entry, evicting the least recently cached row
first when the store is at maxSupplierCacheEntries capacity.
Removes a single supplier query-cache entry, e.g. when it fails TTL validation.
Read every entry in the supplier query cache — the cached search-result
sets keyed by query+supplier. Intended for manual inspection from the debug
console; each record carries its cacheKey, the dumped data, and its
__cacheMetadata (query, supplier, cachedAt, etc.). Returns [] on failure.
Empties the supplier query cache, forcing the next search to refetch.
Reads one entry from the supplier product data cache, which holds per-product detail fetched lazily after a search.
Writes a supplier product-data cache entry, evicting the oldest row first
when the store is at maxSupplierCacheEntries capacity.
Deletes a single product-detail cache entry by its identity key. Used to evict one product from the cache (e.g. the "Remove Product from Cache" context-menu action) so its detail data is re-fetched fresh the next time it is surfaced, without touching the visible results.
Read every entry in the supplier product-detail cache — the enriched
per-product data keyed by product identity. Intended for manual inspection
from the debug console; each record carries its cacheKey, the cached data,
and its last-access timestamp. Returns [] on failure.
Empties the supplier product-data cache.
Reads the per-supplier stats recorded for one day.
Writes one day's per-supplier stats and notifies open surfaces by dispatching IDB_SUPPLIER_STATS_UPDATED, so the stats panel re-reads without polling.
Reads every day of supplier stats, flattened into a dateKey -> suppliers map.
Deletes specific days of supplier stats in one transaction, used to prune days that have aged out of the retention window.
Removes all recorded supplier stats.
Load the user's excluded-products map from the excludedProducts object
store. The whole map lives under a single row keyed by "current" (same
single-row pattern used by searchResults), so this is a single read.
Returns {} when the store is empty or the read fails, letting callers
treat the result as always-valid without null-checking.
Write the full excluded-products map back into IndexedDB, overwriting the
previous value. Callers typically read via getExcludedProducts, mutate
the returned object, and pass it back here — the object store holds one
row keyed by "current" so each call is a complete replacement.
Remove the excluded-products row from IndexedDB entirely. After this, a
subsequent getExcludedProducts() returns {}. Used by the bulk
clearAllCaches flow; callers don't typically invoke this directly.
Read a single price-history series by its id. The id is the product's
identity key for the base row, or ${productKey}::${variantKey} for a
variant row. Returns undefined when no series exists yet (the first time a
product/variant is seen) or when the read fails.
Write a price-history series, overwriting any existing row with the same id.
Callers read via getPriceSeries, append a point, then pass the whole
entry back — the store holds one row per series (keyed by id) so each call
is a complete replacement.
Read every price-history series belonging to a product — its base row plus
one row per tracked variant — via the productKey index. Used by the detail
panel to render a product's full price history in one query. Returns []
when the product has no recorded history or the read fails.
Read every stored price-history series (all base rows and variant rows across every product). The app reads per-product via getPriceSeriesByProduct; this full-store read backs dev/debug tooling that needs the whole set.
Delete all price-history series. Exposed via the "Clear price history" button in the settings panel. Deliberately kept out of clearAllCaches so a routine cache clear never destroys the user's accumulated price history.
Read the app version that last wrote or migrated the cache, from the single
app_meta row keyed "current". Returns undefined on a fresh install (the
row has never been written) or when the read fails — callers treat undefined
as "no marker yet" and seed the current version instead of migrating.
Record the app version that now owns the cache, overwriting the single
app_meta row. Called after migrations complete, after a fresh-install seed,
and after a Cancel/reset, so the stored marker always reflects the version
whose data shape is currently in IndexedDB.
Open the ChemPal database as an untyped idb handle for migration steps.
Unlike getDB, this returns IDBPDatabase without the schema generic, so
step code can read and write records in stores whose current-schema shape no
longer matches the data on disk (renamed stores, old record shapes) using plain
string store names — no type assertions required. The structural schema is
still owned by getDB's upgrade; this connection never upgrades.
Stores a generated .xlsx export, then evicts the oldest exports until the
store is within both caps: at most maxExportEntries records and at most
maxExportsCacheBytes total bytes. The most recent export is always kept, even
if it alone exceeds the byte budget.
Reads every cached export, newest first, for the export-history list.
Deletes a single cached export by id.
Empties the export cache.
Measure every IndexedDB object store: the number of records it holds and the
byte size of its contents when serialized to JSON. Used by the settings panel
to show cache/price-history sizes; the summed totalBytes can be paired with
navigator.storage.estimate() to scale each store's JSON size up to its true
on-disk footprint (indexes, keys, structured-clone overhead). Returns all-zero
counts on failure.
Clears every cache-bearing store in one transaction: search results, history, both supplier caches, stats, and excluded products. Backs the "clear cache" action in settings.
A cached spreadsheet export: the generated .xlsx Blob plus the metadata the
export-history list displays. Stored in the exports object store.
Per-store record count + serialized JSON size, plus the summed total.
Custom event name dispatched when search results are cleared.
Consumers listen for this via window.addEventListener to replace
the former cstorage.onChanged pattern for search_results.
Custom event name dispatched when supplier stats are updated. Consumers listen for this to live-refresh stats during searches.
Custom event name dispatched when the cached exports change (added, deleted, or cleared). The export-history list listens for this to live-refresh.
Reads the persisted search results. The row is stored under a single
"current"key, so this is one read. See getSearchResultsRecord when the originating query is needed too.