Comparing version 0.5.3 to 0.6.0
221
db64.js
const { isArray } = Array | ||
const connections = [] | ||
const openDB = (name = 'default', storeNames) => new Promise((resolve, reject) => { | ||
let db | ||
try { | ||
db = window.indexedDB.open(name, 1) | ||
} catch (e) { | ||
reject(e) | ||
} | ||
let db | ||
try { | ||
db = window.indexedDB.open(name, 1) | ||
} catch (e) { | ||
reject(e) | ||
} | ||
db.onupgradeneeded = e => { | ||
const { result } = e.target | ||
storeNames.forEach(storeName => { | ||
if (!result.objectStoreNames.contains(storeName)) { | ||
const storeCreation = result.createObjectStore(storeName) | ||
storeCreation.onerror = err => reject(err.target.error) | ||
} | ||
}) | ||
} | ||
db.onsuccess = e => (hasDBandStores = true, resolve(e.target.result)) //eslint-disable-line | ||
db.onerror = e => reject(e.target.result) | ||
return db | ||
db.onupgradeneeded = e => { | ||
const { result } = e.target | ||
storeNames.forEach(storeName => { | ||
if (!result.objectStoreNames.contains(storeName)) { | ||
const storeCreation = result.createObjectStore(storeName) | ||
storeCreation.onerror = err => reject(err.target.error) | ||
} | ||
}) | ||
} | ||
db.onsuccess = e => { | ||
hasDBandStores = true | ||
connections.push(db) | ||
resolve(e.target.result) | ||
} | ||
db.onerror = e => reject(e.target.result) | ||
return db | ||
}) | ||
const setData = async (db, storeName, key, dataValue, entries) => { | ||
try { | ||
const obStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
try { | ||
const obStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
if (entries) { | ||
const dataEntries = isArray(dataValue) | ||
? () => dataValue.map((fruitName, index) => obStore.put(fruitName, index)) | ||
: () => Object.entries(dataValue).map(([key, value]) => obStore.put(value, key)) | ||
await Promise.all(dataEntries()) | ||
} else { | ||
await obStore.put(dataValue, key) | ||
} | ||
} catch (e) { | ||
console.error(e) | ||
if (entries) { | ||
const dataEntries = isArray(dataValue) | ||
? () => dataValue.map((fruitName, index) => obStore.put(fruitName, index)) | ||
: () => Object.entries(dataValue).map(([key, value]) => obStore.put(value, key)) | ||
await Promise.all(dataEntries()) | ||
} else { | ||
await obStore.put(dataValue, key) | ||
} | ||
return db64 | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return db64 | ||
} | ||
const getData = async (db, storeName, key, entries) => { | ||
return new Promise((resolve) => { | ||
const objectStore = (db.transaction([storeName])).objectStore(storeName) | ||
let dataRequest | ||
if (entries) { | ||
const results = {} | ||
const cursorRequest = objectStore.openCursor() | ||
return new Promise((resolve) => { | ||
const objectStore = (db.transaction([storeName])).objectStore(storeName) | ||
let dataRequest | ||
if (entries) { | ||
const results = {} | ||
const cursorRequest = objectStore.openCursor() | ||
cursorRequest.onsuccess = e => { | ||
const cursor = e.target.result | ||
cursorRequest.onsuccess = e => { | ||
const cursor = e.target.result | ||
if (cursor) { | ||
if (key.includes(cursor.key)) results[cursor.key] = cursor.value | ||
cursor.continue() | ||
} else { | ||
resolve(results) | ||
} | ||
} | ||
if (cursor) { | ||
if (key.includes(cursor.key)) results[cursor.key] = cursor.value | ||
cursor.continue() | ||
} else { | ||
dataRequest = objectStore.get(key) | ||
dataRequest.onsuccess = () => resolve(dataRequest.result) | ||
resolve(results) | ||
} | ||
}) | ||
} | ||
} else { | ||
dataRequest = objectStore.get(key) | ||
dataRequest.onsuccess = () => resolve(dataRequest.result) | ||
} | ||
}) | ||
} | ||
const deleteData = async (db, storeName, key) => { | ||
try { | ||
const objectStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
const cursorRequest = objectStore.openCursor() | ||
try { | ||
const objectStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
const cursorRequest = objectStore.openCursor() | ||
cursorRequest.onsuccess = e => { | ||
const cursor = e.target.result | ||
cursorRequest.onsuccess = e => { | ||
const cursor = e.target.result | ||
if (cursor) { | ||
if ((isArray(key) ? key : [key]).includes(cursor.key)) cursor.delete() | ||
cursor.continue() | ||
} | ||
} | ||
} catch (e) { | ||
console.error(e) | ||
if (cursor) { | ||
if ((isArray(key) ? key : [key]).includes(cursor.key)) cursor.delete() | ||
cursor.continue() | ||
} | ||
} | ||
return db64 | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return db64 | ||
} | ||
const clearStore = (db, storeName) => { | ||
return new Promise((resolve, reject) => { | ||
const objectStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
const objectStoreRequest = objectStore.clear() | ||
return new Promise((resolve, reject) => { | ||
const objectStore = (db.transaction([storeName], 'readwrite')).objectStore(storeName) | ||
const objectStoreRequest = objectStore.clear() | ||
objectStoreRequest.onsuccess = resolve(db64) | ||
objectStoreRequest.onerror = e => reject(e.target.error) | ||
}) | ||
objectStoreRequest.onsuccess = resolve(db64) | ||
objectStoreRequest.onerror = e => reject(e.target.error) | ||
}) | ||
} | ||
const deleteDB = name => { | ||
return new Promise((resolve, reject) => { | ||
const deleteRequest = indexedDB.deleteDatabase(name) | ||
deleteRequest.onsuccess = () => resolve(db64) | ||
deleteRequest.onerror = e => reject(new Error(`Error deleting database: ${e.target.error}`)) | ||
deleteRequest.onblocked = e => { | ||
for (const db of connections) { | ||
if (db.result.name === name) { | ||
db.result.close() | ||
} | ||
} | ||
deleteDB(name) | ||
} | ||
}) | ||
} | ||
let hasDBandStores = false | ||
const db64 = { | ||
create: async (name, storeNames) => { | ||
if(!isArray(storeNames)) return console.error('storeNames should be an array') | ||
create: async (name, storeNames) => { | ||
if (!isArray(storeNames)) return console.error('storeNames should be an array') | ||
await openDB(name, storeNames, hasDBandStores) | ||
return db64 | ||
}, | ||
use: (name, storeName) => { | ||
if(!hasDBandStores) return console.error('A database and store needs to be created first') | ||
await openDB(name, storeNames, hasDBandStores) | ||
return db64 | ||
}, | ||
use: (name, storeName) => { | ||
if (!hasDBandStores) return console.error('A database and store needs to be created first') | ||
return { | ||
set: async (key, value) => openDB(name, storeName) | ||
.then(db => setData(db, storeName, key, value)) | ||
.catch(console.error) | ||
, | ||
setEntries: async (value) => openDB(name, storeName) | ||
.then(db => setData(db, storeName, null, value, 'entries')) | ||
.catch(console.error) | ||
, | ||
get: async key => openDB(name, storeName) | ||
.then(db => getData(db, storeName, key)) | ||
.catch(console.error) | ||
, | ||
getEntries: async (keys) => openDB(name, storeName) | ||
.then(db => getData(db, storeName, keys, 'entries')) | ||
.catch(console.error) | ||
, | ||
delete: async (keys) => openDB(name, storeName) | ||
.then(db => deleteData(db, storeName, keys)) | ||
.catch(console.error) | ||
} | ||
}, | ||
clear: async (name, storeName) => openDB(name, storeName) | ||
.then(db => clearStore(db, storeName)) | ||
return { | ||
set: async (key, value) => openDB(name, storeName) | ||
.then(db => setData(db, storeName, key, value)) | ||
.catch(console.error), | ||
setEntries: async (value) => openDB(name, storeName) | ||
.then(db => setData(db, storeName, null, value, 'entries')) | ||
.catch(console.error), | ||
get: async key => openDB(name, storeName) | ||
.then(db => getData(db, storeName, key)) | ||
.catch(console.error), | ||
getEntries: async (keys) => openDB(name, storeName) | ||
.then(db => getData(db, storeName, keys, 'entries')) | ||
.catch(console.error), | ||
delete: async (keys) => openDB(name, storeName) | ||
.then(db => deleteData(db, storeName, keys)) | ||
.catch(console.error) | ||
} | ||
}, | ||
clear: async (name, storeName) => openDB(name, storeName) | ||
.then(db => clearStore(db, storeName)) | ||
.catch(console.error), | ||
delete: async name => deleteDB(name) | ||
.catch(console.error) | ||
} | ||
export default db64 |
{ | ||
"name": "db64", | ||
"version": "0.5.3", | ||
"version": "0.6.0", | ||
"description": "A Practical IndexedDB API", | ||
@@ -5,0 +5,0 @@ "main": "db64.js", |
@@ -31,2 +31,5 @@ # db64 | ||
await snes.getEntries(['adventure', 'fighting']) // { adventure: 'Mario Wrold', fighting: 'Street Fighter II' } | ||
// Delete an existing db | ||
await db64.delete('Games') | ||
... | ||
@@ -99,8 +102,33 @@ ``` | ||
**Delete a DB** _(string)_ | ||
```javascript | ||
await db64.delete('game-consoles') // game-consoles is deleted | ||
``` | ||
### Why bd64 opts out of deleting databases and object stores | ||
### Why db64 opts out of deleting object stores | ||
We are avoiding versioning to keep your life simple. Deleting an existing object stores in IndexedDB triggers a version change. _(Whilst compaction may optimise, it doesn't ensure the removal of unwanted data)_ | ||
Deleting existing versions of databases or object stores in IndexedDB is not feasible due to the requirement to create a new version, and old versions remain accessible. While compaction may optimize, it doesn't ensure the removal of unwanted data. db64 provides an effective solution by allowing you to clear an object store, removing all its data. This feature proves beneficial for any application, even in cases where empty stores cannot be removed. If you do require versioning consider using [idb](https://github.com/jakearchibald/idb). **If you're not building a progressive web app (PWA) you probably don't need versioning**. | ||
Here's the db64 workflow: | ||
1. Initialise by creating a DB with stores or multiple DBs with stores. | ||
- _(You won't be able to add stores to a DB later unless you delete the DB in question. This is by design)_ | ||
3. Use a DB. | ||
- _(You can make multiple transactions concurrently for multiple DBs, or stores)_ | ||
4. Set, get and clear data. | ||
5. **Handel the DB deletion and re-creation life-cycle**: | ||
- _When data cannot be retrieved from the user's IndexedDB_ | ||
- _When there's an error_ | ||
- _Data corruption_ | ||
- _Quota exceeded_ | ||
- _General errors_ | ||
- _**When in the future you decide to add more stores at initialisation**_ | ||
- _When you want to remove stores, especially for data protection_ | ||
You must consider step 4, if not you may leave users stuck because you think it works fine on your computer. | ||
If you do require versioning consider using [idb](https://github.com/jakearchibald/idb). **If you're not building a progressive web app (PWA) you probably don't need versioning**. | ||
### Contributors | ||
@@ -110,2 +138,2 @@ Don't hesitate just contribute, it's a tiny library we will figure it out. | ||
--- | ||
MIT (c) Julien Etienne 2023 | ||
MIT © Julien Etienne 2023 |
10181
137
137