Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@inexorabletash
Last active September 23, 2016 16:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inexorabletash/280016e79188b6a28247 to your computer and use it in GitHub Desktop.
Save inexorabletash/280016e79188b6a28247 to your computer and use it in GitHub Desktop.
Transactionless IDB API

Transactionless Indexed DB

Status: Initial thought experiment. Feedback welcome.

There are times when Indexed DB's transaction-centric API is too heavyweight:

Idea

  • Allow direct access to object stores and indexes (or rather, the script handles that represent them) off of the connection, and make simple (non-cursor, non-schema-mutation) methods available directly without going through a transaction.
  • Return Promises rather than events, since the responses are simple.

Sample Code

var open = indexedDB.open('db');

// Schema definition process is unchanged by this proposal:
open.onupgradeneeded = function() {
  var db = open.result;
  var store = db.createObjectStore('store', {autoIncrement: true});
  var index = store.createIndex('index', 'id');
  store.put({id: 123});
  store.put({id: 456});
  store.put({foo: 'bar'});
};

// But now...
open.onsuccess = function() {
  var db = open.result;
  
  var store = db.objectStore('store');
  store.get(1).then(r => console.log(r)); // {id: 123}
  store.count().then(r => console.log(r)); // 3

  var index = store.index('index');
  index.count().then(r => console.log(r)); // 2
};

Issues:

  • The naive idea is that 'read' operations are scheduled "as if" a "readonly" transaction were scheduled with the effective store in scope, and that 'write' operations are scheduled "as if" a "readwrite" transaction were scheduled with the effective store in scope. While code targeting a single store would execute in the order written, code targeting multiple stores might not.
  • What about cursors?
  • What about upgrade transactions? Does calling db.objectStore('s').get() return a promise (like normal), or a request (like tx.objectStore('s').get()) or throw?
(function(global){
if ('objectStore' in IDBDatabase.prototype)
return;
function os_shim(method, mode) {
return function() {
var $this = this, $arguments = arguments;
return new Promise(function(resolve, reject) {
var meta = $this._meta;
var db = meta.transaction.db;
var tx = db.transaction(meta.name, mode);
var s = tx.objectStore(meta.name);
var r = s[method].apply(s, $arguments);
if (mode === 'readwrite') {
tx.oncomplete = function() { resolve(r.result); };
tx.onabort = function() { reject(r.error); };
} else {
r.onsuccess = function() { resolve(r.result); };
r.onerror = function() { reject(r.error); };
}
});
};
}
function idx_shim(method) {
return function() {
var $this = this, $arguments = arguments;
return new Promise(function(resolve, reject) {
var meta = $this._meta;
var db = meta.objectStore.transaction.db;
var tx = db.transaction(meta.objectStore.name);
var s = tx.objectStore(meta.objectStore.name);
var i = s.index(meta.name);
var r = i[method].apply(i, $arguments);
r.onsuccess = function() { resolve(r.result); };
r.onerror = function() { reject(r.error); };
});
};
}
function IDBObjectStoreLite(meta) {
this._meta = meta;
this._indexes = new Map();
}
IDBObjectStoreLite.prototype = {
get name() { return this._meta.name; },
get keyPath() { return this._meta.keyPath; },
get indexNames() { return this._meta.indexNames; },
get db() { return this._meta.transaction.db; },
get autoIncrement() { return this._meta.autoIncrement; },
index: function(name) {
name = String(name);
if (!this._indexes.has(name)) {
var meta = this._meta.index(name);
this._indexes.set(name, new IDBIndexLite(this, meta));
}
return this._indexes.get(name);
}
};
['put', 'add', 'delete', 'clear'].forEach(function(method) {
IDBObjectStoreLite.prototype[method] = os_shim(method, 'readwrite');
});
['get', 'getAll', 'getAllKeys', 'count'].forEach(function(method) {
IDBObjectStoreLite.prototype[method] = os_shim(method, 'readonly');
});
function IDBIndexLite(os, meta) {
this._os = os;
this._meta = meta;
}
IDBIndexLite.prototype = {
get name() { return this._meta.name; },
get objectStore() { return this._os; },
get keyPath() { return this._meta.keyPath; },
get multiEntry() { return this._meta.multiEntry; },
get unique() { return this._meta.unique; }
};
['get', 'getKey', 'getAll', 'getAllKeys', 'count'].forEach(function(method) {
IDBIndexLite.prototype[method] = idx_shim(method, 'readonly');
});
IDBDatabase.prototype.objectStore = function(name) {
name = String(name);
this._stores = this._stores || new Map();
if (!this._stores.has(name)) {
var meta = this.transaction(name).objectStore(name);
this._stores.set(name, new IDBObjectStoreLite(meta));
}
return this._stores.get(name);
};
}(self));
partial interface IDBDatabase {
IDBObjectStoreLite objectStore(DOMString name);
};
interface IDBObjectStoreLite {
readonly attribute DOMString name;
readonly attribute any keyPath;
readonly attribute DOMStringList indexNames;
readonly attribute IDBDatabase db;
readonly attribute boolean autoIncrement;
Promise<any> add(any value, optional any key);
Promise<any> put(any value, optional any key);
Promise<any> delete(any key);
Promise<any> clear();
Promise<any> get(optional any query);
Promise<any> getAll(optional any key);
Promise<any> getAllKeys(optional any key);
Promise<any> count(optional any key);
IDBIndexLite index(DOMString name);
};
interface IDBIndexLite {
readonly attribute DOMString name;
readonly attribute IDBObjectStoreLite objectStore;
readonly attribute any keyPath;
readonly attribute boolean multiEntry;
readonly attribute boolean unique;
Promise<any> get(optional any query);
Promise<any> getKey(optional any query);
Promise<any> getAll(optional any key);
Promise<any> getAllKeys(optional any key);
Promise<any> count(optional any key);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment