BoxDB is a promise-based browser ORM for IndexedDB
Concept#
IndexedDB is async, fast, powerful. but not support Promise and
must use callback pattern to using IndexedDB. It feels like a legacy code.
For this reason, BoxDB project has started.
But, why name is BoxDB?
- Boxes are lightweight.
- Boxes has shape.
- Boxes can store something.
- Boxes are easy to take.
This is the starting point and core concept of BoxDB.
Boxes are the essence of BoxDB. A box is an abstraction that represents a object store in your IndexedDB.
Box is the same concept like Model
used by other ORMs.
Features#
- Promise based ORM
- User friendly and easy to use
- Lightweight(< 10kb) IndexedDB wrapper
- Zero dependency
- Database and object store version management
- Data validation and transaction control via model (box)
- ACID(Atomicity, Consistency, Isolation, Durability) guaranteed with transaction
- Supports TypeScript
- Works on Web workers
Browser Support#
IE |
Edge |
Firefox |
Chrome |
Safari |
iOS Safari |
Samsung |
Opera |
11 |
12~ |
10~ |
23~ |
10~ |
10~ |
4~ |
15~ |
- Test features in your browser here.
- Checkout
IE11
test here.
Installation#
BoxDB is available via npm (or yarn).
1
2
3
|
yarn add bxd
# or
npm install bxd
|
Basics#
Preparing for database#
If database is not exist, create new one.
1
2
3
|
import BoxDB from 'bxd';
const db = new BoxDB('database-name', 1); // database name, version
|
Create box#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// User Box (object store name: user)
const User = db.create('user', {
_id: {
type: BoxDB.Types.STRING,
key: true,
},
name: {
type: BoxDB.Types.STRING,
index: true,
},
age: BoxDB.Types.NUMBER,
});
// Item Box (object store name: item)
const Item = db.create('item', {
uid: {
type: BoxDB.Types.STRING,
index: true,
},
name: {
type: BoxDB.Types.STRING,
index: true,
},
memo: BoxDB.Types.STRING,
});
|
Update database version#
If you want change database version (eg. change box schema, create new box, etc) just change to larger number then previous version
1
2
3
4
|
// Previous version
// const db = new BoxDB('database-name', 1);
const db = new BoxDB('database-name', 2); // Just like this!
|
Open database#
1
2
3
4
5
6
|
// define boxes
// ...
await db.open();
// You can use Boxes from here! (data access/control)
|
Close database#
It usually used in special cases. (eg. open database that has same name)
Transaction#
Add single record#
1
2
|
await User.add({ _id: '0', name: 'EMPTY', age: 0 });
await User.add({ _id: '1', name: 'leegeunhyeok', age: 20 });
|
Add multiple records#
1
2
3
4
5
6
7
8
9
10
11
12
|
const items = [
{ uid: '0', name: 'unknown 1', memo: '' },
{ uid: '0', name: 'unknown 2', memo: '' },
{ uid: '1', name: 'mac', memo: 'so expensive' },
{ uid: '1', name: 'phone', memo: '' },
{ uid: '1', name: 'desktop', memo: '' },
];
// In async function
for (let i = 0; i < items.length; i++) {
await Item.add(items[i]);
}
|
Get record#
1
2
|
await User.get('1'); // { _id: '1', name: 'leegeunhyeok', age: 20 }
await User.get('999'); // null
|
Update record#
1
|
await User.put({ _id: '0', name: 'temp' }); // Update `user._id = '0'`: `name` will update to 'temp'
|
Delete record#
1
|
await User.delete('0'); // Delete `user._id = '0'`
|
Get all records by cursor#
1
2
3
4
5
6
7
8
9
|
await Item.find().get();
// [
// { uid: '0', name: 'unknown 1', memo: '' },
// { uid: '0', name: 'unknown 2', memo: '' },
// { uid: '1', name: 'mac', memo: 'so expensive' },
// { uid: '1', name: 'phone', memo: '' },
// { uid: '1', name: 'desktop', memo: '' }
// ]
|
Get records by cursor (using index)#
1
2
3
|
await Item.find({ index: 'name', value: 'phone' }).get();
// [{ uid: '1', name: 'phone', memo: '' }]
|
Get records by cursor (using filter functions)#
1
2
3
4
5
6
7
|
await Item.find(
null,
(item) => parseInt(item.uid) % 2 === 1,
(item) => item.memo.includes('expensive'),
).get();
// [{ uid: '1', name: 'mac', memo: 'so expensive' }]
|
Get records by cursor (both)#
1
2
3
4
5
6
7
8
9
10
|
await Item.find(
{
index: 'uid',
value: BoxDB.Range.equal('1'),
},
(item) => !!item.memo,
(item) => item.memo.includes('exp'),
).get();
// [{ uid: '1', name: 'mac', memo: 'so expensive' }]
|
Update multiple records by cursor#
1
2
3
4
|
await Item.find(null, (item) => !!item.memo).update({ memo: 'new memo' });
// Before: { uid: '1', name: 'mac', memo: 'so expensive' }
// After: { uid: '1', name: 'mac', memo: 'new memo' }
|
Delete multiple records by cursor#
1
2
3
|
await Item.find(null, (item) => item.uid === '0').delete();
// Delete all of `Item.uid = '0'`
|
Multiple task in one transaction#
If error occurs during transaction task, will be rollback to before transaction.
Transactionable methods name has $
prefix ($add, $put, $delete, $find)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
await db.transaction(
User.$add({ _id: '3', name: 'Aiden', age: 16 }),
Item.$add({ uid: '3', name: 'pencil', memo: 'super sharp' }),
Item.$add({ uid: '3', name: 'eraser' }),
Item.$add({ uid: '3', name: 'book', memo: '200p' }),
Item.$add({ uid: '3', name: 'juice' }),
);
// Add new user: { _id: '3', name: 'Aiden', age: 16 }
// Add new item: { uid: '3', name: 'pencil', memo: 'super sharp' }
// Add new item: { uid: '3', name: 'book', memo: '200 pages' }
// Add new item: { uid: '3', name: 'eraser', memo: '' }
// Add new item: { uid: '3', name: 'juice', memo: '' }
|
Get records count#
1
|
await Item.count(); // n
|
Clear all records#
1
2
3
4
5
|
// A. Using cursor
await User.find().delete();
// B. Using clear()
await User.clear();
|
Web workers#
1
2
3
4
|
importScripts('https://cdn.jsdelivr.net/npm/bxd@latest/dist/bxd.min.js');
// Full features available!
self.BoxDB;
|