UNPKG

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