> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/kstij/Envark/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching

> Learn how Envark's intelligent caching system speeds up scans and improves performance

Envark includes a sophisticated caching system that dramatically improves scan performance on repeated runs. This page explains how the cache works, when it's used, and how to manage it.

## Overview

Envark uses a two-tier caching strategy:

1. **Disk Cache** - Persistent cache stored in `.envark/cache.json`
2. **Memory Cache** - Session-based in-memory cache for the current process

<Info>
  On a typical project with 500+ files, caching can reduce scan time from 2-3 seconds to under 100ms.
</Info>

***

## Disk Cache

### Location

The disk cache is stored in your project directory:

```bash theme={null}
my-project/
├── .envark/
│   └── cache.json      # Scan results cache
├── .env
├── src/
└── package.json
```

<Warning>
  Add `.envark/` to your `.gitignore` to avoid committing cache files.
</Warning>

### Cache Structure

The cache file contains:

```typescript theme={null}
interface CacheEntry<T> {
    hash: string;           // Content hash of all scanned files
    timestamp: number;      // When the cache was created (Unix ms)
    expiresAt: number;      // When the cache expires (Unix ms)
    data: T;                // Cached scan results
}
```

**Example `cache.json`:**

```json theme={null}
{
  "hash": "a3f8c9d7e2b1",
  "timestamp": 1709654400000,
  "expiresAt": 1709654700000,
  "data": {
    "usages": [
      {
        "variableName": "DATABASE_URL",
        "filePath": "/path/to/src/db.ts",
        "lineNumber": 12,
        "language": "typescript",
        "usageType": "usage"
      }
    ],
    "scannedFiles": 342,
    "sourceFiles": 287,
    "envFiles": 3
  }
}
```

***

## How Cache Validation Works

<Steps>
  <Step title="Scan Initiated">
    When you run `envark scan` or `envark analyze`, Envark starts the cache validation process.
  </Step>

  <Step title="File Discovery">
    Envark walks the project directory to discover all relevant files (source code and `.env` files).

    ```typescript theme={null}
    const allFiles = walkDirectory(projectPath, { maxFiles, maxDepth });
    ```
  </Step>

  <Step title="Hash Computation">
    A hash is computed from all discovered files' metadata:

    ```typescript theme={null}
    function computeFilesHash(files: FileInfo[]): string {
        // Hash combines:
        // - File paths
        // - File sizes
        // - Modification times
        // - File count
        
        const data = files
            .map(f => `${f.relativePath}:${f.size}:${f.modifiedTime}`)
            .sort()
            .join('|');
        
        return createHash('md5').update(data).digest('hex').slice(0, 12);
    }
    ```
  </Step>

  <Step title="Cache Lookup">
    Envark attempts to read the cache file:

    ```typescript theme={null}
    const cached = readCache(projectPath, hash);
    if (cached.hit && cached.data) {
        // Use cached results
        return cached.data;
    }
    ```
  </Step>

  <Step title="Validation Checks">
    The cache is considered valid only if:

    ✅ Cache file exists at `.envark/cache.json`\
    ✅ Hash matches current file state\
    ✅ Cache has not expired (\< 5 minutes old)

    ```typescript theme={null}
    // Check hash match
    if (entry.hash !== hash) {
        return { hit: false, data: null };
    }

    // Check expiry
    if (Date.now() > entry.expiresAt) {
        return { hit: false, data: null };
    }

    return { hit: true, data: entry.data };
    ```
  </Step>

  <Step title="Cache Hit or Miss">
    **Cache Hit:** Returns cached scan results immediately\
    **Cache Miss:** Performs full scan and writes new cache
  </Step>
</Steps>

***

## Cache Expiration

<ParamField query="CACHE_EXPIRY_MS" type="number" default="300000">
  Cache automatically expires after 5 minutes (300,000 milliseconds).
</ParamField>

```typescript theme={null}
const CACHE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes

const entry: CacheEntry<T> = {
    hash,
    timestamp: Date.now(),
    expiresAt: Date.now() + CACHE_EXPIRY_MS,
    data
};
```

### Why 5 Minutes?

The 5-minute expiry balances:

* **Performance** - Most development workflows benefit from caching during active editing
* **Freshness** - Prevents stale results from being used too long
* **CI/CD** - In CI, the cache is rarely hit (different environments), so expiry doesn't matter

***

## Cache Invalidation

The cache is automatically invalidated when:

<AccordionGroup>
  <Accordion title="File Changes Detected">
    Any modification to tracked files invalidates the cache:

    ```bash theme={null}
    # Before
    $ envark scan
    ✓ Scanned 342 files in 1.8s (cache miss)

    # Edit a file
    $ echo "const x = process.env.NEW_VAR" >> src/app.ts

    # After - hash changes, cache invalidated
    $ envark scan  
    ✓ Scanned 342 files in 1.9s (cache miss)
    ```

    The hash includes:

    * File path
    * File size
    * Modification timestamp

    So even trivial changes invalidate the cache.
  </Accordion>

  <Accordion title="Files Added or Removed">
    Adding or removing files changes the hash:

    ```bash theme={null}
    # Add a new file
    $ touch src/new-feature.ts

    # Hash changes because file count changed
    $ envark scan
    ✓ Scanned 343 files in 1.9s (cache miss)
    ```
  </Accordion>

  <Accordion title="Cache Expires (5 Minutes)">
    ```bash theme={null}
    $ envark scan
    ✓ Scanned 342 files in 1.8s (cache miss)

    # Immediately run again - cache hit
    $ envark scan
    ✓ Scanned 342 files in 0.08s (cache hit, 2s old)

    # Wait 6 minutes...
    $ envark scan
    ✓ Scanned 342 files in 1.9s (cache miss - expired)
    ```
  </Accordion>

  <Accordion title="Manual Invalidation">
    You can manually clear the cache:

    ```bash theme={null}
    # Delete cache file
    $ rm .envark/cache.json

    # Or use the CLI command (if available)
    $ envark cache clear
    ```
  </Accordion>
</AccordionGroup>

***

## Memory Cache

For the current process, Envark also maintains an in-memory cache:

```typescript theme={null}
class MemoryCache {
    private cache: Map<string, { data: unknown; timestamp: number }>;
    private ttlMs: number = 30000; // 30 seconds
    
    get<T>(key: string): T | null {
        const entry = this.cache.get(key);
        if (!entry) return null;
        
        if (Date.now() - entry.timestamp > this.ttlMs) {
            this.cache.delete(key);
            return null;
        }
        
        return entry.data as T;
    }
    
    set<T>(key: string, data: T): void {
        this.cache.set(key, { data, timestamp: Date.now() });
    }
}

// Singleton instance
export const sessionCache = new MemoryCache();
```

### Use Cases

* **API Server Mode** - If Envark is used as a long-running service
* **Watch Mode** - For potential future watch/daemon features
* **Testing** - Speeds up test suites that scan the same project repeatedly

<Note>
  The memory cache has a shorter TTL (30 seconds) than the disk cache (5 minutes).
</Note>

***

## Controlling Cache Behavior

### Disable Cache

You can disable caching with the `--no-cache` flag:

```bash theme={null}
# Force fresh scan, ignore cache
$ envark scan --no-cache
```

Programmatically:

```typescript theme={null}
import { scanProject } from 'envark';

const result = scanProject('/path/to/project', {
    useCache: false  // Disable cache
});
```

### Cache Location

The cache is always stored in the project directory:

```typescript theme={null}
const CACHE_DIR = '.envark';
const CACHE_FILE = 'cache.json';

function getCachePath(projectPath: string): string {
    return join(projectPath, CACHE_DIR, CACHE_FILE);
}
```

<Info>
  There is no global cache directory. Each project has its own isolated cache.
</Info>

***

## Cache Statistics

You can inspect cache status:

```typescript theme={null}
import { getCacheStats, formatCacheAge } from 'envark';

const stats = getCacheStats('/path/to/project');

console.log(stats);
// {
//   exists: true,
//   size: 45673,           // bytes
//   age: 120000,           // milliseconds
//   expired: false
// }

if (stats.age) {
    console.log(formatCacheAge(stats.age));
    // "2m ago"
}
```

### Age Formatting

```typescript theme={null}
formatCacheAge(500)        // "just now"
formatCacheAge(5000)       // "5s ago"
formatCacheAge(120000)     // "2m ago"
formatCacheAge(3700000)    // "1h ago"
```

***

## Performance Impact

### Benchmark: Medium Project (500 files)

| Scenario                | Duration | Cache Status        |
| ----------------------- | -------- | ------------------- |
| First scan              | 2,340ms  | miss                |
| Second scan (immediate) | 82ms     | hit (0s old)        |
| After file edit         | 2,410ms  | miss (hash changed) |
| After 6 minutes         | 2,380ms  | miss (expired)      |

### Speedup

<Check>
  Cache hit = **28x faster** than full scan
</Check>

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Add to .gitignore" icon="ban">
    ```bash theme={null}
    # .gitignore
    .envark/
    ```

    Never commit cache files to version control.
  </Card>

  <Card title="CI/CD: Disable Cache" icon="rotate">
    ```yaml theme={null}
    # .github/workflows/envark.yml
    - run: envark analyze --no-cache
    ```

    In CI, always use fresh scans for accurate results.
  </Card>

  <Card title="Development: Use Cache" icon="gauge-high">
    Default behavior is perfect for development:

    ```bash theme={null}
    $ envark scan  # Fast repeat scans
    ```
  </Card>

  <Card title="Clear Stale Cache" icon="trash">
    If you suspect cache issues:

    ```bash theme={null}
    $ rm -rf .envark/
    $ envark scan
    ```
  </Card>
</CardGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Cache Not Being Used">
    **Symptoms:** Every scan shows "cache miss"

    **Possible Causes:**

    1. Files are being modified between scans
    2. System clock is incorrect (affects timestamp comparison)
    3. Cache directory is not writable
    4. Using `--no-cache` flag

    **Debug:**

    ```bash theme={null}
    # Check if cache exists
    $ ls -la .envark/cache.json

    # Check cache content
    $ cat .envark/cache.json | jq '.timestamp, .expiresAt'

    # Check current time
    $ date +%s000  # Unix timestamp in milliseconds
    ```
  </Accordion>

  <Accordion title="Stale Results Returned">
    **Symptoms:** Scan results don't reflect recent code changes

    **Possible Causes:**

    1. File modification time not updated (e.g., using `git checkout`)
    2. Clock skew

    **Solution:**

    ```bash theme={null}
    # Force cache clear
    $ rm -rf .envark/
    $ envark scan --no-cache
    ```
  </Accordion>

  <Accordion title="Permission Errors">
    **Symptoms:** `EACCES` errors when writing cache

    **Solution:**

    ```bash theme={null}
    # Make directory writable
    $ chmod 755 .envark/

    # Or disable caching
    $ envark scan --no-cache
    ```

    Caching is optional - Envark will continue working if cache writes fail.
  </Accordion>
</AccordionGroup>

***

## Implementation Details

### Cache Write Process

```typescript theme={null}
export function writeCache<T>(projectPath: string, hash: string, data: T): boolean {
    try {
        // Ensure .envark/ directory exists
        ensureCacheDir(projectPath);
        
        const cachePath = getCachePath(projectPath);
        
        const entry: CacheEntry<T> = {
            hash,
            timestamp: Date.now(),
            expiresAt: Date.now() + CACHE_EXPIRY_MS,
            data
        };
        
        // Write atomically with pretty formatting
        writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8');
        return true;
    } catch {
        // Cache writes are best-effort, don't throw
        return false;
    }
}
```

### Cache Read Process

```typescript theme={null}
export function readCache<T>(projectPath: string, hash: string): CacheResult<T> {
    const cachePath = getCachePath(projectPath);
    
    if (!existsSync(cachePath)) {
        return { hit: false, data: null };
    }
    
    try {
        const content = readFileSync(cachePath, 'utf-8');
        const entry: CacheEntry<T> = JSON.parse(content);
        
        // Validate hash
        if (entry.hash !== hash) {
            return { hit: false, data: null };
        }
        
        // Validate expiry
        if (Date.now() > entry.expiresAt) {
            return { hit: false, data: null };
        }
        
        const age = Date.now() - entry.timestamp;
        return { hit: true, data: entry.data, age };
    } catch {
        return { hit: false, data: null };
    }
}
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Performance" icon="bolt" href="/guides/caching">
    Learn more optimization techniques for large codebases
  </Card>

  <Card title="CI/CD Integration" icon="code-branch" href="/guides/best-practices#cicd-integration">
    Set up Envark in your CI/CD pipeline
  </Card>
</CardGroup>
