npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

sqlite-cache

v0.0.2

Published

A simple SQLite cache for Node.js applications

Readme

SQLite Cache

Discord GitHub Action NPM Version NPM Downloads NPM Unpacked Size NPM License

A high-performance, type-safe SQLite-based cache for Node.js applications with built-in TTL (Time To Live), size limits, and schema validation.

Features

  • 🚀 High Performance: Built on Node.js native SQLite driver
  • 🔒 Type Safe: Full TypeScript support with generic types
  • TTL Support: Automatic expiration with configurable time-to-live
  • 📏 Size Limits: LRU-style eviction when max entries exceeded
  • 🔍 Schema Validation: JSON Schema validation using TypeBox
  • 🪵 Built-in Logging: Configurable logging with colorful output
  • 🔄 Iterable: Full Map-like interface with iteration support
  • 💾 Persistent: Data survives application restarts
  • 🧹 Auto Cleanup: Automatic cleanup of expired entries

Requirements

  • Node.js >= 22.5.0 (required for native SQLite driver)

Installation

npm install sqlite-cache

Quick Start

import { SQLiteCache } from "sqlite-cache"
import { Type } from "@sinclair/typebox"

// Define your data schema
const UserSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.String(),
    active: Type.Boolean()
})

type User = {
    id: number
    name: string
    email: string
    active: boolean
}

// Create cache instance
const cache = new SQLiteCache<string, User>({
    path: "./cache.db",
    schema: UserSchema,
    ttl: 60_000, // 1 minute
    max: 1000, // Maximum 1000 entries
    log: true // Enable logging
})

// Use the cache
const user: User = {
    id: 1,
    name: "John Doe",
    email: "[email protected]",
    active: true
}

cache.set("user:1", user)
const retrieved = cache.get("user:1")
console.log(retrieved) // { id: 1, name: 'John Doe', ... }

// Clean up
cache.close()

API Reference

Constructor

new SQLiteCache<Key extends string, Value extends object>(options: CacheOptions)

Options

| Option | Type | Required | Default | Description | | -------- | ----------------------- | -------- | ------- | ---------------------------------- | | path | string | ✅ | - | Path to SQLite database file | | schema | TSchema | ✅ | - | TypeBox JSON schema for validation | | ttl | number | ❌ | 60000 | Time to live in milliseconds | | max | number | ❌ | 100 | Maximum number of entries | | log | boolean \| LogOptions | ❌ | false | Logging configuration |

Log Options

interface LogOptions {
    prefix?: string // Log prefix (default: '[SQLiteCache]')
    timestamp?: boolean // Include timestamps (default: true)
}

Methods

set(key: Key, value: Value): this

Store a value in the cache with the given key.

cache.set("user:123", { id: 123, name: "Alice", email: "[email protected]", active: true })

get(key: Key): Value | null

Retrieve a value from the cache. Returns null if not found or expired.

const user = cache.get("user:123")
if (user) {
    console.log(user.name)
}

has(key: Key): boolean

Check if a key exists and is not expired.

if (cache.has("user:123")) {
    console.log("User exists in cache")
}

delete(key: Key): boolean

Remove a specific key from the cache. Returns true if the key existed.

const wasDeleted = cache.delete("user:123")

clear(): void

Remove all entries from the cache.

cache.clear()

close(): void

Close the database connection. Call this when your application shuts down.

cache.close()

Properties

size: number

Get the current number of valid (non-expired) entries in the cache.

console.log(`Cache contains ${cache.size} entries`)

Iteration

The cache implements the iterable protocol and provides Map-like iteration methods:

keys(): IterableIterator<Key>

Iterate over all valid keys.

for (const key of cache.keys()) {
    console.log(key)
}

values(): IterableIterator<Value>

Iterate over all valid values.

for (const user of cache.values()) {
    console.log(user.name)
}

entries(): IterableIterator<[Key, Value]>

Iterate over all valid key-value pairs.

for (const [key, user] of cache.entries()) {
    console.log(`${key}: ${user.name}`)
}

forEach(callback: (value: Value, key: Key, cache: this) => void): void

Execute a callback for each valid entry.

cache.forEach((user, key) => {
    console.log(`Processing ${user.name} with key ${key}`)
})

[Symbol.iterator](): IterableIterator<[Key, Value]>

Direct iteration support.

for (const [key, user] of cache) {
    console.log(`${key}: ${user.name}`)
}

Advanced Usage

Custom Schema with Optional Fields

import { Type } from "@sinclair/typebox"

const ProductSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    price: Type.Number(),
    description: Type.Optional(Type.String()),
    metadata: Type.Optional(
        Type.Object({
            category: Type.String(),
            tags: Type.Array(Type.String()),
            inStock: Type.Boolean()
        })
    )
})

type Product = {
    id: number
    name: string
    price: number
    description?: string
    metadata?: {
        category: string
        tags: string[]
        inStock: boolean
    }
}

const productCache = new SQLiteCache<string, Product>({
    path: "./products.db",
    schema: ProductSchema,
    ttl: 300_000, // 5 minutes
    max: 5000,
    log: {
        prefix: "[ProductCache]",
        timestamp: true
    }
})

Error Handling

try {
    const cache = new SQLiteCache({
        path: "/invalid/path/cache.db",
        schema: UserSchema
    })
} catch (error) {
    console.error("Failed to initialize cache:", error.message)
}

Memory Database

For testing or temporary caching, you can use an in-memory database:

const tempCache = new SQLiteCache<string, User>({
    path: ":memory:",
    schema: UserSchema,
    ttl: 30_000
})

Batch Operations

// Batch insert
const users = [
    { id: 1, name: "Alice", email: "[email protected]", active: true },
    { id: 2, name: "Bob", email: "[email protected]", active: false },
    { id: 3, name: "Charlie", email: "[email protected]", active: true }
]

users.forEach((user, index) => {
    cache.set(`user:${user.id}`, user)
})

// Batch retrieve
const cachedUsers = users.map((user) => cache.get(`user:${user.id}`)).filter((user) => user !== null)

console.log(`Retrieved ${cachedUsers.length} users from cache`)

Performance Considerations

  • Database Location: Use SSD storage for better performance
  • TTL Strategy: Shorter TTL reduces memory usage but increases cache misses
  • Max Entries: Higher limits improve hit rates but use more storage
  • Schema Complexity: Simpler schemas serialize/deserialize faster
  • Key Design: Use consistent, predictable key patterns for better organization

Best Practices

1. Proper Cleanup

Always close the cache when your application shuts down:

process.on("SIGINT", () => {
    cache.close()
    process.exit(0)
})

2. Key Naming Conventions

Use consistent key patterns:

// Good
cache.set("user:123", user)
cache.set("product:456", product)
cache.set("session:abc123", session)

// Avoid
cache.set("123", user)
cache.set("prod456", product)
cache.set("sessionabc123", session)

3. Schema Design

Design schemas that match your data structure:

// Prefer this
const UserSchema = Type.Object({
    id: Type.Number(),
    profile: Type.Object({
        name: Type.String(),
        email: Type.String()
    })
})

// Over this (less structured)
const UserSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.String()
    // ... many flat properties
})

4. Error Handling

Handle potential errors gracefully:

const user = cache.get("user:123")
if (!user) {
    // Cache miss - fetch from primary data source
    const user = await fetchUserFromDB(123)
    cache.set("user:123", user)
    return user
}
return user

Troubleshooting

Node.js Version Error

Error: "Node version must be 22.5.0 or higher"

Solution: Upgrade to Node.js 22.5.0 or later, which includes the native SQLite driver.

Database Connection Errors

Error: Failed to connect to database

Solutions:

  • Ensure the directory exists for file-based databases
  • Check file permissions
  • Verify disk space availability
  • Use :memory: for testing

Schema Validation Errors

Error: Data does not match schema

Solutions:

  • Verify your TypeBox schema matches your data structure
  • Check for required vs optional fields
  • Ensure type compatibility (string vs number, etc.)

Performance Issues

  • Use appropriate TTL values
  • Monitor cache hit/miss ratios
  • Consider database file location (SSD vs HDD)
  • Optimize key naming patterns

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/new-feature
  3. Make your changes and add tests
  4. Run tests: npm test
  5. Commit your changes: git commit -am 'Add new feature'
  6. Push to the branch: git push origin feature/new-feature
  7. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Changelog

v0.0.2

  • Initial release with basic caching functionality
  • TTL support with automatic cleanup
  • Size limits with LRU-style eviction
  • TypeBox schema validation
  • Full TypeScript support
  • Configurable logging
  • Map-like iteration interface

Made with ❤️ by xcfio