# How MiniSearch Search Caching Works with IndexedDB, TTL, and Max Entry Limits

> Discover how MiniSearch search caching uses IndexedDB, TTL, and max entries to efficiently store and manage query results, ensuring fast performance and controlled storage.

- Repository: [Victor Nogueira/minisearch](https://github.com/felladrin/minisearch)
- Tags: internals
- Published: 2026-03-01

---

**MiniSearch caches text and image query results in IndexedDB via the Dexie wrapper, automatically expiring entries after a 15-minute TTL and pruning stores to a maximum of 100 entries to prevent unbounded storage growth.**

The felladrin/minisearch repository implements a sophisticated client-side caching layer that accelerates repeated searches while rigorously managing browser storage quotas. Understanding the MiniSearch search caching mechanism reveals how the application balances query performance against resource constraints through time-based expiration and hard entry limits configured in [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts).

## IndexedDB Cache Architecture and Configuration

MiniSearch persists query results using IndexedDB through the Dexie library, creating distinct object stores for text and image searches. The caching behavior is governed by two configurable policies defined as constants at the top of [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts).

**TTL (time-to-live)** dictates how long a cached result remains valid. By default, this is set to 15 minutes (`15 * 60 * 1000` ms) at line 11.

**Maximum entries** establishes a hard ceiling on the number of items each store may retain. The default limit of 100 entries is defined at line 13.

These constraints ensure that the cache delivers fast results for recent queries without consuming excessive disk space.

## Inserting Entries and Pruning Strategy

When a search completes, the `executeCachedSearch` function delegates result storage to `cacheResult`. This method writes a record containing the query hash, result payload, and current timestamp:

```typescript
await store.put({ key, results, timestamp: Date.now() });

```

To avoid expensive cleanup operations on every write, MiniSearch employs a batched pruning strategy. The module maintains an internal `_cacheWriteCount` counter, triggering a size-based cleanup every **PRUNE_INTERVAL** writes (defaulting to 10):

```typescript
if (this._cacheWriteCount % CACHE_CONFIG.PRUNE_INTERVAL === 0) {
    this.pruneCache(storeName);
}

```

This logic appears in the `cacheResult` method around line 75 of [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts).

## Enforcing the Maximum Entry Limit

The `pruneCache` function (implemented around line 100) proactively maintains the entry ceiling. It first counts the current rows in the specified store; if the count exceeds `cacheConfig.maxEntries`, it calculates the excess and removes the oldest entries by timestamp:

```typescript
const count = await store.count();
if (count > maxEntries) {
    const excess = count - maxEntries;
    const oldestEntries = await store.orderBy("timestamp")
                                      .limit(excess)
                                      .primaryKeys();
    await store.bulkDelete(oldestEntries);
}

```

This ensures the cache never grows beyond the configured limit, preserving storage for recent, relevant queries.

## TTL-Based Expiration and Validation

Freshness enforcement operates through two mechanisms. First, before any cache lookup, `executeCachedSearch` invokes `cleanExpiredCache` to purge stale entries. This function scans for records older than `Date.now() - cacheConfig.ttl` and bulk deletes them:

```typescript
const expiredItems = await store.where("timestamp")
                                .below(currentTime - timeToLive)
                                .toArray();
await store.bulkDelete(expiredItems.map(item => item.key));

```

Additionally, when reading cached results via `getCachedResult`, the module validates the entry's age:

```typescript
const fresh = Date.now() - cachedItem.timestamp < cacheConfig.ttl;

```

If the entry is fresh, MiniSearch returns the cached results and increments a hit metric. Otherwise, it treats the lookup as a cache miss and executes a fresh search against the remote API.

## Runtime Configuration and Statistics

Both cache policies can be adjusted dynamically without restarting the application. The `searchService.updateCacheConfig` method (around line 85) accepts new TTL and max entry values, validating that inputs are non-negative before applying them:

```typescript
if (newConfig.ttl !== undefined && newConfig.ttl < 0) …
if (newConfig.maxEntries !== undefined && newConfig.maxEntries < 0) …
Object.assign(cacheConfig, newConfig);

```

Changes take effect immediately, influencing subsequent cleanup cycles and pruning operations. Developers can also inspect cache performance through `getCacheStats()`, which exposes hit rates and current configuration settings.

## Practical Implementation Examples

### Automatic Cache Utilization

Search functions automatically leverage the cache when available:

```typescript
import { searchText, searchImages } from "./modules/search";

// First call misses cache and queries the remote API
const textResults = await searchText("open source AI");

// Subsequent call within 15 minutes hits the cache instantly
const cachedText = await searchText("open source AI");

```

### Adjusting Cache Policies

Reduce TTL and increase capacity for high-traffic scenarios:

```typescript
import { searchServiceInstance } from "./modules/search";

// Set TTL to 5 minutes and allow 200 entries per store
searchServiceInstance.updateCacheConfig({ 
  ttl: 5 * 60 * 1000, 
  maxEntries: 200 
});

```

### Cache Maintenance Operations

Clear the cache entirely or inspect statistics:

```typescript
// Clear all cached entries
await searchServiceInstance.clearSearchCache();

// View hit rates and configuration
const stats = searchServiceInstance.getCacheStats();
console.log({
  textHitRate: stats.textHitRate,
  imageHitRate: stats.imageHitRate,
  config: stats.config,
});

```

## Summary

- **Storage Backend**: MiniSearch uses IndexedDB via Dexie, with separate stores for text and image search history.
- **Default Limits**: Entries expire after 15 minutes (TTL) and stores cap at 100 items (max entries), defined in [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts).
- **Write Behavior**: Every 10 writes (`PRUNE_INTERVAL`), the system prunes excess entries by age, keeping only the newest results.
- **Read Validation**: Lookups automatically purge expired items and validate freshness before returning cached data.
- **Dynamic Control**: Applications can adjust TTL and entry limits at runtime via `updateCacheConfig`, with changes applying immediately to subsequent operations.

## Frequently Asked Questions

### How does MiniSearch handle cache storage limits in the browser?

MiniSearch enforces a configurable maximum entry limit (default 100) per IndexedDB store. When inserts exceed this threshold, the `pruneCache` function automatically deletes the oldest entries by timestamp, ensuring the cache never grows beyond the specified bound regardless of query volume.

### Can I change the cache expiration time after initialization?

Yes. The `searchService.updateCacheConfig` method allows runtime modification of the TTL value. Changes apply immediately to subsequent cache reads and cleanup cycles, though existing entries are only evaluated against the new TTL during their next access or during the periodic expiration sweep.

### What happens when the cache reaches the maximum number of entries?

Once a store contains the maximum configured entries (100 by default), MiniSearch removes the oldest records to make room for new results. This occurs every 10 write operations (`PRUNE_INTERVAL`), preventing performance degradation from excessive pruning while maintaining strict storage boundaries.

### How can I verify if my searches are hitting the cache?

Call `searchService.getCacheStats()` to retrieve current metrics, including `textHitRate` and `imageHitRate`. These values indicate the percentage of recent queries served from IndexedDB versus remote API calls, helping you tune the TTL and entry limits for your specific usage patterns.