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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@livequery/indexeddb

v1.0.0

Published

IndexedDB storage adapter for @livequery/client

Readme

@livequery/indexeddb

Persistent IndexedDB storage adapter for @livequery/client.

Replaces the built-in LivequeryMemoryStorage with a real browser database, so data survives page reloads and tab switches — with zero dependencies beyond @livequery/client itself. Works natively in any modern browser without WebAssembly or native modules.


Why this package?

| Feature | LivequeryMemoryStorage | @livequery/indexeddb | @livequery/sqlite | |---|---|---|---| | Data survives reload | ❌ | ✅ | ✅ | | cache-first mode useful | ❌ | ✅ | ✅ | | Offline reads | ❌ | ✅ | ✅ | | Zero extra dependencies | ✅ | ✅ | ❌ (needs WASM) | | Works in browser | ✅ | ✅ | ✅ (dedicated worker) | | Works in React Native | ✅ | ❌ | ✅ (via native driver) |

Use @livequery/indexeddb when you need persistence in the browser with no build complexity. Use @livequery/sqlite when you also need React Native support or need the structured query power of SQL.


Installation

# npm
npm install @livequery/indexeddb @livequery/client

# bun
bun add @livequery/indexeddb @livequery/client

How it works

Each collection gets its own IndexedDB object store, created on first access. Documents are stored directly via the browser's structured clone algorithm — no JSON serialisation needed.

Storage architecture

IndexedDB database: "livequery"  (configurable)
├── Object store: "users"          ← one store per collection
├── Object store: "posts"
└── Object store: "settings"

Each record = the document itself:
{ id: "abc123", name: "Ba", _adding: true, ... }

Dynamic store creation

IndexedDB only allows creating object stores inside an onupgradeneeded handler, which requires a database version bump. This package handles it automatically: when a new collection is accessed for the first time, all pending operations are paused, the database is closed, reopened at version + 1 with the new store created, then operations resume.

This is safe because the livequery client is designed to run inside a Shared Worker — only one database connection exists at any time, so no race conditions between tabs.

Promise queue

All operations are serialised through an internal #pending promise chain (a standard mutex/queue pattern). This guarantees:

  • No read or write runs while a store upgrade is in progress
  • Multiple collections first accessed simultaneously are created sequentially, each waiting for the previous upgrade to finish

Usage

import { LivequeryIndexedDBStorage } from '@livequery/indexeddb'
import { LivequeryClient } from '@livequery/client'

const client = new LivequeryClient({
    storage: new LivequeryIndexedDBStorage(),
    transporters: { /* ... */ }
})

Custom database name

Useful when running multiple independent LivequeryClient instances in the same origin:

new LivequeryIndexedDBStorage({ dbName: 'myapp-v2' })

Full example — React

// client.ts
import { LivequeryIndexedDBStorage } from '@livequery/indexeddb'
import { LivequeryClient } from '@livequery/client'
import { myTransporter } from './transporter'

export const client = new LivequeryClient({
    storage: new LivequeryIndexedDBStorage({ dbName: 'myapp' }),
    transporters: { api: myTransporter }
})
// App.tsx
import { LivequeryClientProvider } from '@livequery/react'
import { client } from './client'

export function App() {
    return (
        <LivequeryClientProvider client={client}>
            <TodoList />
        </LivequeryClientProvider>
    )
}
// TodoList.tsx
import { useCollection } from '@livequery/react'

type Todo = { id: string; title: string; done: boolean; createdAt: number }

export function TodoList() {
    const collection = useCollection<Todo>('todos', { mode: 'cache-first' })

    return (
        <ul>
            {collection.items.map(doc => (
                <li key={doc.value.id}>
                    <input
                        type="checkbox"
                        checked={doc.value.done}
                        onChange={() => doc.update({ done: !doc.value.done })}
                    />
                    {doc.value.title}
                    <button onClick={() => doc.del()}>Delete</button>
                </li>
            ))}
        </ul>
    )
}

API Reference

LivequeryIndexedDBStorage

new LivequeryIndexedDBStorage(options?: LivequeryIndexedDBStorageOptions)

Implements the full LivequeryStorge interface from @livequery/client.

| Method | Description | |---|---| | query(collection, filters?) | Returns filtered + sorted documents and a paging object | | get(ref, id) | Returns one document by id, or null | | add(collection, document) | Inserts a document; generates a local:<uuidv7> id if none provided | | update(collection, id, patch) | Merges patch fields into the existing document; handles id changes atomically | | delete(collection, id) | Deletes and returns the removed document, or null | | flush() | Clears all documents from the store (all collections) |

LivequeryIndexedDBStorageOptions

| Option | Type | Default | Description | |---|---|---|---| | dbName | string | "livequery" | IndexedDB database name |


Supported filter operators

All filter operators from @livequery/client are supported. Filtering and sorting are applied in-memory using the same filterDocs helper used by LivequeryMemoryStorage:

| Operator | Example | Description | |---|---|---| | eq (default) | { status: 'active' } | Strict equality | | eq-number | { age:eq-number: 30 } | Numeric equality | | gt / gte | { score:gt: 100 } | Greater than / or equal | | lt / lte | { price:lt: 50 } | Less than / or equal | | in | { role:in: ['admin', 'mod'] } | Value in array | | nin | { role:nin: ['banned'] } | Value not in array | | include | { tags:include: 'news' } | Array field contains value | | like | { name:like: 'van' } | Case-insensitive substring match | | boolean | { active:boolean: 'true' } | Boolean match | | null | { deletedAt:null: 'null-only' } | Null / not-null check | | sort | { createdAt:sort: 'desc' } | Sort direction |

Paging filters (:limit, :page, :before, :after, :around) are passed through but not yet applied at the storage level.


Browser requirements

IndexedDB is supported in all modern browsers (Chrome 24+, Firefox 16+, Safari 10+, Edge 12+). No WebAssembly, no Workers, no additional headers required.


License

MIT