IndexedDB Ready

USERS

Operation Log
0 operations ⌘K search · ⌘N add · ESC close

ArkIndexDB

A complete, production-grade IndexedDB wrapper library for the browser. Zero dependencies. Pure ES2020. Everything runs locally — no servers, no network, no backend.

100% Browser-Native Zero Dependencies ES2020+ IndexedDB API

Installation

Script Tag (CDN / Local)

Drop a single <script> tag before your app code. No bundler, no npm, no build step required.

HTML
<!-- Local file -->
<script src="https://cdn.jsdelivr.net/npm/ark-indexdb@latest/ark-indexdb.js"></script>

<!-- Or inline if bundling is not available -->
<script>
  // paste ark-indexdb.js content here
</script>

ES Module (if using a bundler)

JavaScript
// If you add export statements to ark-indexdb.js:
import { ArkIndexdb } from './ark-indexdb.js';
Browser Support
IndexedDB is supported in all modern browsers. ArkIndexdb uses ES2020 private class fields (#) — supported in Chrome 74+, Firefox 90+, Safari 14.1+, Edge 79+.

Quick Start

Everything begins with new ArkIndexdb() and a single open() call that sets up your database schema. After that you're ready for full CRUD.

JavaScript
// 1. Create the library instance
const ark = new ArkIndexdb();

// 2. Open (or create) the database — await until ready
await ark.open('MyAppDB', 1, {
  users: {
    keyPath: 'id',
    indexes: [
      { name: 'by_email', keyPath: 'email', unique: true  },
      { name: 'by_role',  keyPath: 'role',  unique: false },
    ]
  }
});

// 3. INSERT — UUID id is generated automatically
const key = await ark.insert('users', {
  name:  'Alice Chen',
  email: 'alice@example.com',
  role:  'admin',
});

// 4. READ
const user = await ark.findById('users', key);

// 5. UPDATE (partial merge)
await ark.update('users', key, { role: 'superadmin' });

// 6. DELETE
await ark.delete('users', key);

Schema & Indexes

Pass your schema as the third argument to open(). Each top-level key becomes an object store. Indexes let you run fast native IDB lookups via findByIndex() and findByRange().

JavaScript
const schema = {
  // Store name → definition
  posts: {
    keyPath:       'id',      // primary key field (default: 'id')
    autoIncrement: false,    // set true for auto numeric keys
    indexes: [
      // { name, keyPath, unique }
      { name: 'by_author',   keyPath: 'authorId', unique: false },
      { name: 'by_category', keyPath: 'category', unique: false },
      { name: 'by_slug',     keyPath: 'slug',     unique: true  },
    ]
  },
  comments: {
    keyPath: 'id',
    indexes: [
      { name: 'by_post', keyPath: 'postId', unique: false },
    ]
  },
  settings: {
    keyPath: 'key',    // any field can be the key
  }
};

await ark.open('BlogDB', 1, schema);
Schema Upgrades
To add new stores or indexes after the DB is created, increment the version number (e.g. open('BlogDB', 2, schema)). The onupgradeneeded handler runs automatically.

Lifecycle

Method Returns Description
ark.open(name, version, schema) Promise<ArkIndexdb> Open or create the database. Always await this before any operation.
ark.close() void Close the active database connection.
ArkIndexdb.dropDatabase(name) Promise<boolean> Permanently delete a database by name (static method).
ArkIndexdb.listDatabases() Promise<Array> List all IndexedDB databases in the current origin (static method).
ark.isOpen boolean Whether the database is currently open.
ark.storeNames string[] Array of object store names.

Create

insert(storeName, data)

Insert a single record. A UUID id is auto-generated if the keyPath is 'id' and none is provided. _createdAt and _updatedAt ISO timestamps are added automatically.

JavaScript
const key = await ark.insert('users', {
  name:  'Bob',
  email: 'bob@example.com',
  role:  'user',
  tags:  ['beta', 'early-adopter'],
});
// returns the new record's primary key (UUID string)

insertMany(storeName, records[])

Insert multiple records in a single IDB transaction — much faster than looping insert() for bulk operations.

JavaScript
const keys = await ark.insertMany('products', [
  { name: 'Widget A', price: 9.99,  category: 'widgets' },
  { name: 'Widget B', price: 14.99, category: 'widgets' },
  { name: 'Gadget X', price: 49.99, category: 'gadgets' },
]);
// returns string[] of inserted keys

Read & Query

findById(storeName, id)

JavaScript
const user = await ark.findById('users', 'abc-123');
// returns the record object, or null if not found

findAll(storeName, options?)

Retrieve all records with optional in-memory filter, sort, limit and offset.

JavaScript
const { data, total } = await ark.findAll('users', {
  filter: { role: 'admin', age: { $gte: 18 } },
  sort:   'name',
  order:  'asc',
  limit:  10,
  offset: 0,
});
// data: matching records array   total: count before pagination

findByIndex(storeName, indexName, value)

Uses a native IDB index for fast O(log n) lookups — ideal for indexed fields.

JavaScript
// Uses the native 'by_role' IDB index
const { data } = await ark.findByIndex('users', 'by_role', 'admin');

// Combine with additional in-memory filtering
const { data } = await ark.findByIndex(
  'users', 'by_role', 'admin',
  { filter: { status: 'active' }, sort: 'name' }
);

findByRange(storeName, indexName, range)

JavaScript
// Products priced between $10 and $50
const { data } = await ark.findByRange(
  'products', 'by_price',
  { lower: 10, upper: 50 }
);

// Open bounds (exclusive): price > 10
const { data } = await ark.findByRange(
  'products', 'by_price',
  { lower: 10, lowerOpen: true }
);

paginate(storeName, page, pageSize, options?)

JavaScript
const result = await ark.paginate('users', 1, 10, {
  filter: { status: 'active' },
  sort: 'name', order: 'asc'
});

result.data        // current page records
result.total       // total matching records
result.totalPages  // total page count
result.hasNext     // boolean
result.hasPrev     // boolean

Update & Patch

update(storeName, id, changes)

Partial merge — only the fields you provide are changed. All other fields are preserved.

JavaScript
await ark.update('users', id, {
  name: 'Alice Smith',    // update these fields
  city: 'Berlin',
});
// all other fields (email, role, etc.) are untouched
// _updatedAt is set automatically

patch(storeName, id, ops)

Field-level operators — increment counters, toggle booleans, push/pull array items:

JavaScript
await ark.patch('users', id, {
  $set:    { city: 'Tokyo' },         // set field
  $inc:    { loginCount: 1 },          // increment by 1
  $dec:    { credits: 5 },             // decrement by 5
  $toggle: { isVerified: true },       // flip boolean
  $push:   { tags: 'vip' },            // append to array
  $pull:   { tags: 'beta' },           // remove from array
  $mul:    { score: 1.5 },             // multiply
  $unset:  ['temporaryField'],         // delete field
});

upsert(storeName, data)

Insert or replace — uses IDB's native put(). Does not require the record to exist first.

JavaScript
// Always works — creates if missing, replaces if key exists
await ark.upsert('settings', { id: 'theme', value: 'dark' });
await ark.upsert('settings', { id: 'theme', value: 'light' }); // overwrites

updateWhere(storeName, filter, changes)

JavaScript
// Deactivate all users with role 'guest'
const updated = await ark.updateWhere(
  'users',
  { role: 'guest' },
  { status: 'inactive' }
);
// returns array of all updated records

Delete & Clear

JavaScript
// Delete by primary key
await ark.delete('users', id);

// Delete all matching a filter
const n = await ark.deleteWhere('sessions', {
  expiresAt: { $lt: new Date().toISOString() }
});
console.log(`Pruned ${n} expired sessions`);

// Clear ALL records (keeps store structure)
await ark.clear('logs');

Query Builder

A fluent, chainable API that reads like English. Returns a ArkQueryBuilder instance — call .exec() to run.

JavaScript
// Chained query
const { data } = await ark
  .query('users')
  .where({ role: 'admin', status: { $ne: 'inactive' } })
  .sortBy('name', 'asc')
  .limit(10)
  .offset(0)
  .exec();

// Count without fetching records
const n = await ark.query('users')
  .where({ status: 'active' })
  .count();

// First matching record
const admin = await ark.query('users')
  .where({ email: 'alice@example.com' })
  .first();

// Paginate via builder
const { data } = await ark.query('posts')
  .where({ category: 'tech' })
  .sortBy('_createdAt', 'desc')
  .page(2, 20)   // page 2, 20 per page
  .exec();

Transactions

Run multiple operations across one or more stores atomically. If anything throws, the entire transaction rolls back automatically.

JavaScript
await ark.transaction(
  ['users', 'orders'],  // stores involved
  'readwrite',
  async (stores) => {
    // Both operations succeed or both roll back
    const userId = crypto.randomUUID();
    await stores.users.add({
      id: userId,
      name: 'Dave',
      _createdAt: new Date().toISOString()
    });
    await stores.orders.add({
      id: crypto.randomUUID(),
      userId,
      total: 149.99,
    });
  }
);
Tip
The stores proxy exposes promisified versions of: add, put, get, delete, clear, getAll, count, getAllKeys, and index(name).

Events

Subscribe to operations with ark.on(event, callback). The wildcard '*' receives every event. Returns an unsubscribe function.

JavaScript
// Subscribe to insert events
const off = ark.on('insert', ({ storeName, key, data }) => {
  console.log(`Inserted into ${storeName}: key=${key}`);
});

// Wildcard — catches everything
ark.on('*', e => console.log('[Ark]', e.event, e));

// Unsubscribe
off();

// All available events:
// 'open'  'upgrade'  'insert'  'insertMany'  'update'
// 'updateMany'  'upsert'  'delete'  'deleteMany'
// 'clear'  'import'  'transaction'  'error'  'versionchange'

Import / Export

JavaScript
// Export one store as JSON string
const json = await ark.exportStore('users');

// Export ALL stores
const fullBackup = await ark.exportAll();

// Trigger browser file download
await ark.downloadStore('users');
// → browser saves "users_1700000000.json"

// Import from JSON string (e.g., after fetch or file read)
const json = await fetch('/backup/users.json').then(r => r.text());
const n = await ark.importStore('users', json);
console.log(`Imported ${n} records`);

Filter Operators

Use these inside filter objects with findAll(), findOne(), updateWhere(), deleteWhere(), and the Query Builder's .where().

Operator Description Example
$eq Equal to (same as plain value shorthand) { age: { $eq: 30 } }
$ne Not equal to { status: { $ne: 'banned' } }
$gt Greater than { price: { $gt: 100 } }
$gte Greater than or equal to { age: { $gte: 18 } }
$lt Less than { stock: { $lt: 10 } }
$lte Less than or equal to { score: { $lte: 50 } }
$in Value is in the given array { role: { $in: ['admin','mod'] } }
$nin Value is NOT in the given array { status: { $nin: ['banned','deleted'] } }
$contains String contains substring (case-insensitive) { name: { $contains: 'ali' } }
$startsWith String starts with prefix (case-insensitive) { email: { $startsWith: 'admin' } }
$endsWith String ends with suffix (case-insensitive) { email: { $endsWith: '.gov' } }
$regex String matches a regular expression (case-insensitive) { phone: { $regex: '^\\+1' } }
$exists Field exists (true) or is null/undefined (false) { avatar: { $exists: true } }
$type typeof field equals the given string { score: { $type: 'number' } }
$size Array field has exactly N elements { tags: { $size: 3 } }
Dot Notation
Access nested fields with dot notation: { 'address.city': 'Berlin' }

Patch Operators

Used with ark.patch() for field-level mutations without reading then writing the full record manually.

Operator Type Description
$set { field: value } Set field to the given value
$unset ['field', ...] Remove (delete) the listed fields
$inc { field: n } Add n to the field (negative n = subtract)
$dec { field: n } Subtract n from the field
$mul { field: n } Multiply the field by n
$toggle { field: true } Flip a boolean field to its opposite
$push { field: value } Append value to an array field
$pull { field: value } Remove all occurrences of value from an array field

Full Method List

Method Returns Description
open(name, version, schema) Promise<this> Open or create database
close() void Close connection
ArkIndexdb.dropDatabase(name) Promise<boolean> Delete a database
ArkIndexdb.listDatabases() Promise<Array> List origin's databases
insert(store, data) Promise<key> Insert one record
insertMany(store, records[]) Promise<key[]> Batch insert in one transaction
findById(store, id) Promise<Object|null> Fetch by primary key
findAll(store, options?) Promise<{data, total}> Fetch all with filter/sort/page
findByIndex(store, index, value) Promise<{data, total}> Native index lookup
findByRange(store, index, range) Promise<{data, total}> Key range query via index
findOne(store, filter) Promise<Object|null> First match or null
exists(store, id) Promise<boolean> Check record existence
count(store, filter?) Promise<number> Count records
getAllKeys(store) Promise<Array> All primary keys
paginate(store, page, size, opts?) Promise<{data, total, …}> Paginated fetch
update(store, id, changes) Promise<Object> Partial record merge
upsert(store, data) Promise<key> Insert or replace
patch(store, id, ops) Promise<Object> Field-level patch operators
updateWhere(store, filter, changes) Promise<Object[]> Bulk update by filter
delete(store, id) Promise<boolean> Delete by primary key
deleteWhere(store, filter) Promise<number> Delete all matching
clear(store) Promise<number> Delete all records in store
query(store) ArkQueryBuilder Fluent query builder
transaction(stores[], mode, fn) Promise<void> Multi-store atomic transaction
iterateCursor(store, cb, dir?) Promise<void> Memory-efficient cursor iteration
exportStore(store) Promise<string> Export store as JSON
exportAll() Promise<string> Export all stores
importStore(store, json) Promise<number> Import records from JSON
downloadStore(store) Promise<void> Browser file download
on(event, callback) Function (unsubscribe) Subscribe to events
off(event, callback) void Unsubscribe listener

CONFIRM ACTION

Are you sure?