/indexeddb

A pure Node.js implementation of the async W3C Index DB API.

Primary LanguageJavaScriptMIT LicenseMIT

Actions Status codecov License: MIT

A pure-JavaScript, persistent implementation of IndexedDB.

What Where
Discussion #1
Documentation https://bigeasy.github.io/indexeddb
Source https://github.com/bigeasy/indexeddb
Issues https://github.com/bigeasy/indexeddb/issues
CI https://travis-ci.org/bigeasy/indexeddb
Coverage: https://codecov.io/gh/bigeasy/indexeddb
License: MIT

IndexedDB installs from NPM.

npm install indexeddb

Living README.md

This README.md is also a unit test using the Proof unit test framework. We'll use the Proof okay function to assert out statements in the readme. A Proof unit test generally looks like this.

require('proof')(4, async okay => {
    okay('always okay')
    okay(true, 'okay if true')
    okay(1, 1, 'okay if equal')
    okay({ value: 1 }, { value: 1 }, 'okay if deep strict equal')
})

You can run this unit test yourself to see the output from the various code sections of the readme.

git clone git@github.com:bigeasy/ascension.git
cd ascension
npm install --no-package-lock --no-save
node test/readme.t.js

Status

Most Web Platform Tests pass. Working on this README.md which will have an accurate accounting of WPT and more verbiage around the example. I'm going to defer any description as to how to use the IndexedDB API to the many other resources on the Internet.

Overview

IndexedDB is a pure-JavaScript implementation of the IndxedDB API for Node.js.

IndexedDB exports a single IndexedDB object.

const IndexedDB = require('indexeddb')

Additional requires.

const Destructible = require('destructible')

For the sake of our unit test.

const path = require('path')

Construction.

const destructible = new Destructible('indexeddb.readme.t')

const indexedDB = IndexedDB.create(destructible, path.join(__dirname, 'tmp', 'readme'))

For the sake of the test.

const request = indexedDB.open('test', 1)

request.onupgradeneeded = function (event) {
    const db = request.result
    const store = db.createObjectStore('president', { keyPath: [ 'lastName', 'firstName' ] })
    store.put({ firstName: 'George', lastName: 'Washington' })
    store.put({ firstName: 'John', lastName: 'Adams' })
    store.put({ firstName: 'Thomas', lastName: 'Jefferson' })
}

request.onsuccess = function (event) {
    const db = request.result
    const cursor = db.transaction('president')
                     .objectStore('president')
                     .openCursor()
    const gathered = []
    cursor.onsuccess = function (event) {
        const cursor = event.target.result
        if (cursor != null) {
            gathered.push(cursor.value)
            cursor.continue()
        } else {
            okay(gathered, [{
                firstName: 'John', lastName: 'Adams'
            }, {
                firstName: 'Thomas', lastName: 'Jefferson'
            }, {
                firstName: 'George', lastName: 'Washington'
            }], 'gathered')
            db.close()
            destructible.destroy()
        }
    }
}

Shutdown.

await destructible.promise

Open for Discussion

I am unable to find an implementation of Event/EventTarget on NPM that provided an implementation of the "get the parent" algorithm mentioned in the specification. Without this I was unable to implement progation of error or abort events from the Request to the Transaction to the Database.

At the outset I was unable to find an implementation that implemented the legacyOutputHandlersDidThrowFlag that was added to address a "monkey patch" of the DOM by IndexedDB that went unnoticed for years. Essentially, there is no way to know if a dispatched event generated an error. It doens't concert the UI event model of the DOM, but IndexedDB need to know if an event handler failed so it can abort the transaction.

I extracted the Event/EventTarget implementation from JSDOM because it appeared to have a complete "get the parent." I added the legacyOutputHandlersDidThrowFlag which is simply a flag, so that was simple enough. At that point I thought that their might be a future where this change could get rolled back into JSDOM and this IndexedDB implementation could use the JSDOM event targets as a module.

However, during testing I found that it would not correctly create a list of targets such that the event maintained its target property during bubbling. There appears to be something in the specification that allows for invocation with an explicit EventTarget path with the event target property specified, but the logic of JSDOM does not seem to offer a path where the event is not retargeted if the existing target is not a Node.

With that, the prospect of getting a patch back into JSDOM have dimmed, but the next issue I'm pretty sure we're going to have to accept that this implementation of IndexedDB will have to maintain a separate implementation of JSDOM.

There is a test in the Web Platform Test IndexedDB test suite called X that asserts that a Promise executed in an event handler attached to a request will execute before the next event handler is invoked. This behavior changes if you call dispatchEvent which will dispatch the event synchronously.

It would appear that the only event dispatch implementation in JSDOM is the _dispatch function which is synchronous so all event handlers will be called and there will be no way to clear the microtask queue.

The test will not pass until a I'm able to provide a new implementation of _dispatch that is optionally asynchronous. Which for my future reference I'll say I could implement with Recriprocate.

What strkes me as particularlly peculiar is that there is no mention of an event queue in the Living DOM. The test dictates the desired behavior that is obviously impossible with a synchrnous dispatchEvent and even makes mention of the synchronousness of dispatchEvent in a comment in the code. Searching StackOverflow produces a question — Why is there a difference in the task/microtask execution order when a button is programmatically clicked vs DOM clicked? — that confirms this behavior, but it is not specified with the same sort of detail used to specify the event queues in IndexedDB itself. To understand it fully I'd probably have to read the Chrome or Mozilla source.

Was informed in one thread that you could start with the Simple implementation of EventTarget at MDN, but this simple implementation barely covers the dispatch algorithm as documented in the spec.

I've noticed that the Node.js source goes to great lengths to make private members private using symbols. I've yet to adopt this style of programming myself. It adds a lot of code to declare an identifier before you use it when you can just type it as you need it. If privacy is so dear the language should (and has) add support for private members that can still be referenced merely by typing out an identifier name.

Add a note about the true hack that got event bubbling working, or not working, that is suppressed when bubbles is false.