gs-idb-pro
Version:
> A full-featured wrapper for browser IndexedDB
345 lines (286 loc) • 9.7 kB
Markdown
# gs-idb-pro
> A full-featured wrapper for browser IndexedDB
<details>
<summary>中文</summary>
> 一款对于浏览器indexedDB的全功能封装
> 包含: 查询、游标、分页、事务、关联、数据库版本处理、配置管理……等功能
## 安装
```shell
npx yarn gs-idb-pro
```
## 使用
### 1. 基础使用
#### 1. 基本CRUD
```ts
import {dbStore} from 'gs-idb-pro'
//在默认数据库中创建名为`user`的`IDBObjectStore`,默认`keyPath`为`id`,`autoIncrement`为`true`
const userStore = dbStore('user')
// 在 `userStore` 中创建名为`name`的索引
const nameIndex = userStore.index('name');
// 在 `userStore` 中创建名为`age`的索引
const ageIndex = userStore.index('age');
// 通过 `add` 方法向 `userStore` 中添加数据,默认会记录添加时间与修改时间(可配置)
await userStore.add({name: '张三', age: 18, id: 1})
await userStore.add({name: '李四', age: 19, id: 2})
await userStore.add({name: '王五', age: 20, id: 3})
await userStore.add({name: '赵六', age: 20, id: 4})
// 通过 `all` 方法获取 `userStore` 中的所有数据
console.log(await userStore.all())
// 通过 `get` 方法获取 `userStore` 中 `id` 为 1 的数据
console.log(await userStore.get(1))
// 通过 `get` 方法获取 `userStore` 中 `name`索引值为 '张三' 的数据
console.log(await nameIndex.get('张三'))
// 通过 `get` 方法获取 `age`索引值为 20 的数据
console.log(await ageIndex.get(20))
// 通过 `delete` 方法删除 `userStore` 中 `id` 为 1 的数据
await userStore.delete(1)
// 获取分页数据第一页(添加时间降序,前2条)
const page1 = await userStore.page({size:2},1)
console.log(page1)
// 获取下一页分页数据(最后1条数据)
console.log(await userStore.nextPage(page1.info))
```
#### 2. 保存配置信息
> 将某一个`IDBObjectStore`当作一个键值对数据库使用
> 可用于保存常见配置信息
> 特别是文件句柄(`FileSystemHandle`)在目前的浏览器环境中的只能使用`indexedDB`保存
```ts
import {dbMap} from 'gs-idb-pro'
/**
* 创建一个无`keyPath`的`IDBObjectStore`
*/
const settings = dbMap('settings');
await new Promise(( (resolve, reject)=> {
const btn = document.createElement('button');
btn.innerText = 'Click me to test.'
document.body.append(btn)
btn.onclick = async ()=>{
try{
// 设置 工作目录
await settings.set('workDir', await showDirectoryPicker());
resolve();
} catch (e) {
reject(e)
}
}
}))
// 读取 工作目录 的设置
console.log(await settings.get('workDir'))
```
#### 3. 事务与批处理
```ts
// 批量读取,过程中不会重复关闭打开数据库
const result = await userStore.batchRead(async (openedStore) => {
return [
await openedStore.get(1),
await openedStore.filter({lte: 10, gt: 3})
]
})
// 多次读写操作, 如果中途出错,将回滚所有写入
await userStore.batchWrite(async (openedStore)=>{
// 代码
})
```
#### 4. 关联
```ts
import {asyncMap } from 'gs-base'
import {dbMap} from 'gs-idb-pro'
//创建 `user` 与 `role` 的关联存储
const stores = dbStores(['user','role'])
// 执行关联读取
const result = await stores.read((userStore, roleStore) => {
return asyncMap(userStore.filter(), async (user) => ({...user, role: await roleStore.get(user.roleId)}))
})
```
### 2. 更多
#### 1. Schema定义
```ts
import {IDbPro} from 'gs-idb-pro'
//配置默认数据库名称
IDbPro.defaultDb.schema.name = 'db-name'
//配置默认数据库版本
IDbPro.defaultDb.schema.version = 2
//配置将来创建`IDBObjectStore`时的默认值
IDbPro.defaultDb.schema.storeTemplate = {
keyPath: 'id',
autoIncrement: true,
addedTimeField: false,
updatedTimeField: 'updated_at',
softDeletedField: true
}
// 创建带默认值、无`keyPath`的`IDBObjectStore`
dbMap('map', [
['v1', 'k1'],
['v2', 'k2'],
// ……
])
// 详细创建多索引和默认值的`IDBObjectStore`
dbStore({
name: 'user',
indexSchemas: [
{name: 'name_age', keyPath: ['name', 'age']},
{name: 'tags', keyPath: 'tags', multiEntry: true},
],
defaultData: [
{name: '张三', age: 18, tags: ['年轻', '贪玩']},
]
}, {name: 'name', unique: true})
// 初始化 Schema (赋予默认值和转换详细信息,仅用于调试配置信息,非调试时不需要手工调用 )
IDbPro.defaultDb.initSchema();
//向控制台输出当前配置结果
IDbPro.defaultDb.traceSchema()
```
#### 2. 多数据库
> 由于浏览器限制,不能同时打开多个数据库
> 所以没有特殊情况不建议创建多个数据库
```ts
const db1 = new IDbPro('db1')
const db2 = new IDbPro({name:'db2',version:2})
db1.store('db1-store-1')
db1.store('db1-store-2')
db2.store('db2-store-1')
db2.store('db2-store-2')
```
</details>
<details>
<summary>English</summary>
> A full-featured wrapper for browser IndexedDB
> Includes: query, cursor, pagination, transactions, relations, DB version handling, config management, etc.
## Installation
```shell
npx yarn gs-idb-pro
```
## Usage
### 1. Basic Usage
#### 1. Basic CRUD
```ts
import {dbStore} from 'gs-idb-pro'
// Create an `IDBObjectStore` named `user` in the default DB, with `keyPath`=`id` and `autoIncrement`=true
const userStore = dbStore('user')
// Create an index named `name` in `userStore`
const nameIndex = userStore.index('name');
// Create an index named `age` in `userStore`
const ageIndex = userStore.index('age');
// Add data using `add` (creation & update timestamps are added by default, configurable)
await userStore.add({name: 'John Smith', age: 18, id: 1})
await userStore.add({name: 'Alice Brown', age: 19, id: 2})
await userStore.add({name: 'Michael Lee', age: 20, id: 3})
await userStore.add({name: 'Sarah Johnson', age: 20, id: 4})
// Get all data from `userStore`
console.log(await userStore.all())
// Get data with `id` = 1
console.log(await userStore.get(1))
// Get data by index `name` = 'John Smith'
console.log(await nameIndex.get('John Smith'))
// Get data by index `age` = 20
console.log(await ageIndex.get(20))
// Delete data with `id` = 1
await userStore.delete(1)
// Get first page (size 2, sorted by added time desc)
const page1 = await userStore.page({size:2},1)
console.log(page1)
// Get next page (remaining data)
console.log(await userStore.nextPage(page1.info))
```
#### 2. Storing Config Data
> Use an IDBObjectStore as a simple key-value DB
> Suitable for storing common configuration
> Especially FileSystemHandle must be stored in indexedDB in browsers
```ts
import {dbMap} from 'gs-idb-pro'
/**
* Create an `IDBObjectStore` without `keyPath`
*/
const settings = dbMap('settings');
await new Promise((resolve, reject)=> {
const btn = document.createElement('button');
btn.innerText = 'Click me to test.'
document.body.append(btn)
btn.onclick = async ()=>{
try{
// Set working directory
await settings.set('workDir', await showDirectoryPicker());
resolve();
} catch (e) {
reject(e)
}
}
})
// Read working directory setting
console.log(await settings.get('workDir'))
```
#### 3. Transactions & Batch
```ts
// Batch read, DB won't reopen repeatedly
const result = await userStore.batchRead(async (openedStore) => {
return [
await openedStore.get(1),
await openedStore.filter({lte: 10, gt: 3})
]
})
// Multiple read/write in a single transaction; all writes will rollback if error occurs
await userStore.batchWrite(async (openedStore)=>{
// code
})
```
#### 4. Relations
```ts
import {asyncMap } from 'gs-base'
import {dbMap, dbStores} from 'gs-idb-pro'
// Create related stores `user` and `role`
const stores = dbStores(['user','role'])
// Perform relational read
const result = await stores.read((userStore, roleStore) => {
return asyncMap(userStore.filter(), async (user) => ({...user, role: await roleStore.get(user.roleId)}))
})
```
### 2. Advanced
#### 1. Schema Definition
```ts
import {IDbPro} from 'gs-idb-pro'
// Set default DB name
IDbPro.defaultDb.schema.name = 'db-name'
// Set default DB version
IDbPro.defaultDb.schema.version = 2
// Default schema template for future stores
IDbPro.defaultDb.schema.storeTemplate = {
keyPath: 'id',
autoIncrement: true,
addedTimeField: false,
updatedTimeField: 'updated_at',
softDeletedField: true
}
// Create a key-value style store with defaults
dbMap('map', [
['v1', 'k1'],
['v2', 'k2'],
// ...
])
// Create store with multiple indexes and default data
dbStore({
name: 'user',
indexSchemas: [
{name: 'name_age', keyPath: ['name', 'age']},
{name: 'tags', keyPath: 'tags', multiEntry: true},
],
defaultData: [
{name: 'John Smith', age: 18, tags: ['young', 'playful']},
]
}, {name: 'name', unique: true})
// Initialize schema (for debugging, not required normally)
IDbPro.defaultDb.initSchema();
// Trace schema in console
IDbPro.defaultDb.traceSchema()
```
#### 2. Multiple Databases
> Due to browser limits, you can't open multiple DBs at once.
> Avoid multiple DBs unless necessary.
```ts
const db1 = new IDbPro('db1')
const db2 = new IDbPro({name:'db2',version:2})
db1.store('db1-store-1')
db1.store('db1-store-2')
db2.store('db2-store-1')
db2.store('db2-store-2')
```
</details>