@qualified-cactus/promised-db

Promised DB - A Typescript wrapper for IndexedDB

It is highly recommended to use this library with Typescript.

API Documentation

How to use

Step 0: Get this package from NPM

npm install @qualified-cactus/promised-db

Step 1: Define your data structure

Use type ObjectStoreDef and class IndexDef to define object store(s) and index(es) respectively. It is recommened to group related definitions in a namespace or module to avoid repetitive name such as "TodoTaskTypeWithoutKey", "TodoTaskObjectStore", etc...

namespace TodoTask {
// define TypeWithoutKey if you use auto-generated inline key(s)
export interface TypeWithoutKey {
name: string
completed: number // 0 for false, 1 for true
}
export interface Type extends TypeWithoutKey {
id: number
}

export const ObjectStore: ObjectStoreDef<Type, number, TypeWithoutKey> = {
name: "todo-tasks",
options: {
keyPath: "id",
autoIncrement: true
}
}
export const NameIndex = new IndexDef<string>(
"todo-task-name-index", // index name
"name", // index path
{ unique: true } // index's options
)
export const CompletedIndex = new IndexDef<number>(
"todo-task-completed-index", // index name
"completed", // index path
)
}

Step 2: Define your database

Use the class DatabaseDef to define a database.

const TodoTaskDbDef = new DatabaseDef(
"todo-tasks-db", // db name
1, // version

// upgrade handler
(db, oldVersion, newVersion) => {
if (oldVersion < 1) {
const objectStore = db.createObjectStore(TodoTask.ObjectStore)
objectStore.createIndex(TodoTask.NameIndex)
objectStore.createIndex(TodoTask.CompletedIndex)
}
}
)

Step 3: Open DB and start a transaction

async function doOperation() {

const db: Database = await TodoTaskDbDef.open()
await db.transaction(
[TodoTask.ObjectStore], // transaction's scope
"readwrite", // mode
async (transaction) => {

// find the first completed todo-task in db and delete it
const todoTaskObjectStore = transaction.objectStore(TodoTask.ObjectStore)
const completedIndex = todoTaskObjectStore.index(TodoTask.CompletedIndex)
const task = await completedIndex.get(1)
if (task) {
await todoTaskObjectStore.delete(task.id)
}

// key range can be created from Index definition
const nameIndex = todoTaskObjectStore.index(TodoTask.NameIndex)
// get all tasks with name greater than "bazz"
const tasksList = await nameIndex.getAll(TodoTask.NameIndex.lowerBound("bazz"))

// iterate over keys of index / objectstore
await nameIndex.iterator.iterateKeys(async (cursor)=>{
console.log(cursor.key) // access index key
console.log(cursor.primaryKey) // access primary key

// break iteration if key equals "foo"
if (cursor.key === "foo") {
return true // return true to break iteration early
}
})

// iterate over objects of index / objectstore
await nameIndex.iterator.iterateValues(async (cursor)=>{
console.log(cursor.key) // access index key
console.log(cursor.primaryKey) // access primary key
console.log(cursor.value) // access object

await cursor.update({...cursor.value, name: "new name"}) // update value using cursor
await cursor.delete() // or delete value using cursor
},{
query: TodoTask.NameIndex.lowerBound("a"), // iterate over name starting with "a",
direction: "prev" // descending order
})
}
)
}

WARNING: Perform other long-running async operation (fetching api, etc...) inside transaction will cause TransactionInactiveError. The reason is that IndexedDB autocommit when there is no pending database operation within a brief period of time.