UNPKG

7.12 kBJavaScriptView Raw
1/* eslint-env browser */
2
3/**
4 * Helpers to work with IndexedDB.
5 *
6 * @module indexeddb
7 */
8
9import * as promise from './promise.js'
10import * as error from './error.js'
11
12/* c8 ignore start */
13
14/**
15 * IDB Request to Promise transformer
16 *
17 * @param {IDBRequest} request
18 * @return {Promise<any>}
19 */
20export const rtop = request => promise.create((resolve, reject) => {
21 // @ts-ignore
22 request.onerror = event => reject(new Error(event.target.error))
23 // @ts-ignore
24 request.onsuccess = event => resolve(event.target.result)
25})
26
27/**
28 * @param {string} name
29 * @param {function(IDBDatabase):any} initDB Called when the database is first created
30 * @return {Promise<IDBDatabase>}
31 */
32export const openDB = (name, initDB) => promise.create((resolve, reject) => {
33 const request = indexedDB.open(name)
34 /**
35 * @param {any} event
36 */
37 request.onupgradeneeded = event => initDB(event.target.result)
38 /**
39 * @param {any} event
40 */
41 request.onerror = event => reject(error.create(event.target.error))
42 /**
43 * @param {any} event
44 */
45 request.onsuccess = event => {
46 /**
47 * @type {IDBDatabase}
48 */
49 const db = event.target.result
50 db.onversionchange = () => { db.close() }
51 if (typeof addEventListener !== 'undefined') {
52 addEventListener('unload', () => db.close())
53 }
54 resolve(db)
55 }
56})
57
58/**
59 * @param {string} name
60 */
61export const deleteDB = name => rtop(indexedDB.deleteDatabase(name))
62
63/**
64 * @param {IDBDatabase} db
65 * @param {Array<Array<string>|Array<string|IDBObjectStoreParameters|undefined>>} definitions
66 */
67export const createStores = (db, definitions) => definitions.forEach(d =>
68 // @ts-ignore
69 db.createObjectStore.apply(db, d)
70)
71
72/**
73 * @param {IDBDatabase} db
74 * @param {Array<string>} stores
75 * @param {"readwrite"|"readonly"} [access]
76 * @return {Array<IDBObjectStore>}
77 */
78export const transact = (db, stores, access = 'readwrite') => {
79 const transaction = db.transaction(stores, access)
80 return stores.map(store => getStore(transaction, store))
81}
82
83/**
84 * @param {IDBObjectStore} store
85 * @param {IDBKeyRange} [range]
86 * @return {Promise<number>}
87 */
88export const count = (store, range) =>
89 rtop(store.count(range))
90
91/**
92 * @param {IDBObjectStore} store
93 * @param {String | number | ArrayBuffer | Date | Array<any> } key
94 * @return {Promise<String | number | ArrayBuffer | Date | Array<any>>}
95 */
96export const get = (store, key) =>
97 rtop(store.get(key))
98
99/**
100 * @param {IDBObjectStore} store
101 * @param {String | number | ArrayBuffer | Date | IDBKeyRange | Array<any> } key
102 */
103export const del = (store, key) =>
104 rtop(store.delete(key))
105
106/**
107 * @param {IDBObjectStore} store
108 * @param {String | number | ArrayBuffer | Date | boolean} item
109 * @param {String | number | ArrayBuffer | Date | Array<any>} [key]
110 */
111export const put = (store, item, key) =>
112 rtop(store.put(item, key))
113
114/**
115 * @param {IDBObjectStore} store
116 * @param {String | number | ArrayBuffer | Date | boolean} item
117 * @param {String | number | ArrayBuffer | Date | Array<any>} key
118 * @return {Promise<any>}
119 */
120export const add = (store, item, key) =>
121 rtop(store.add(item, key))
122
123/**
124 * @param {IDBObjectStore} store
125 * @param {String | number | ArrayBuffer | Date} item
126 * @return {Promise<number>} Returns the generated key
127 */
128export const addAutoKey = (store, item) =>
129 rtop(store.add(item))
130
131/**
132 * @param {IDBObjectStore} store
133 * @param {IDBKeyRange} [range]
134 * @param {number} [limit]
135 * @return {Promise<Array<any>>}
136 */
137export const getAll = (store, range, limit) =>
138 rtop(store.getAll(range, limit))
139
140/**
141 * @param {IDBObjectStore} store
142 * @param {IDBKeyRange} [range]
143 * @param {number} [limit]
144 * @return {Promise<Array<any>>}
145 */
146export const getAllKeys = (store, range, limit) =>
147 rtop(store.getAllKeys(range, limit))
148
149/**
150 * @param {IDBObjectStore} store
151 * @param {IDBKeyRange|null} query
152 * @param {'next'|'prev'|'nextunique'|'prevunique'} direction
153 * @return {Promise<any>}
154 */
155export const queryFirst = (store, query, direction) => {
156 /**
157 * @type {any}
158 */
159 let first = null
160 return iterateKeys(store, query, key => {
161 first = key
162 return false
163 }, direction).then(() => first)
164}
165
166/**
167 * @param {IDBObjectStore} store
168 * @param {IDBKeyRange?} [range]
169 * @return {Promise<any>}
170 */
171export const getLastKey = (store, range = null) => queryFirst(store, range, 'prev')
172
173/**
174 * @param {IDBObjectStore} store
175 * @param {IDBKeyRange?} [range]
176 * @return {Promise<any>}
177 */
178export const getFirstKey = (store, range = null) => queryFirst(store, range, 'next')
179
180/**
181 * @typedef KeyValuePair
182 * @type {Object}
183 * @property {any} k key
184 * @property {any} v Value
185 */
186
187/**
188 * @param {IDBObjectStore} store
189 * @param {IDBKeyRange} [range]
190 * @param {number} [limit]
191 * @return {Promise<Array<KeyValuePair>>}
192 */
193export const getAllKeysValues = (store, range, limit) =>
194 // @ts-ignore
195 promise.all([getAllKeys(store, range, limit), getAll(store, range, limit)]).then(([ks, vs]) => ks.map((k, i) => ({ k, v: vs[i] })))
196
197/**
198 * @param {any} request
199 * @param {function(IDBCursorWithValue):void|boolean|Promise<void|boolean>} f
200 * @return {Promise<void>}
201 */
202const iterateOnRequest = (request, f) => promise.create((resolve, reject) => {
203 request.onerror = reject
204 /**
205 * @param {any} event
206 */
207 request.onsuccess = async event => {
208 const cursor = event.target.result
209 if (cursor === null || (await f(cursor)) === false) {
210 return resolve()
211 }
212 cursor.continue()
213 }
214})
215
216/**
217 * Iterate on keys and values
218 * @param {IDBObjectStore} store
219 * @param {IDBKeyRange|null} keyrange
220 * @param {function(any,any):void|boolean|Promise<void|boolean>} f Callback that receives (value, key)
221 * @param {'next'|'prev'|'nextunique'|'prevunique'} direction
222 */
223export const iterate = (store, keyrange, f, direction = 'next') =>
224 iterateOnRequest(store.openCursor(keyrange, direction), cursor => f(cursor.value, cursor.key))
225
226/**
227 * Iterate on the keys (no values)
228 *
229 * @param {IDBObjectStore} store
230 * @param {IDBKeyRange|null} keyrange
231 * @param {function(any):void|boolean|Promise<void|boolean>} f callback that receives the key
232 * @param {'next'|'prev'|'nextunique'|'prevunique'} direction
233 */
234export const iterateKeys = (store, keyrange, f, direction = 'next') =>
235 iterateOnRequest(store.openKeyCursor(keyrange, direction), cursor => f(cursor.key))
236
237/**
238 * Open store from transaction
239 * @param {IDBTransaction} t
240 * @param {String} store
241 * @returns {IDBObjectStore}
242 */
243export const getStore = (t, store) => t.objectStore(store)
244
245/**
246 * @param {any} lower
247 * @param {any} upper
248 * @param {boolean} lowerOpen
249 * @param {boolean} upperOpen
250 */
251export const createIDBKeyRangeBound = (lower, upper, lowerOpen, upperOpen) => IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen)
252
253/**
254 * @param {any} upper
255 * @param {boolean} upperOpen
256 */
257export const createIDBKeyRangeUpperBound = (upper, upperOpen) => IDBKeyRange.upperBound(upper, upperOpen)
258
259/**
260 * @param {any} lower
261 * @param {boolean} lowerOpen
262 */
263export const createIDBKeyRangeLowerBound = (lower, lowerOpen) => IDBKeyRange.lowerBound(lower, lowerOpen)
264
265/* c8 ignore stop */