Search History Cleanup Logic in MiniSearch: Retention Days, Max Entries, and Auto-Cleanup Explained
MiniSearch automatically cleans up search history by removing entries older than a configured retention period and enforcing a maximum entry limit, while exempting pinned items from deletion.
The MiniSearch client maintains a persistent record of every search query using an IndexedDB database managed by Dexie (client/modules/history.ts). When users enable automatic maintenance, the system evaluates each new search entry against three configurable thresholds to prevent unbounded storage growth. Understanding this search history cleanup logic is essential for developers customizing MiniSearch deployments or troubleshooting data retention issues.
How Search History Cleanup Works in MiniSearch
The cleanup mechanism is triggered reactively. Every time a new search is created, a Dexie hook (this.searches.hook("creating", …)) invokes the performCleanup() method. This design ensures that storage maintenance happens automatically without blocking the user interface, running asynchronously after the new entry is persisted.
The routine operates in two distinct phases: first removing stale entries based on age, then enforcing a hard ceiling on total record count. Both phases respect the pinned status of entries, ensuring that user-saved searches survive all automatic purges.
The Three Core Settings Controlling Cleanup
User preferences are retrieved from a centralized settings store (client/modules/pubSub.ts) at the start of each cleanup cycle. These three boolean and numeric values determine whether and how aggressively the system prunes history.
Auto-Cleanup Toggle
The historyAutoCleanup setting acts as a master switch. When set to false, performCleanup() returns immediately without querying the database or evaluating other thresholds. This allows users to maintain an unlimited history indefinitely, provided they have sufficient storage.
Retention Days
The historyRetentionDays setting defines the maximum age (in days) for non-pinned entries. The cleanup routine calculates a cutoff timestamp using Date.now() - retentionDays * 24 * 60 * 60 * 1000. Any entry with a timestamp value older than this cutoff becomes eligible for deletion.
To prevent UI blocking during large deletions, the query limits results to 100 rows per execution. If additional stale entries remain, they will be removed during subsequent cleanup cycles triggered by future searches.
Maximum Entries
The historyMaxEntries setting establishes a hard cap on the total number of stored searches. Unlike the retention-days rule, which targets age, this rule targets volume. The routine first counts all rows (await this.searches.count()). If the total exceeds maxEntries, it identifies the oldest excess entries using an offset query:
const excess = await this.searches
.orderBy("timestamp")
.reverse()
.offset(settings.maxEntries)
.filter(search => !search.pinned)
.toArray();
All entries in the resulting array are then bulk-deleted. This ensures the database size remains strictly bounded while preserving the most recent activity.
Step-by-Step Cleanup Execution Logic
Understanding the precise sequence helps developers debug why certain entries persist or disappear.
Phase 1: Removing Entries by Retention Days
First, the system targets chronological staleness. It queries for entries where timestamp < cutoff and pinned === false, limiting the batch to 100 records. Found entries are removed in a single bulk operation, and a log entry records the count of deleted items.
Phase 2: Enforcing the Maximum Entry Limit
Second, the system addresses capacity. It counts total rows. If the count exceeds maxEntries, it calculates how many rows must go (total - maxEntries). It then fetches that many oldest non-pinned entries (skipping the newest maxEntries rows) and deletes them. This two-phase approach prioritizes removing ancient entries first, then trimming excess volume if the database remains too large.
Error Handling and Safety Mechanisms
The entire cleanup routine is wrapped in a try…catch block. If any database operation fails—whether due to storage quotas, corrupted indexes, or transaction conflicts—the error is caught and logged to the console. The failure does not propagate upward, ensuring that the creation of the new search entry (which triggered the cleanup) succeeds regardless of maintenance errors.
Code Example: Manual Cleanup Trigger
While cleanup runs automatically, developers can force immediate maintenance using the HistoryDatabase class. This is useful for "Clear History" buttons or administrative tools:
import { HistoryDatabase } from "@/modules/history";
// Instantiate the database (singleton pattern used throughout the app)
const db = new HistoryDatabase();
// Execute cleanup immediately using current settings
await db.performCleanup();
This call respects all user-configured thresholds (historyRetentionDays, historyMaxEntries) and the historyAutoCleanup toggle. If auto-cleanup is disabled, the function returns immediately without database operations.
Summary
- Automatic triggering occurs via a Dexie
creatinghook inclient/modules/history.tsevery time a new search is stored. - Three settings govern behavior:
historyAutoCleanup(master switch),historyRetentionDays(age limit), andhistoryMaxEntries(volume cap). - Two-phase deletion first removes entries older than the retention period (batch-limited to 100), then trims excess entries if the total count exceeds the maximum.
- Pinned protection ensures entries marked as pinned survive both cleanup phases.
- Error resilience prevents cleanup failures from interrupting the search creation flow.
Frequently Asked Questions
How does MiniSearch determine which entries to delete first?
MiniSearch uses a two-phase priority system. First, it deletes entries exceeding the retention days threshold, targeting the oldest non-pinned items regardless of total count. Second, if the database still contains more entries than the max entries limit allows, it deletes the oldest excess entries. Pinned entries are filtered out of both queries and are never automatically deleted.
What happens if I disable auto-cleanup?
When historyAutoCleanup is set to false in the settings, the performCleanup() function returns immediately without executing any database queries. Your search history will grow indefinitely, subject only to browser storage quotas. You can still manually trigger cleanup via the HistoryDatabase API if needed.
Are pinned search entries ever deleted automatically?
No. The cleanup logic explicitly filters out pinned entries using .filter(search => !search.pinned) in both the retention-days and max-entries deletion phases. Pinned entries persist until the user manually unpins them or clears the database entirely. This design ensures important searches remain accessible regardless of age or storage pressure.
Can I trigger search history cleanup manually?
Yes. While the system runs cleanup automatically after each new search, you can invoke it manually by instantiating HistoryDatabase from client/modules/history.ts and calling await db.performCleanup(). This respects all current user settings, meaning it will only execute deletions if historyAutoCleanup is enabled and thresholds are exceeded.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →