UNPKG

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