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

@neabyte/indexed-orm

v0.1.0

Published

Async IndexedDB ORM, lightweight, and ready for modern runtimes

Readme

Indexed-ORM Module type: Deno/ESM npm version JSR CI License

Async IndexedDB ORM, lightweight, and ready for modern runtimes.

Installation

Deno

deno add jsr:@neabyte/indexed-orm

npm

npm install @neabyte/indexed-orm

CDN (browser / any ESM)

<script type="module">
  import { Database } from 'https://esm.sh/jsr/@neabyte/indexed-orm'
  // or pin version: .../[email protected]
</script>
  • Latest: https://esm.sh/jsr/@neabyte/indexed-orm
  • Pinned: https://esm.sh/jsr/@neabyte/indexed-orm@<version>

Package exports

You can import the following from @neabyte/indexed-orm:

  • Database – open DB and run schema upgrade.
  • Range – build IDBKeyRange from options (e.g. for custom cursor logic).
  • Store – advanced: create a store handle from an existing IDBDatabase via Store.createStore(db, storeName, keyPath, options?).
  • TypesFindOptions, StoreHandle, OpenOptions, Builder, Chain, KeyRangeOptions, IDBValidKey, TransactionDurability, and others.
import { Database, Range, Store } from '@neabyte/indexed-orm'
import type { FindOptions, StoreHandle, OpenOptions } from '@neabyte/indexed-orm'

Usage

Quick start

import { Database } from '@neabyte/indexed-orm'

const db = await Database.open('MyApp', 1, builder => {
  builder.store('users', 'id').index('email')
})
const users = db.store('users')
await users.put({ id: '1', email: '[email protected]', name: 'Alice' })
const user = await users.get('1')

Database

Open a database by name and version. The upgrade callback runs on first open and on version bumps. The second argument to store() is the key path (e.g. 'id') — the property on your objects used as the primary key.

Version and data: Bumping the version (e.g. 1 → 2) only reruns the upgrade callback so you can create new stores or indexes. Existing data is kept unless you explicitly delete object stores or clear them inside upgrade. Data is lost only if you drop a store or the user clears site data.

import { Database } from '@neabyte/indexed-orm'

const db = await Database.open('MyApp', 1, builder => {
  builder.store('users', 'id').index('email').index('name')
  builder.store('posts', 'id').index('userId').index('createdAt')
})

// Optional: openOptions (e.g. relaxed durability for better write perf where allowed)
const dbRelaxed = await Database.open(
  'MyApp',
  1,
  builder => {
    builder.store('users', 'id')
  },
  { durability: 'relaxed' }
)

Get a store handle (db is a DBHandle):

const users = db.store('users')

StoreHandle (CRUD)

// add(item, key?): with keyPath store, the key is taken from item[keyPath]; pass key only to override or when the item has no keyPath property
await users.add({ id: '1', email: '[email protected]', name: 'Alice' })
await users.put({ id: '1', email: '[email protected]', name: 'Alice Updated' })

// Read, update, delete
const user = await users.get('1')
await users.update('1', { name: 'Alice Smith' })
await users.delete('1')

// Bulk and clear
await users.putMany([
  { id: '1', email: '[email protected]' },
  { id: '2', email: '[email protected]' }
])
await users.deleteMany(['1', '2'])
await users.clear()

StoreHandle (FindOptions)

Basic find with index, prefix, ordering, limit, and offset:

const out = await users.find({
  index: 'email',
  prefix: 'a',
  order: 'asc',
  limit: 10,
  offset: 0
})

Range queries (eq, min, max, includeMin, includeMax). Use eq with no index to match by primary key; use index to match by that index:

await users.find({ eq: '1' })
await users.find({ index: 'email', eq: '[email protected]' })
await users.find({ index: 'createdAt', min: 1000, max: 2000, includeMin: true, includeMax: false })

Multiple prefix match (prefixes — match if key starts with any of the given strings):

await users.find({ index: 'email', prefixes: ['alice', 'admin'] })

Additional filters:

  • whereClient-side only. The predicate runs in JS while iterating or filtering results; it is not pushed to IndexedDB indexes. Use it for flexible conditions (e.g. item.role === 'admin'), but on large datasets index-based options (eq, prefix, min/max) are faster because they use the engine’s range scan.
  • ignoreCase — Applies only to eq, prefix, and prefixes (case-insensitive string comparison). It does not affect min/max or other range options. Comparison is done in JS after the range is applied.
const out = await users.find({
  anyOf: ['id1', 'id2'],
  noneOf: ['id3'],
  notEq: 'id4',
  where: item => item.role === 'admin',
  distinct: true,
  ignoreCase: true
})

For custom key ranges (e.g. multiple intervals), use the ranges option with pre-built IDBKeyRange objects — build them with Range.buildKeyRange(opts).

StoreHandle (Count, First, Last, Keys)

await users.count({ prefix: 'a' })
await users.first({ order: 'asc' })
await users.last({ order: 'desc' })
await users.keys({ limit: 100 })
await users.primaryKeys({ limit: 100 })
await users.uniqueKeys('email', {})

StoreHandle (each, eachKey, eachUniqueKey)

Return false from the callback to stop iteration:

await users.each(item => {
  if (item.id === 'stop') {
    return false
  }
})

await users.eachKey(key => {
  console.log(key)
})

await users.eachUniqueKey('email', key => {
  console.log(key)
})

StoreHandle (modify)

modify updates records by cursor. Return a partial object to merge into each record, or false to stop iteration. Each modify call runs inside a single readwrite transaction (atomic). Two overlapping modify calls are separate transactions; IndexedDB does not lock across them, so if two modify calls touch the same records at the same time, the last write wins. For predictable results, avoid concurrent modify on the same store or sequence writes in your app.

await users.modify(item => ({ ...item, role: 'user' }), { where: item => item.role === 'guest' })

Transaction scope

Each store method uses its own transaction (e.g. one putMany = one readwrite transaction; one find = one readonly transaction). The ORM does not expose cross-store or custom multi-operation transactions. If you need a single atomic transaction across multiple stores (e.g. deduct from accounts and append to ledger), use raw IndexedDB: open the database, start a transaction with multiple store names, and perform the operations on transaction.objectStore(...).

Advanced: Store.createStore

If you already have an IDBDatabase (e.g. from another library or custom indexedDB.open()), you can create a store handle without Database.open. The database must already have the object store (e.g. created in onupgradeneeded).

import { Store } from '@neabyte/indexed-orm'

const rawDb = await new Promise<IDBDatabase>((resolve, reject) => {
  const r = indexedDB.open('MyApp', 1)
  r.onupgradeneeded = () => {
    r.result.createObjectStore('users', { keyPath: 'id' })
  }
  r.onsuccess = () => resolve(r.result)
  r.onerror = () => reject(r.error)
})
const users = Store.createStore(rawDb, 'users', 'id')
// same StoreHandle API: users.get(), users.find(), etc.

Error handling

All store methods return Promises. Rejections can come from IndexedDB (e.g. quota exceeded, database blocked by user, or transaction aborted). For robust apps, wrap calls in try/catch and handle known cases:

try {
  await users.put(largeItem)
} catch (e) {
  if (e.name === 'QuotaExceededError') {
    // Storage full; prompt user or evict old data
  }
  if (e.name === 'SecurityError' || e.name === 'UnknownError') {
    // e.g. private browsing or user disabled storage
  }
  throw e
}

Errors from Database.open (e.g. version conflict or blocked) also need handling if you support multiple tabs or strict reliability.

Modules Feature List

| Method | Category | Description | | :-------------------------- | :---------- | :------------------------------------------------ | | Database.open | Database | Opens DB and runs upgrade callback. | | DBHandle.store | Database | Returns store handle by name. | | Store.createStore | Store | Returns store handle from existing IDBDatabase. | | StoreHandle.add | StoreHandle | Adds item; optional key when applicable. | | StoreHandle.put | StoreHandle | Puts item (overwrites by key). | | StoreHandle.get | StoreHandle | Gets one item by key. | | StoreHandle.update | StoreHandle | Merges changes into item at key. | | StoreHandle.delete | StoreHandle | Deletes item by key. | | StoreHandle.putMany | StoreHandle | Puts many items in one transaction. | | StoreHandle.deleteMany | StoreHandle | Deletes many keys in one transaction. | | StoreHandle.clear | StoreHandle | Clears all items in store. | | StoreHandle.find | StoreHandle | Finds items matching FindOptions. | | StoreHandle.count | StoreHandle | Counts items matching FindOptions. | | StoreHandle.first | StoreHandle | Returns first item matching options. | | StoreHandle.last | StoreHandle | Returns last item matching options. | | StoreHandle.keys | StoreHandle | Returns primary keys matching options. | | StoreHandle.primaryKeys | StoreHandle | Alias for keys. | | StoreHandle.uniqueKeys | StoreHandle | Returns deduped keys from index. | | StoreHandle.each | StoreHandle | Iterates matching items; stop with false. | | StoreHandle.eachKey | StoreHandle | Iterates matching keys; stop with false. | | StoreHandle.eachUniqueKey | StoreHandle | Iterates index keys; stop with false. | | StoreHandle.modify | StoreHandle | Cursor update; returns updated count. | | Range.buildKeyRange | Range | Builds IDBKeyRange from range options. |

API Reference

Database.open

Database.open(name, version, upgrade?, openOptions?)
  • name <string>: Database name.
  • version <number>: Schema version; bump to rerun upgrade.
  • upgrade <(db: Builder) => void>: (Optional) Defines stores and indexes.
  • openOptions <OpenOptions>: (Optional) { durability?: 'default' | 'relaxed' }. Defaults to {}.
  • Returns: Promise<DBHandle>
  • Description: Opens DB, runs upgrade, and returns handle.

DBHandle.store

db.store(storeName)
  • storeName <string>: Object store name.
  • Returns: StoreHandle<unknown>
  • Description: Returns store handle bound to store name.

Store.createStore

Store.createStore(db, storeName, keyPath, options?)
  • db <IDBDatabase>: Opened IndexedDB database.
  • storeName <string>: Object store name.
  • keyPath <string>: Primary key path (e.g. 'id').
  • options <CreateStoreOptions>: (Optional) { durability?: 'default' | 'relaxed' }.
  • Returns: StoreHandle<T>
  • Description: Creates a store handle when you already have an IDBDatabase (e.g. from custom indexedDB.open() or another library).

StoreHandle.add

store.add(item, key?)
  • item <T>: Item to add.
  • key <IDBValidKey>: (Optional) The key is taken from item[keyPath], so usually omitted. Pass key only to override that key or when the item does not have the keyPath property.
  • Returns: Promise<IDBValidKey>
  • Description: Adds item; fails if a record with the same key already exists (use put to overwrite).

StoreHandle.put

store.put(item)
  • item <T>: Item with keyPath field.
  • Returns: Promise<IDBValidKey>
  • Description: Puts item; overwrites existing item with same key.

StoreHandle.putMany

store.putMany(items)
  • items <T[]>: Array of items to put.
  • Returns: Promise<void>
  • Description: Puts multiple items in one transaction; no-op when not an array.

StoreHandle.get

store.get(key)
  • key <IDBValidKey>: Primary key.
  • Returns: Promise<T | undefined>
  • Description: Returns one item by key, or undefined when missing.

StoreHandle.update

store.update(key, changes)
  • key <IDBValidKey>: Primary key.
  • changes <Partial<T>>: Fields to merge into existing item.
  • Returns: Promise<void>
  • Description: Gets existing item, merges changes, puts back; no-op when key missing.

StoreHandle.delete

store.delete(key)
  • key <IDBValidKey>: Primary key to delete.
  • Returns: Promise<void>
  • Description: Deletes item by key; no-op when key null or undefined.

StoreHandle.deleteMany

store.deleteMany(keys)
  • keys <IDBValidKey[]>: Array of keys to delete.
  • Returns: Promise<void>
  • Description: Deletes multiple keys in one transaction; no-op when not an array.

StoreHandle.clear

store.clear()
  • Returns: Promise<void>
  • Description: Clears all items in the store in one transaction.

StoreHandle.find

store.find(options?)
  • options <FindOptions<T>>: (Optional) Filter and query options. Defaults to {}.
  • Returns: Promise<T[]>
  • Description: Returns items matching options and filters. Options like eq, prefix, min/max use the index/range; where, anyOf, noneOf, distinct, ignoreCase are applied client-side (see Option Types).

StoreHandle.count

store.count(options?)
  • options <FindOptions<T>>: (Optional) Same filters as find.
  • Returns: Promise<number>
  • Description: Counts items matching options; uses range count when possible.

StoreHandle.first

store.first(options?)
  • options <FindOptions<T>>: (Optional) Same filters as find; order applies.
  • Returns: Promise<T | undefined>
  • Description: Returns first item matching options, or undefined.

StoreHandle.last

store.last(options?)
  • options <FindOptions<T>>: (Optional) Same filters as find; order applies.
  • Returns: Promise<T | undefined>
  • Description: Returns last item matching options, or undefined.

StoreHandle.keys

store.keys(options?)
  • options <FindOptions<T>>: (Optional) Same filters as find.
  • Returns: Promise<IDBValidKey[]>
  • Description: Returns primary keys matching options.

StoreHandle.primaryKeys

store.primaryKeys(options?)
  • options <FindOptions<T>>: (Optional) Same as keys.
  • Returns: Promise<IDBValidKey[]>
  • Description: Alias for keys; returns primary keys matching options.

StoreHandle.uniqueKeys

store.uniqueKeys(indexName?, options?)
  • indexName <string>: (Optional) Index name; when omitted, behaves like keys.
  • options <FindOptions<T>>: (Optional) Range and filter options.
  • Returns: Promise<IDBValidKey[]>
  • Description: Returns deduped keys from index; falls back to keys when indexName omitted.

StoreHandle.each

store.each(callback, options?)
  • callback <(item: T) => void | false>: Invoked per item; return false to stop.
  • options <FindOptions<T>>: (Optional) Same filters as find.
  • Returns: Promise<void>
  • Description: Iterates matching items; return false from callback to stop.

StoreHandle.eachKey

store.eachKey(callback, options?)
  • callback <(key: IDBValidKey) => void | false>: Invoked per key; return false to stop.
  • options <FindOptions<T>>: (Optional) Same filters as find.
  • Returns: Promise<void>
  • Description: Iterates matching keys only; return false to stop.

StoreHandle.eachUniqueKey

store.eachUniqueKey(indexName, callback, options?)
  • indexName <string>: Index name (required).
  • callback <(key: IDBValidKey) => void | false>: Invoked per unique key; return false to stop.
  • options <FindOptions<T>>: (Optional) Range options.
  • Returns: Promise<void>
  • Description: Iterates unique keys of index; key cursor; indexName required.

StoreHandle.modify

store.modify(changeCallback, options?)
  • changeCallback <(item: T) => void | Partial<T> | false>: Callback per item; object merges; false stops.
  • options <FindOptions<T>>: (Optional) Select items to modify. Defaults to {}.
  • Returns: Promise<number>
  • Description: Updates records by cursor; returns updated count.

Range.buildKeyRange

Range.buildKeyRange(opts)
  • opts <KeyRangeOptions>: Key range options.
  • Returns: IDBKeyRange | null
  • Description: Builds IDBKeyRange for cursor queries.

Option Types

  • IDBValidKey: key type for get, delete, add( key ), etc. — string | number | Date | ArrayBufferView | ArrayBuffer.
  • OpenOptions (optional): durability ('default' | 'relaxed').
  • CreateStoreOptions (optional): durability ('default' | 'relaxed').
  • Builder (passed to Database.open upgrade): store(storeName, keyPath) → returns Chain; use to define stores and indexes.
  • Chain (from builder.store()): index(indexName) → returns Chain; add index names for the store.
  • KeyRangeOptions (optional): eq, min, max, includeMin, includeMax, prefix.
  • FindOptions (optional): index, order, eq, notEq, min, max, includeMin, includeMax, prefix, prefixes, ignoreCase (for eq/prefix/prefixes only), anyOf, noneOf, where (client-side filter), distinct, ranges, limit, offset.
  • StoreSchemaConfig (internal): { keyPath, indexes } — schema shape per store (from upgrade).

Reference

License

This project is licensed under the MIT license. See the LICENSE file for details.