idb
使い方
databaseを開く
code:sample.js
import { openDB } from './with-async-ittr.js';
const db = await openDB('databaseName', 1,
upgradeでdatabaseの初期化及び更新処理を行う
code:sample.js
upgrade(db) {
const books = db.createObjectStore('books', {keyPath: 'ISBN'});
const authors = db.createObjectStore('authors', {keyPath: 'id', autoIncrement: true});
}
)
新規作成
code:sample.js
{
await db.add('books', {
price: 1234,
title: 'valuable book',
}, 'aaa-aaaaaa');
}
値の更新
code:sample.js
{
await db.put('books', {
price: 1234,
title: 'acvanced book',
}, 'aaa-aaaaaa');
}
Object storeはtx.storeで取得できる
終了処理は自動的に行われる
終了するまで待ちたい場合は、await tx.doneを使う
code:sample.js
{
const tx = db.transaction('books', 'readwrite');
const books = tx.store;
await Promise.all([
books.add({price: 114514, title: 'wonderful book', keywords: /*...*/}, 'xxx-xxxxxx'); books.add({price: 1919893, title: 'amazing book', keywords: /*...*/}, 'yyy-yyyyyy'); ]);
await tx.done;
}
一つのtransactionで複数のtransactionを開くこともできる
code:sample.js
{
const books = tx.objectStore('books');
const authors = tx.objectStore('authors');
await Promise.all([
books.add({price: 114514, title: 'fascinating book', keywords: /*...*/}, 'zzz-zzzzzz'); authors.add({name: {first: 'Mary', second: 'Smith'}, keywords: /*...*/}); ]);
await tx.done;
}
Scrapboxから使えるようにコードをコピペしといた
LICENSE
code:LICENSE.md
ISC License (ISC)
Copyright (c) 2016, Jake Archibald <jaffathecake@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
/api/code/:project/:titleを省けることに気づいたtakker.icon
code:index.js
import { w as wrap, r as replaceTraps } from './wrap-idb-value.js';
export { u as unwrap, w as wrap } from './wrap-idb-value.js';
/**
* Open a database.
*
* @param name Name of the database.
* @param version Schema version.
* @param callbacks Additional callbacks.
*/
function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
const request = indexedDB.open(name, version);
const openPromise = wrap(request);
if (upgrade) {
request.addEventListener('upgradeneeded', (event) => {
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction));
});
}
if (blocked)
request.addEventListener('blocked', () => blocked());
openPromise
.then((db) => {
if (terminated)
db.addEventListener('close', () => terminated());
if (blocking)
db.addEventListener('versionchange', () => blocking());
})
.catch(() => { });
return openPromise;
}
/**
* Delete a database.
*
* @param name Name of the database.
*/
function deleteDB(name, { blocked } = {}) {
const request = indexedDB.deleteDatabase(name);
if (blocked)
request.addEventListener('blocked', () => blocked());
return wrap(request).then(() => undefined);
}
const cachedMethods = new Map();
function getMethod(target, prop) {
if (!(target instanceof IDBDatabase &&
!(prop in target) &&
typeof prop === 'string')) {
return;
}
if (cachedMethods.get(prop))
return cachedMethods.get(prop);
const targetFuncName = prop.replace(/FromIndex$/, '');
const useIndex = prop !== targetFuncName;
const isWrite = writeMethods.includes(targetFuncName);
if (
// Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
!(isWrite || readMethods.includes(targetFuncName))) {
return;
}
const method = async function (storeName, ...args) {
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
let target = tx.store;
if (useIndex)
target = target.index(args.shift());
// Must reject if op rejects.
// If it's a write operation, must reject if tx.done rejects.
// Must reject with op rejection first.
// Must resolve with op value.
// Must handle both promises (no unhandled rejections)
return (await Promise.all([
isWrite && tx.done,
};
cachedMethods.set(prop, method);
return method;
}
replaceTraps((oldTraps) => ({
...oldTraps,
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
}));
export { deleteDB, openDB };
pathを調節した
code:with-async-ittr.js
export * from './index.js';
import './async-iterators.js';
code:async-iterators.js
import { r as replaceTraps, i as instanceOfAny, a as reverseTransformCache, u as unwrap } from './wrap-idb-value.js';
const methodMap = {};
const advanceResults = new WeakMap();
const ittrProxiedCursorToOriginalProxy = new WeakMap();
const cursorIteratorTraps = {
get(target, prop) {
if (!advanceMethodProps.includes(prop))
let cachedFunc = methodMapprop; if (!cachedFunc) {
cachedFunc = methodMapprop = function (...args) { advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)prop(...args)); };
}
return cachedFunc;
},
};
async function* iterate(...args) {
// tslint:disable-next-line:no-this-assignment
let cursor = this;
if (!(cursor instanceof IDBCursor)) {
cursor = await cursor.openCursor(...args);
}
if (!cursor)
return;
cursor = cursor;
const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
// Map this double-proxy back to the original, so other cursor methods work.
reverseTransformCache.set(proxiedCursor, unwrap(cursor));
while (cursor) {
yield proxiedCursor;
// If one of the advancing methods was not called, call continue().
cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
advanceResults.delete(proxiedCursor);
}
}
function isIteratorProp(target, prop) {
return ((prop === Symbol.asyncIterator &&
}
replaceTraps((oldTraps) => ({
...oldTraps,
get(target, prop, receiver) {
if (isIteratorProp(target, prop))
return iterate;
return oldTraps.get(target, prop, receiver);
},
has(target, prop) {
return isIteratorProp(target, prop) || oldTraps.has(target, prop);
},
}));
code:wrap-idb-value.js
const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
let idbProxyableTypes;
let cursorAdvanceMethods;
// This is a function to prevent it throwing up in node environments.
function getIdbProxyableTypes() {
return (idbProxyableTypes ||
(idbProxyableTypes = [
IDBDatabase,
IDBObjectStore,
IDBIndex,
IDBCursor,
IDBTransaction,
]));
}
// This is a function to prevent it throwing up in node environments.
function getCursorAdvanceMethods() {
return (cursorAdvanceMethods ||
(cursorAdvanceMethods = [
IDBCursor.prototype.advance,
IDBCursor.prototype.continue,
IDBCursor.prototype.continuePrimaryKey,
]));
}
const cursorRequestMap = new WeakMap();
const transactionDoneMap = new WeakMap();
const transactionStoreNamesMap = new WeakMap();
const transformCache = new WeakMap();
const reverseTransformCache = new WeakMap();
function promisifyRequest(request) {
const promise = new Promise((resolve, reject) => {
const unlisten = () => {
request.removeEventListener('success', success);
request.removeEventListener('error', error);
};
const success = () => {
resolve(wrap(request.result));
unlisten();
};
const error = () => {
reject(request.error);
unlisten();
};
request.addEventListener('success', success);
request.addEventListener('error', error);
});
promise
.then((value) => {
// Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
// (see wrapFunction).
if (value instanceof IDBCursor) {
cursorRequestMap.set(value, request);
}
// Catching to avoid "Uncaught Promise exceptions"
})
.catch(() => { });
// This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
// is because we create many promises from a single IDBRequest.
reverseTransformCache.set(promise, request);
return promise;
}
function cacheDonePromiseForTransaction(tx) {
// Early bail if we've already created a done promise for this transaction.
if (transactionDoneMap.has(tx))
return;
const done = new Promise((resolve, reject) => {
const unlisten = () => {
tx.removeEventListener('complete', complete);
tx.removeEventListener('error', error);
tx.removeEventListener('abort', error);
};
const complete = () => {
resolve();
unlisten();
};
const error = () => {
reject(tx.error || new DOMException('AbortError', 'AbortError'));
unlisten();
};
tx.addEventListener('complete', complete);
tx.addEventListener('error', error);
tx.addEventListener('abort', error);
});
// Cache it for later retrieval.
transactionDoneMap.set(tx, done);
}
let idbProxyTraps = {
get(target, prop, receiver) {
if (target instanceof IDBTransaction) {
// Special handling for transaction.done.
if (prop === 'done')
return transactionDoneMap.get(target);
// Polyfill for objectStoreNames because of Edge.
if (prop === 'objectStoreNames') {
return target.objectStoreNames || transactionStoreNamesMap.get(target);
}
// Make tx.store return the only store in the transaction, or undefined if there are many.
if (prop === 'store') {
return receiver.objectStoreNames1 ? undefined
: receiver.objectStore(receiver.objectStoreNames0); }
}
// Else transform whatever we get back.
},
set(target, prop, value) {
return true;
},
has(target, prop) {
if (target instanceof IDBTransaction &&
(prop === 'done' || prop === 'store')) {
return true;
}
return prop in target;
},
};
function replaceTraps(callback) {
idbProxyTraps = callback(idbProxyTraps);
}
function wrapFunction(func) {
// Due to expected object equality (which is enforced by the caching in wrap), we
// only create one new func per func.
// Edge doesn't support objectStoreNames (booo), so we polyfill it here.
if (func === IDBDatabase.prototype.transaction &&
!('objectStoreNames' in IDBTransaction.prototype)) {
return function (storeNames, ...args) {
const tx = func.call(unwrap(this), storeNames, ...args);
transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : storeNames); return wrap(tx);
};
}
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
// with real promises, so each advance methods returns a new promise for the cursor object, or
// undefined if the end of the cursor has been reached.
if (getCursorAdvanceMethods().includes(func)) {
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
func.apply(unwrap(this), args);
return wrap(cursorRequestMap.get(this));
};
}
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
return wrap(func.apply(unwrap(this), args));
};
}
function transformCachableValue(value) {
if (typeof value === 'function')
return wrapFunction(value);
// This doesn't return, it just creates a 'done' promise for the transaction,
// which is later returned for transaction.done (see idbObjectHandler).
if (value instanceof IDBTransaction)
cacheDonePromiseForTransaction(value);
if (instanceOfAny(value, getIdbProxyableTypes()))
return new Proxy(value, idbProxyTraps);
// Return the same value back if we're not going to transform it.
return value;
}
function wrap(value) {
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
if (value instanceof IDBRequest)
return promisifyRequest(value);
// If we've already transformed this value before, reuse the transformed value.
// This is faster, but it also provides object equality.
if (transformCache.has(value))
return transformCache.get(value);
const newValue = transformCachableValue(value);
// Not all types are transformed.
// These may be primitive types, so they can't be WeakMap keys.
if (newValue !== value) {
transformCache.set(value, newValue);
reverseTransformCache.set(newValue, value);
}
return newValue;
}
const unwrap = (value) => reverseTransformCache.get(value);
export { reverseTransformCache as a, instanceOfAny as i, replaceTraps as r, unwrap as u, wrap as w };
WebWorker用code
code:worker.js
var idb=function(e){"use strict";const t=(e,t)=>t.some((t=>e instanceof t));let n,r;const o=new WeakMap,s=new WeakMap,a=new WeakMap,i=new WeakMap,c=new WeakMap;let u={get(e,t,n){if(e instanceof IDBTransaction){if("done"===t)return s.get(e);if("objectStoreNames"===t)return e.objectStoreNames||a.get(e);if("store"===t)return n.objectStoreNames1?void 0:n.objectStore(n.objectStoreNames0)}return p(et)},set:(e,t,n)=>(et=n,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function d(e){u=e(u)}function f(e){return e!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(r||(r=IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey)).includes(e)?function(...t){return e.apply(D(this),t),p(o.get(this))}:function(...t){return p(e.apply(D(this),t))}:function(t,...n){const r=e.call(D(this),t,...n);return a.set(r,t.sort?t.sort():t),p(r)}}function l(e){return"function"==typeof e?f(e):(e instanceof IDBTransaction&&function(e){if(s.has(e))return;const t=new Promise(((t,n)=>{const r=()=>{e.removeEventListener("complete",o),e.removeEventListener("error",s),e.removeEventListener("abort",s)},o=()=>{t(),r()},s=()=>{n(e.error||new DOMException("AbortError","AbortError")),r()};e.addEventListener("complete",o),e.addEventListener("error",s),e.addEventListener("abort",s)}));s.set(e,t)}(e),t(e,n||(n=IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction))?new Proxy(e,u):e)}function p(e){if(e instanceof IDBRequest)return function(e){const t=new Promise(((t,n)=>{const r=()=>{e.removeEventListener("success",o),e.removeEventListener("error",s)},o=()=>{t(p(e.result)),r()},s=()=>{n(e.error),r()};e.addEventListener("success",o),e.addEventListener("error",s)}));return t.then((t=>{t instanceof IDBCursor&&o.set(t,e)})).catch((()=>{})),c.set(t,e),t}(e);if(i.has(e))return i.get(e);const t=l(e);return t!==e&&(i.set(e,t),c.set(t,e)),t}const D=e=>c.get(e);const I="get","getKey","getAll","getAllKeys","count",B="put","add","delete","clear",b=new Map;function v(e,t){if(!(e instanceof IDBDatabase)||t in e||"string"!=typeof t)return;if(b.get(t))return b.get(t);const n=t.replace(/FromIndex$/,""),r=t!==n,o=B.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!o&&!I.includes(n))return;const s=async function(e,...t){const s=this.transaction(e,o?"readwrite":"readonly");let a=s.store;return r&&(a=a.index(t.shift())),(await Promise.all([an(...t),o&&s.done]))0};return b.set(t,s),s}d((e=>({...e,get:(t,n,r)=>v(t,n)||e.get(t,n,r),has:(t,n)=>!!v(t,n)||e.has(t,n)})));const g="continue","continuePrimaryKey","advance",y={},h=new WeakMap,m=new WeakMap,w={get(e,t){if(!g.includes(t))return et;let n=yt;return n||(n=yt=function(...e){h.set(this,m.get(this)t(...e))}),n}};async function*E(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;t=t;const n=new Proxy(t,w);for(m.set(n,t),c.set(n,D(t));t;)yield n,t=await(h.get(n)||t.continue()),h.delete(n)}function L(e,n){return n===Symbol.asyncIterator&&t(e,IDBIndex,IDBObjectStore,IDBCursor)||"iterate"===n&&t(e,IDBIndex,IDBObjectStore)}return d((e=>({...e,get:(t,n,r)=>L(t,n)?E:e.get(t,n,r),has:(t,n)=>L(t,n)||e.has(t,n)}))),e.deleteDB=function(e,{blocked:t}={}){const n=indexedDB.deleteDatabase(e);return t&&n.addEventListener("blocked",(()=>t())),p(n).then((()=>{}))},e.openDB=function(e,t,{blocked:n,upgrade:r,blocking:o,terminated:s}={}){const a=indexedDB.open(e,t),i=p(a);return r&&a.addEventListener("upgradeneeded",(e=>{r(p(a.result),e.oldVersion,e.newVersion,p(a.transaction))})),n&&a.addEventListener("blocked",(()=>n())),i.then((e=>{s&&e.addEventListener("close",(()=>s())),o&&e.addEventListener("versionchange",(()=>o()))})).catch((()=>{})),i},e.unwrap=D,e.wrap=p,e}({}); test code
code:js
import('/api/code/programming-notes/idb/test1.js').then(({demo}) => demo());
code:test1.js
import { openDB } from './with-async-ittr.js';
export async function demo() {
const db = await openDB('Articles', 1, {
JS objectに関数を代入するときに、こんな書き方出来たんだtakker.icon
もしかしてJS classのmethodも同じ原理だったりする?
code:test1.js
upgrade(db) {
// Create a store of objects
const store = db.createObjectStore('articles', {
// The 'id' property of the object will be the key.
keyPath: 'id',
// If it isn't explicitly set, create a value by auto incrementing.
autoIncrement: true,
});
// Create an index on the 'date' property of the objects.
store.createIndex('date', 'date');
},
});
// Add an article:
await db.add('articles', {
title: 'Article 1',
date: new Date('2019-01-01'),
body: '…',
});
// Add multiple articles in one transaction:
{
const tx = db.transaction('articles', 'readwrite');
transactionの終了処理もPromise.allに入れちゃっていいのか?
前2つの処理より先にtx.doneが実行されちゃったらどうするんだろう?
2021-02-21 22:18:32 問題ないっぽい
tx.doneはすべてのtransaction処理が終了した後に解決される
なので前2つの処理が終わるまで解決されることはない
code:test1.js
await Promise.all([
tx.store.add({
title: 'Article 2',
date: new Date('2019-01-01'),
body: '…',
}),
tx.store.add({
title: 'Article 3',
date: new Date('2019-01-02'),
body: '…',
}),
tx.done,
]);
}
// Get all the articles in date order:
console.log(await db.getAllFromIndex('articles', 'date'));
dateの値でデータを絞り込む
code:test1.js
// Add 'And, happy new year!' to all articles on 2019-01-01:
{
const tx = db.transaction('articles', 'readwrite');
const index = tx.store.index('date');
for await (const cursor of index.iterate(new Date('2019-01-01'))) {
const article = { ...cursor.value };
article.body += ' And, happy new year!';
cursor.update(article);
}
await tx.done;
}
}