function(secret, options) {
        if (!secret) {
            throw new ReferenceError('secret must be defined');
        }

        if (typeof secret == 'string') {
            var _secret = secret;
            secret = function(req) {return _secret};
        }

        options = options || {};
        var defaults = {
            cookie: 'jwt-express',
            cookieOptions: {
                httpOnly: true
            },
            cookies: true,
            refresh: true,
            reqProperty: 'jwt',
            revoke: function(jwt) {},
            signOptions: {},
            stales: 900000,
            verify: function(jwt) {return true},
            verifyOptions: {}
        };

        for (var key in defaults) {
            this.options[key] = options[key] !== undefined? options[key]: defaults[key];
        }

        return function(req, res, next) {
            var token;
            if (this.options.cookies) {
                token = req.cookies[this.options.cookie];
            } else if (req.headers.authorization) {
                // Authorization: Bearer abc.abc.abc
                token = req.headers.authorization.split(' ')[1];
            }

            var jwt = new JWT(secret(req), this.options);
            req[this.options.reqProperty] = jwt.verify(token);

            if (jwt.valid && !jwt.stale && jwt.options.refresh) {
                jwt.resign().store(res);
            }

            /**
             * jwt - Creates and signs a new JWT. If cookies are in use, it stores
             *     the JWT in the cookie as well.
             * @param object payload The payload of the JWT
             * @return JWT
             */
            res.jwt = function(payload) {
                var jwt = new JWT(secret(req), this.options);
                return jwt.sign(payload).store(res);
            }.bind(this);

            this.clear = function() {
                if (this.options.cookies) {
                    res.clearCookie(this.options.cookie);
                }
            }.bind(this);

            next();
        }.bind(this);
    }

init - initialize jwt-express
@param string|function secret If secret is a string, that will be used
to verify / sign with. If secret is a function, that function will be
called with the Express HTTP Request as it's first parameter, and must
return a string which will be used to verify / sign with.
@param object options (Optional) The options of jwt-express
cookie:        The name of the cookie (default: 'jwt-express')
cookieOptions: Options to use when storing the cookie
(default: {httpOnly: true})
cookies:       Boolean indicating to use cookies to look for / save the JWTs
or to use the Authorization header to look for the JWTs (default: true)
refresh:       Boolean indicating if the JWT should be refreshed and stored every request
reqProperty:   The property of req to populate (default: 'jwt')
revoke:        The function to call when jwt.revoke() is called
(default: function(jwt) {})
signOptions:   Options to use when signing the JWT (default: {})
stales:        Milliseconds when the jwt will go stale
verify:        Additional function to call when verifying a JWT
(default: function(jwt) {return true})
verifyOptions: Options to use when verifying the JWT (default: {})
@return function middleware


function() {
        return function(req, res, next) {
            var jwt = req[this.options.reqProperty] || {};

            if (!jwt.valid) {
                next(new JWTExpressError('JWT is invalid'));
            } else {
                next();
            }
        }.bind(this);
    }

function
(
)
{
return
function
(
req
,
res
,
next
)
{
var
jwt
=
req
[
this
.
options
.
reqProperty
]
||
{
}
;
if
(
!
jwt
.
valid
)
{
next
(
new
JWTExpressError
(
'JWT is invalid'
)
)
;
}
else
{
next
(
)
;
}
}
.
bind
(
this
)
;
}

valid - requires that a JWT is valid
@return function middleware


function setupComponent (fixture, options) {
    // tear down any existing component instance
    if (this.component) {
      this.component.teardown();
      this.$node.remove();
    }

    if (fixture instanceof jQuery || typeof fixture === 'string') {
      // use the fixture to create component root node
      this.$node = $(fixture).addClass('component-root');
    } else {
      // create an empty component root node
      this.$node = $('<div class="component-root" />');
      options = fixture;
      fixture = null;
    }

    // append component root node to body
    $('body').append(this.$node);

    // normalize options
    options = options === undefined ? {} : options;

    // instantiate component on component root node
    this.component = (new this.Component()).initialize(this.$node, options);
  }

setupComponent
- Assumes it has been called in the context of a jasmine spec.
- Creates a new HTML element and attaches to it an instance of this.Component
- If a fixture is provided, the fixture will serve as the component root.

@param fixture: HTML or jQuery fixture
@param options: component initialization options

setupComponent - Assumes it has been called in the context of a jasmine spec . - Creates a new HTML element and attaches to it an instance of this . Component - If a fixture is provided the fixture will serve as the component root .


function describeModuleFactory (modulePath, specDefinitions) {
    return function () {
      beforeEach(function (done) {
        this.module = null;

        var requireCallback = function (module) {
          this.module = module;
          done();
        }.bind(this);

        require([modulePath], requireCallback);
      });

      specDefinitions.apply(this);
    };
  }

function describeModuleFactory ( modulePath , specDefinitions ) { return function ( ) { beforeEach ( function ( done ) { this . module = null ; var requireCallback = function ( module ) { this . module = module ; done ( ) ; } . bind ( this ) ; require ( [ modulePath ] , requireCallback ) ; } ) ; specDefinitions . apply ( this ) ; } ; }

Load amd module before executing specDefinitions


function consul (options, resilient) {
      defineResilientOptions(params, options)

      return {
        // Incoming traffic middleware
        'in': function inHandler (err, res, next) {
          if (err) return next()

          // resilient.js sometimes calls the middleware function more than once, with the output
          // of the previous invokation; checking here the type of the items in the response to
          // only call `mapServers` with service objects, not URLs (strings)
          if (Array.isArray(res.data) && Object(res.data[0]) === res.data[0]) {
            res.data = mapServers(res.data)
          }

          next()
        },
        // Outgoing traffic middleware
        'out': function outHandler (options, next) {
          options.params = options.params || {}

          if (params.datacenter) {
            options.params.dc = params.datacenter
          }

          if (params.onlyHealthy) {
            options.params.passing = true
          }

          if (params.tag) {
            options.params.tag = params.tag
          }

          next()
        }
      }
    }


function inHandler (err, res, next) {
          if (err) return next()

          // resilient.js sometimes calls the middleware function more than once, with the output
          // of the previous invokation; checking here the type of the items in the response to
          // only call `mapServers` with service objects, not URLs (strings)
          if (Array.isArray(res.data) && Object(res.data[0]) === res.data[0]) {
            res.data = mapServers(res.data)
          }

          next()
        }


function outHandler (options, next) {
          options.params = options.params || {}

          if (params.datacenter) {
            options.params.dc = params.datacenter
          }

          if (params.onlyHealthy) {
            options.params.passing = true
          }

          if (params.tag) {
            options.params.tag = params.tag
          }

          next()
        }

function outHandler ( options , next ) { options . params = options . params || { } if ( params . datacenter ) { options . params . dc = params . datacenter } if ( params . onlyHealthy ) { options . params . passing = true } if ( params . tag ) { options . params . tag = params . tag } next ( ) }


function(categoryId, event) {
    event.preventDefault();
    this.refs.scroller.prepareAnimationSync();
    this.setState({
      mode: 'single',
      selected: categoryId,
      previousScrollPosition: this.refs.scroller.scrollTop,
    }, function() {
      this.refs.scroller.animateAndResetScroll(0, 0);
    });
  }

function ( categoryId , event ) { event . preventDefault ( ) ; this . refs . scroller . prepareAnimationSync ( ) ; this . setState ( { mode : 'single' , selected : categoryId , previousScrollPosition : this . refs . scroller . scrollTop , } , function ( ) { this . refs . scroller . animateAndResetScroll ( 0 , 0 ) ; } ) ; }

When listing all categories and user taps on a given category


function(event) {
    event.preventDefault();
    this.setState({
      mode: 'all',
      selected: null,
    }, function() {
      this.refs.scroller.animateAndResetScroll(0, this.state.previousScrollPosition);
    });
  }

function ( event ) { event . preventDefault ( ) ; this . setState ( { mode : 'all' , selected : null , } , function ( ) { this . refs . scroller . animateAndResetScroll ( 0 , this . state . previousScrollPosition ) ; } ) ; }

When showing a single category and user taps the stack


function Service(displayName, UUID, subtype) {

  if (!UUID) throw new Error("Services must be created with a valid UUID.");

  this.displayName = displayName;
  this.UUID = UUID;
  this.subtype = subtype;
  this.iid = null; // assigned later by our containing Accessory
  this.characteristics = [];
  this.optionalCharacteristics = [];

  // every service has an optional Characteristic.Name property - we'll set it to our displayName
  // if one was given
  // if you don't provide a display name, some HomeKit apps may choose to hide the device.
  if (displayName) {
    // create the characteristic if necessary
    var nameCharacteristic =
      this.getCharacteristic(Characteristic.Name) ||
      this.addCharacteristic(Characteristic.Name);

    nameCharacteristic.setValue(displayName);
  }
}

Service represents a set of grouped values necessary to provide a logical function. For instance, a
"Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the
"current lock state". A particular Service is distinguished from others by its "type", which is a UUID.
HomeKit provides a set of known Service UUIDs defined in HomeKitTypes.js along with a corresponding
concrete subclass that you can instantiate directly to setup the necessary values. These natively-supported
Services are expected to contain a particular set of Characteristics.

Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service,
you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine
a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be
a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype"
property on Service, that can be a string or other string-convertible object that uniquely identifies the
Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'`
for one and `service2.subtype = 'backlight'` for the other.

You can also define custom Services by providing your own UUID for the type that you generate yourself.
Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to
work with these.

@event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { }
Emitted after a change in the value of one of our Characteristics has occurred.

Service represents a set of grouped values necessary to provide a logical function . For instance a Door Lock Mechanism service might contain two values one for the desired lock state and one for the current lock state . A particular Service is distinguished from others by its type which is a UUID . HomeKit provides a set of known Service UUIDs defined in HomeKitTypes . js along with a corresponding concrete subclass that you can instantiate directly to setup the necessary values . These natively - supported Services are expected to contain a particular set of Characteristics .


function Characteristic(displayName, UUID, props) {
  this.displayName = displayName;
  this.UUID  = UUID;
  this.iid   = null; // assigned by our containing Service
  this.value = null;
  this.props = props || {
    format  : null,
    unit    : null,
    minValue: null,
    maxValue: null,
    minStep : null,
    perms   : []
  };
}

function
Characteristic
(
displayName
,
UUID
,
props
)
{
this
.
displayName
=
displayName
;
this
.
UUID
=
UUID
;
this
.
iid
=
;
// assigned by our containing Service
this
.
value
=
;
this
.
props
=
props
||
{
format
:
,
unit
:
,
minValue
:
,
maxValue
:
,
minStep
:
,
perms
:
[
]
}
;
}

Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a
"Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic
to a Service in order to store that value. A particular Characteristic is distinguished from others by its
UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes.js along with a
corresponding concrete subclass.

You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added
to any native or custom Services, but Siri will likely not be able to work with these.

Note that you can get the "value" of a Characteristic by accessing the "value" property directly, but this
is really a "cached value". If you want to fetch the latest value, which may involve doing some work, then
call getValue().

@event 'get' => function(callback(err, newValue), context) { }
Emitted when someone calls getValue() on this Characteristic and desires the latest non-cached
value. If there are any listeners to this event, one of them MUST call the callback in order
for the value to ever be delivered. The `context` object is whatever was passed in by the initiator
of this event (for instance whomever called `getValue`).

@event 'set' => function(newValue, callback(err), context) { }
Emitted when someone calls setValue() on this Characteristic with a desired new value. If there
are any listeners to this event, one of them MUST call the callback in order for this.value to
actually be set. The `context` object is whatever was passed in by the initiator of this change
(for instance, whomever called `setValue`).

@event 'change' => function({ oldValue, newValue, context }) { }
Emitted after a change in our value has occurred. The new value will also be immediately accessible
in this.value. The event object contains the new value as well as the context object originally
passed in by the initiator of this change (if known).

Characteristic represents a particular typed variable that can be assigned to a Service . For instance a Hue Characteristic might store a float value of type arcdegrees . You could add the Hue Characteristic to a Service in order to store that value . A particular Characteristic is distinguished from others by its UUID . HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes . js along with a corresponding concrete subclass .


function migrateDatabase(nativeDatabase, nativeTransaction, schemaDescriptors,
    currentVersion) {
  let descriptorsToProcess = schemaDescriptors.filter((descriptor) => {
    return descriptor.version > currentVersion
  })

  if (!descriptorsToProcess.length) {
    return PromiseSync.resolve(undefined)
  }

  return migrateDatabaseVersion(
    nativeDatabase,
    nativeTransaction,
    descriptorsToProcess[0]
  ).then(() => {
    return migrateDatabase(
      nativeDatabase,
      nativeTransaction,
      descriptorsToProcess,
      descriptorsToProcess[0].version
    )
  })
}

function migrateDatabase ( nativeDatabase , nativeTransaction , schemaDescriptors , currentVersion ) { let descriptorsToProcess = schemaDescriptors . filter ( ( descriptor ) => { return descriptor . version > currentVersion } ) if ( ! descriptorsToProcess . length ) { return PromiseSync . resolve ( undefined ) } return migrateDatabaseVersion ( nativeDatabase , nativeTransaction , descriptorsToProcess [ 0 ] ) . then ( ( ) => { return migrateDatabase ( nativeDatabase , nativeTransaction , descriptorsToProcess , descriptorsToProcess [ 0 ] . version ) } ) }

Processes the schema descriptors to upgrade the database schema to the
greatest version specified.

@param {IDBDatabase} nativeDatabase The native Indexed DB database being
migrated to a higher version.
@param {IDBTransaction} nativeTransaction The native Indexed DB
{@code versionchange} transaction to use to manipulate the data.
@param {(DatabaseSchema|UpgradedDatabaseSchema)[]} schemaDescriptors Schema
descriptors of the database schemas for various versions, sorted in
ascending order by the version number.
@param {number} currentVersion The current version of the database schema.
@return {PromiseSync<undefined>} A promise that resolves when the schema is
upgraded to the greatest version specified in the schema
descriptors.

Processes the schema descriptors to upgrade the database schema to the greatest version specified .


function migrateDatabaseVersion(nativeDatabase, nativeTransaction,
    descriptor) {
  let fetchPromise
  if (descriptor.fetchBefore && descriptor.fetchBefore.length) {
    let fetcher = new RecordFetcher()
    let objectStores = normalizeFetchBeforeObjectStores(descriptor.fetchBefore)
    fetchPromise = fetcher.fetchRecords(nativeTransaction, objectStores)
  } else {
    fetchPromise = PromiseSync.resolve({})
  }

  return fetchPromise.then((recordsMap) => {
    let versionMigrator = new DatabaseVersionMigrator(
      nativeDatabase,
      nativeTransaction,
      descriptor.objectStores
    )

    return versionMigrator.executeMigration(
      descriptor.after || (() => {}),
      recordsMap
    )
  })
}

function migrateDatabaseVersion ( nativeDatabase , nativeTransaction , descriptor ) { let fetchPromise if ( descriptor . fetchBefore && descriptor . fetchBefore . length ) { let fetcher = new RecordFetcher ( ) let objectStores = normalizeFetchBeforeObjectStores ( descriptor . fetchBefore ) fetchPromise = fetcher . fetchRecords ( nativeTransaction , objectStores ) } else { fetchPromise = PromiseSync . resolve ( { } ) } return fetchPromise . then ( ( recordsMap ) => { let versionMigrator = new DatabaseVersionMigrator ( nativeDatabase , nativeTransaction , descriptor . objectStores ) return versionMigrator . executeMigration ( descriptor . after || ( ( ) => { } ) , recordsMap ) } ) }

Performs a single-version database migration to the schema described by the
provided database schema descriptor.

@param {IDBDatabase} nativeDatabase The native Indexed DB database being
migrated to a higher version.
@param {IDBTransaction} nativeTransaction The native Indexed DB
{@code versionchange} transaction to use to manipulate the data.
@param {(DatabaseSchema|UpgradedDatabaseSchema)} descriptor Schema
descriptor of the version to which the database is to be upgraded.
@return {PromiseSync<undefined>} A promise that resolves once the database
has been upgraded to the schema described by the provided schema
descriptor.

Performs a single - version database migration to the schema described by the provided database schema descriptor .


function normalizeFetchBeforeObjectStores(objectStores) {
  return objectStores.map((objectStore) => {
    if (typeof objectStore === "string") {
      return {
        objectStore,
        preprocessor: record => record
      }
    } else if (!objectStore.preprocessor) {
      return {
        objectStore: objectStore.objectStore,
        preprocessor: record => record
      }
    } else {
      return objectStore
    }
  })
}

function normalizeFetchBeforeObjectStores ( objectStores ) { return objectStores . map ( ( objectStore ) => { if ( typeof objectStore === "string" ) { return { objectStore , preprocessor : record => record } } else if ( ! objectStore . preprocessor ) { return { objectStore : objectStore . objectStore , preprocessor : record => record } } else { return objectStore } } ) }

Normalizes the provided array of object store fetch descriptors to process
before upgrading the database schema.

@param {(string|{objectStore: string, preprocessor: function(*, (number|string|Date|Array)): (*|UpgradedDatabaseSchema.SKIP_RECORD|UpgradedDatabaseSchema.DELETE_RECORD)=})[]} objectStores
The names of object stores that should have their records fetch or
(possibly partially filled) object store fetch descriptors, mixed in
an array.
@return {{objectStore: string, preprocessor: function(*, (number|string|Date|Array)): (*|UpgradedDatabaseSchema.SKIP_RECORD|UpgradedDatabaseSchema.DELETE_RECORD)}[]}
Normalized object store fetch descriptors.

Normalizes the provided array of object store fetch descriptors to process before upgrading the database schema .


function checkSchemaDescriptorTypes(schemaDescriptors) {
  let onlyPlainObjects = schemaDescriptors.every((descriptor) => {
    return descriptor.constructor === Object
  })
  if (onlyPlainObjects) {
    return
  }

  if (!(schemaDescriptors[0] instanceof DatabaseSchema)) {
    throw new TypeError("The schema descriptor of the lowest described " +
        `database version (${schemaDescriptors[0].version}) must be a ` +
        "DatabaseSchema instance, or all schema descriptors must be plain " +
        "objects")
  }
  schemaDescriptors.slice(1).forEach((descriptor) => {
    if (!(descriptor instanceof UpgradedDatabaseSchema)) {
      throw new TypeError("The schema descriptors of the upgraded database " +
          "versions must be UpgradedDatabaseSchema instances, but the " +
          `provided descriptor of version ${descriptor.version} was not an ` +
          "UpgradedDatabaseSchema instance, or all schema descriptors must " +
          "be plain objects")
    }
  })
}

Validates the types of the provided schema descriptors.

@param {((DatabaseSchema|UpgradedDatabaseSchema)[]|Object[])} schemaDescriptors
The database schemas for database versions to validate, sorted by
version number in the ascending order.
@throws {TypeError} Thrown if the schema descriptors are of invalid type.

Validates the types of the provided schema descriptors .


function list(storage, keyRange, filter, direction, unique, pageSize,
    storageFactory) {
  return new Promise((resolve, reject) => {
    let items = []

    storage.createCursorFactory(keyRange, direction)((cursor) => {
      if (!filter || filter(cursor.record, cursor.primaryKey, cursor.key)) {
        if (items.length === pageSize) {
          finalize(true, cursor.key, cursor.primaryKey)
          return
        } else {
          items.push(cursor.record)
        }
      }

      cursor.continue()
    }).then(() => finalize(false, null, null)).catch(error => reject(error))

    function finalize(hasNextPage, nextKey, nextPrimaryKey) {
      resolve(new RecordList(items, storageFactory, nextKey, nextPrimaryKey,
          direction, unique, filter, pageSize, hasNextPage))
    }
  })
}

function list ( storage , keyRange , filter , direction , unique , pageSize , storageFactory ) { return new Promise ( ( resolve , reject ) => { let items = [ ] storage . createCursorFactory ( keyRange , direction ) ( ( cursor ) => { if ( ! filter || filter ( cursor . record , cursor . primaryKey , cursor . key ) ) { if ( items . length === pageSize ) { finalize ( true , cursor . key , cursor . primaryKey ) return } else { items . push ( cursor . record ) } } cursor . continue ( ) } ) . then ( ( ) => finalize ( false , null , null ) ) . catch ( error => reject ( error ) ) function finalize ( hasNextPage , nextKey , nextPrimaryKey ) { resolve ( new RecordList ( items , storageFactory , nextKey , nextPrimaryKey , direction , unique , filter , pageSize , hasNextPage ) ) } } ) }

Creates a promise that resolves to a record list containing the first page
of records matching the provided filter.

@param {AbstractReadOnlyStorage} storage The current storage accessor - will
be used to fetch the first page of records.
@param {(undefined|IDBKeyRange)} keyRange The key range to use for the first
page or records.
@param {(undefined|function(*, (number|string|Date|Array), (number|string|Date|Array)): boolean)} filter
The filter function restricting the records that will be listed.
If a function is provided, the first argument will be set to the
record, the second argument will be set to the primary key of the
record, and the third argument will be set to the key referencing the
record (the primary key if traversing an object store).
@param {CursorDirection} direction The direction in which the records in the
storage should be listed.
@param {boolean} unique When {@code true}, the keys by which the records are
organized in the store are always unique for each record.
@param {number} pageSize The maximum number of records per page. Must be a
positive integer.
@param {function(): AbstractReadOnlyStorage} storageFactory A function that
creates a new read-only transaction and returns this storage accessor
each time it is invoked.
@return {Promise<RecordList<*>>} A promise that resolves to a record list of
the fetched records matching the filter.

Creates a promise that resolves to a record list containing the first page of records matching the provided filter .


function normalizeCompoundObjectKey(keyPaths, key) {
  let normalizedKey = []

  keyPaths.forEach((keyPath) => {
    let keyValue = key

    keyPath.split(".").forEach((fieldName) => {
      if (!keyValue.hasOwnProperty(fieldName)) {
        throw new Error(`The ${keyPath} key path is not defined in the ` +
            "provided compound key")
      }

      keyValue = keyValue[fieldName]
    })

    normalizedKey.push(keyValue)
  })

  return normalizedKey
}

function
normalizeCompoundObjectKey
(
keyPaths
,
key
)
{
let
normalizedKey
=
[
]
keyPaths
.
forEach
(
(
keyPath
)
=>
{
let
keyValue
=
key
keyPath
.
split
(
"."
)
.
forEach
(
(
fieldName
)
=>
{
if
(
!
keyValue
.
hasOwnProperty
(
fieldName
)
)
{
throw
new
Error
(
`
${
keyPath
}
`
+
"provided compound key"
)
}
keyValue
=
keyValue
[
fieldName
]
}
)
normalizedKey
.
push
(
keyValue
)
}
)
return
normalizedKey
}

Normalizes the provided compound key represented as an object into a
compound key representation compatible with the Indexed DB.

@param {string[]} keyPaths The key paths of this storage.
@param {Object} key The compound key to normalize for use with the Indexed
DB.
@return {Array<(number|string|Date|Array)>} Normalized compound key.

Normalizes the provided compound key represented as an object into a compound key representation compatible with the Indexed DB .


function iterateCursor(request, cursorConstructor, recordCallback) {
  return new PromiseSync((resolve, reject) => {
    let traversedRecords = 0
    let canIterate = true

    request.onerror = () => reject(request.error)
    request.onsuccess = () => {
      if (!canIterate) {
        console.warn("Cursor iteration was requested asynchronously, " +
            "ignoring the new cursor position")
        return
      }

      if (!request.result) {
        resolve(traversedRecords)
        return
      }

      traversedRecords++

      let iterationRequested = handleCursorIteration(
        request,
        cursorConstructor,
        recordCallback,
        reject
      )

      if (!iterationRequested) {
        canIterate = false
        resolve(traversedRecords)
      }
    }
  })
}

Iterates the cursor to which the provided Indexed DB request resolves. The
method will iterate the cursor over the records in this storage within the
range specified when the cursor was opened until the provided callback does
not request iterating to the next record or the last matching record is
reached.

@param {IDBRequest} request Indexed DB request that resolves to a cursor
every time the cursor iterates to a record.
@param {function(new: ReadOnlyCursor, IDBRequest, function(), function(IDBRequest): PromiseSync)} cursorConstructor
Constructor of the cursor class to use to wrap the native IDBRequest
producing the native IndexedDB cursor.
@param {function(ReadOnlyCursor)} recordCallback The callback to execute,
passing a high-level cursor instance pointing to the current record
in each iteration of the cursor.
@return {PromiseSync<number>} A promise that resolves to the number of
records the cursor traversed.

Iterates the cursor to which the provided Indexed DB request resolves . The method will iterate the cursor over the records in this storage within the range specified when the cursor was opened until the provided callback does not request iterating to the next record or the last matching record is reached .


function handleCursorIteration(request, cursorConstructor, recordCallback,
    reject) {
  let iterationRequested = false
  let cursor = new cursorConstructor(request, () => {
    iterationRequested = true
  }, (subRequest) => {
    return PromiseSync.resolve(subRequest).catch((error) => {
      reject(error)
      throw error
    })
  })

  try {
    recordCallback(cursor)
  } catch (error) {
    iterationRequested = false
    reject(error)
  }

  return iterationRequested
}

function handleCursorIteration ( request , cursorConstructor , recordCallback , reject ) { let iterationRequested = false let cursor = new cursorConstructor ( request , ( ) => { iterationRequested = true } , ( subRequest ) => { return PromiseSync . resolve ( subRequest ) . catch ( ( error ) => { reject ( error ) throw error } ) } ) try { recordCallback ( cursor ) } catch ( error ) { iterationRequested = false reject ( error ) } return iterationRequested }

Handles a single iteration of a Indexed DB cursor iterating the records in
the storage.

@param {IDBRequest} request Indexed DB request resolved to a cursor.
@param {function(new: ReadOnlyCursor, IDBRequest, function(), function(IDBRequest): PromiseSync)} cursorConstructor
Constructor of the high-level cursor implementation to use.
@param {function(ReadOnlyCursor)} recordCallback The callback to execute,
passing a high-level cursor instance pointing to the current record.
@param {function(Error)} reject Callback to call if any sub-operation
triggered by the callback results in an error.
@return {boolean} {@code true} if the cursor should iterate to the next
record.

Handles a single iteration of a Indexed DB cursor iterating the records in the storage .


function fetchAllRecords(transaction, objectStores) {
  return PromiseSync.all(objectStores.map((descriptor) => {
    return fetchRecords(
      transaction.getObjectStore(descriptor.objectStore),
      descriptor.preprocessor
    )
  })).then((fetchedRecords) => {
    let recordsMap = {}

    for (let i = 0; i < objectStores.length; i++) {
      recordsMap[objectStores[i].objectStore] = fetchedRecords[i]
    }

    return recordsMap
  })
}

function fetchAllRecords ( transaction , objectStores ) { return PromiseSync . all ( objectStores . map ( ( descriptor ) => { return fetchRecords ( transaction . getObjectStore ( descriptor . objectStore ) , descriptor . preprocessor ) } ) ) . then ( ( fetchedRecords ) => { let recordsMap = { } for ( let i = 0 ; i < objectStores . length ; i ++ ) { recordsMap [ objectStores [ i ] . objectStore ] = fetchedRecords [ i ] } return recordsMap } ) }

Fetches all records from the specified object stores using the provided
read-write transaction.

@param {Transaction} transaction The read-write transaction to use to access
the object stores.
@param {(string|{objectStore: string, preprocessor: function(*, (number|string|Date|Array)): (*|UpgradedDatabaseSchema.SKIP_RECORD|UpgradedDatabaseSchema.DELETE_RECORD)=})[]} objectStores
The names of object stores that should have their records fetch or
(possibly partially filled) object store fetch descriptors, mixed in
an array.
@return {PromiseSync<Object<string, {key: (number|string|Date|Array), record: *}[]>>}
A promise that resolves once all the records have been fetched and
the records marked for deletion were deleted.
The promise resolves to a map of object store names to the records
fetched from the object store, except for the records marked for
skipping or deletion.

Fetches all records from the specified object stores using the provided read - write transaction .


function fetchRecords(objectStore, preprocessor) {
  return new PromiseSync((resolve, reject) => {
    let records = []

    objectStore.openCursor(null, CursorDirection.NEXT, (cursor) => {
      let primaryKey = cursor.primaryKey
      if (primaryKey instanceof Object) {
        Object.freeze(primaryKey)
      }

      let preprocessedRecord = preprocessor(cursor.record, primaryKey)
      if (preprocessedRecord === UpgradedDatabaseSchema.DELETE_RECORD) {
        cursor.delete()
        cursor.continue()
        return
      } else if (preprocessedRecord !== UpgradedDatabaseSchema.SKIP_RECORD) {
        records.push({
          key: primaryKey,
          record: preprocessedRecord
        })
      } else {
        // SKIP_RECORD returned, do nothing
      }

      cursor.continue()
    }).then(() => resolve(records)).catch(error => reject(error))
  })
}

function
fetchRecords
(
objectStore
,
preprocessor
)
{
return
new
PromiseSync
(
(
resolve
,
reject
)
=>
{
let
records
=
[
]
objectStore
.
openCursor
(
,
CursorDirection
.
NEXT
,
(
cursor
)
=>
{
let
primaryKey
=
cursor
.
primaryKey
if
(
primaryKey
instanceof
Object
)
{
Object
.
freeze
(
primaryKey
)
}
let
preprocessedRecord
=
preprocessor
(
cursor
.
record
,
primaryKey
)
if
(
preprocessedRecord
===
UpgradedDatabaseSchema
.
DELETE_RECORD
)
{
cursor
.
delete
(
)
cursor
.
continue
(
)
return
}
else
if
(
preprocessedRecord
!==
UpgradedDatabaseSchema
.
SKIP_RECORD
)
{
records
.
push
(
{
key
:
primaryKey
,
record
:
preprocessedRecord
}
)
}
else
{
// SKIP_RECORD returned, do nothing
}
cursor
.
continue
(
)
}
)
.
then
(
(
)
=>
resolve
(
records
)
)
.
catch
(
error
=>
reject
(
error
)
)
}
)
}

Extracts all records from the provided object store and preprocess them
using the provided preprocessor.

The method traverses the records of the object store in ascending order of
their primary keys, deleting the records for which the preprocessor returns
the {@linkcode UpgradedDatabaseSchema.DELETE_RECORD} before traversing to
the next record.

@param {ObjectStore} objectStore The read-write accessor the object store
from which the records should be read.
@param {function(*, (number|string|Date|Array)): (*|UpgradedDatabaseSchema.SKIP_RECORD|UpgradedDatabaseSchema.DELETE_RECORD)} preprocessor
The callback to call on each record. The value returned by it will be
stored in the resulting record array instead of the original record.
The record will not be included in the resulting record array if the
preprocessor returns {@linkcode UpgradedDatabaseSchema.SKIP_RECORD}
or {@linkcode UpgradedDatabaseSchema.DELETE_RECORD}.
@return {PromiseSync<{key: (number|string|Date|Array), record: *}[]>} A
promise that resolves once all records in the object store have been
traversed. The promise will resolve to an array of the records
processed by the provided record preprocessor, in the order they
were traversed, and not containing the records that the preprocessor
marked as to be skipped or deleted.

Extracts all records from the provided object store and preprocess them using the provided preprocessor .


function writeFileP (outputPath, data, cb) {
    outputPath = abs(outputPath);
    let dirname = path.dirname(outputPath);
    mkdirp(dirname, err => {
        if (err) { return cb(err); }
        let str = data;
        if (typpy(data, Array) || typpy(data, Object)) {
            str = JSON.stringify(data, null, 2);
        }
        fs.writeFile(outputPath, str, err => cb(err, data));
    });
}

function writeFileP ( outputPath , data , cb ) { outputPath = abs ( outputPath ) ; let dirname = path . dirname ( outputPath ) ; mkdirp ( dirname , err => { if ( err ) { return cb ( err ) ; } let str = data ; if ( typpy ( data , Array ) || typpy ( data , Object ) ) { str = JSON . stringify ( data , null , 2 ) ; } fs . writeFile ( outputPath , str , err => cb ( err , data ) ) ; } ) ; }

writeFileP
Create the directory structure and then create the file.

@name writeFileP
@function
@param {String} outputPath The path to the file you want to create.
@param {String|Object} data The file content. If an Array/Object is provided, it will be stringified.
@param {Function} cb The callback function.

writeFileP Create the directory structure and then create the file .


function runTransaction(transaction, objectStoreNames, transactionOperations) {
  let callbackArguments = objectStoreNames.map((objectStoreName) => {
    return transaction.getObjectStore(objectStoreName)
  })
  callbackArguments.push(() => transaction.abort())

  let resultPromise = transactionOperations(...callbackArguments)
  return Promise.resolve(resultPromise).then((result) => {
    return transaction.completionPromise.then(() => result)
  })
}

function runTransaction ( transaction , objectStoreNames , transactionOperations ) { let callbackArguments = objectStoreNames . map ( ( objectStoreName ) => { return transaction . getObjectStore ( objectStoreName ) } ) callbackArguments . push ( ( ) => transaction . abort ( ) ) let resultPromise = transactionOperations ( ... callbackArguments ) return Promise . resolve ( resultPromise ) . then ( ( result ) => { return transaction . completionPromise . then ( ( ) => result ) } ) }

Runs the provided transaction operations on the specified object stores
obtained from the provided transaction.

The function returns a promise resolved when the transaction completes.

@param {ReadOnlyTransaction} transaction The transaction from which the
object stores will be retrieved. The returned promise will resolve
when this transaction is completed.
@param {string[]} objectStoreNames The names of the object stores to pass to the
{@linkcode transactionOperations} callback.
@param {function(...ReadOnlyObjectStore, function()): PromiseSync<*>} transactionOperations
The callback containing the operations on the object stores in the
transaction.
The callback will receive the requested object stores as arguments,
followed by a callback for aborting the transaction.
The callback should return a synchronous promise (a return value of
one of the data-related methods of object stores and their indexes),
the value to which the returned promise resolves will be the result
value of the promise returned by this function.
@return {Promise<*>} A promise that resolves when the transaction is
completed. The promise will resolve to the value to which resolved
the promise returned by the {@linkcode transactionOperations}
callback.

Runs the provided transaction operations on the specified object stores obtained from the provided transaction .


function toNativeCursorDirection(direction, unique) {
  if (typeof direction === "string") {
    if (CURSOR_DIRECTIONS.indexOf(direction.toUpperCase()) === -1) {
      throw new Error("When using a string as cursor direction, use NEXT " +
          `or PREVIOUS, ${direction} provided`);
    }
  } else {
    direction = direction.value
  }

  let cursorDirection = direction.toLowerCase().substring(0, 4)
  if (unique) {
    cursorDirection += "unique"
  }

  return cursorDirection
}

Returns the cursor direction to use with the native Indexed DB API.

@param {(CursorDirection|string)=} direction The direction in which the
cursor will traverse the records. Use either the
{@code CursorDirection.*} constants, or strings {@code "NEXT"} and
{@code "PREVIOUS"} (or {@code "PREV"} for short). The letter case
used in the strings does not matter.
@param {boolean=} unique When {@code true}, it cursor will skip over the
records stored with the same index key value.
@return {string} The cursor direction compatible with the native Indexed DB
API.

Returns the cursor direction to use with the native Indexed DB API .


function createIndex(objectStore, indexSchema) {
  let indexNames = Array.from(objectStore.indexNames)

  if (indexNames.indexOf(indexSchema.name) !== -1) {
    return
  }

  objectStore.createIndex(indexSchema.name, indexSchema.keyPath, {
    unique: indexSchema.unique,
    multiEntry: indexSchema.multiEntry
  })
}

function createIndex ( objectStore , indexSchema ) { let indexNames = Array . from ( objectStore . indexNames ) if ( indexNames . indexOf ( indexSchema . name ) !== - 1 ) { return } objectStore . createIndex ( indexSchema . name , indexSchema . keyPath , { unique : indexSchema . unique , multiEntry : indexSchema . multiEntry } ) }

Creates a new index in the provided object store according to the provided
index schema.

@param {IDBObjectStore} objectStore The native Indexed DB object store.
@param {IndexSchema} indexSchema The schema of the index to create.

Creates a new index in the provided object store according to the provided index schema .


function fetchNextPage(storageFactory, keyRange, cursorDirection, unique,
    firstPrimaryKey, filter, pageSize) {
  let storage = storageFactory()

  let nextItems = []

  return new Promise((resolve, reject) => {
    let idb = idbProvider()
    let cursorFactory = storage.createCursorFactory(
      keyRange,
      cursorDirection,
      unique
    )

    cursorFactory((cursor) => {
      if (!unique) {
        let shouldSkip =
          (
            (cursorDirection === CursorDirection.NEXT) &&
            (idb.cmp(firstPrimaryKey, cursor.primaryKey) > 0)
          ) || (
            (cursorDirection === CursorDirection.PREVIOUS) &&
            (idb.cmp(firstPrimaryKey, cursor.primaryKey) < 0)
          )

        if (shouldSkip) {
          cursor.continue()
          return
        }
      }

      if (!filter || filter(cursor.record, cursor.primaryKey, cursor.key)) {
        if (nextItems.length === pageSize) {
          finalize(true, cursor.key, cursor.primaryKey)
          return
        } else {
          nextItems.push(cursor.record)
        }
      }

      cursor.continue()
    }).then(() => finalize(false, null, null)).catch(error => reject(error))

    function finalize(hasNextPage, nextKey, nextPrimaryKey) {
      resolve(new RecordList(nextItems, storageFactory, nextKey,
          nextPrimaryKey, cursorDirection, unique, filter, pageSize,
          hasNextPage))
    }
  })
}

function fetchNextPage ( storageFactory , keyRange , cursorDirection , unique , firstPrimaryKey , filter , pageSize ) { let storage = storageFactory ( ) let nextItems = [ ] return new Promise ( ( resolve , reject ) => { let idb = idbProvider ( ) let cursorFactory = storage . createCursorFactory ( keyRange , cursorDirection , unique ) cursorFactory ( ( cursor ) => { if ( ! unique ) { let shouldSkip = ( ( cursorDirection === CursorDirection . NEXT ) && ( idb . cmp ( firstPrimaryKey , cursor . primaryKey ) > 0 ) ) || ( ( cursorDirection === CursorDirection . PREVIOUS ) && ( idb . cmp ( firstPrimaryKey , cursor . primaryKey ) < 0 ) ) if ( shouldSkip ) { cursor . continue ( ) return } } if ( ! filter || filter ( cursor . record , cursor . primaryKey , cursor . key ) ) { if ( nextItems . length === pageSize ) { finalize ( true , cursor . key , cursor . primaryKey ) return } else { nextItems . push ( cursor . record ) } } cursor . continue ( ) } ) . then ( ( ) => finalize ( false , null , null ) ) . catch ( error => reject ( error ) ) function finalize ( hasNextPage , nextKey , nextPrimaryKey ) { resolve ( new RecordList ( nextItems , storageFactory , nextKey , nextPrimaryKey , cursorDirection , unique , filter , pageSize , hasNextPage ) ) } } ) }

Fetches the next page of records in a new ready-only transaction and
resolves into a record list containing the fetched records.

@param {function(): AbstractReadOnlyStorage} storageFactory A function that
creates a new read-only transaction and returns this storage accessor
each time it is invoked.
@param {IDBKeyRange} keyRange The key range to use when opening the cursor
in order to skip most of the records traversed previously.
@param {CursorDirection} cursorDirection The direction in which the records
of the storage are traversed.
@param {boolean} unique Set to {@code true} if the key used by the storage
to organize records has a unique value for each record.
@param {(number|string|Date|Array)} firstPrimaryKey The primary key of the
first record to include to the result.
@param {(undefined|function(*, (number|string|Date|Array), (number|string|Date|Array)): boolean)} filter
The filter function restricting the records that will be listed.
If a function is provided, the first argument will be set to the
record, the second argument will be set to the primary key of the
record, and the third argument will be set to the key referencing the
record (the primary key if traversing an object store).
@param {number} pageSize The maximum number of records per page. Must be a
positive integer.
@return {Promise<RecordList<*>>} A promise that resolves to the next page of
records.

Fetches the next page of records in a new ready - only transaction and resolves into a record list containing the fetched records .


function executeEventListeners(listeners, ...parameters) {
  listeners.forEach((listener) => {
    try {
      listener.apply(null, parameters)
    } catch (error) {
      console.error("An event listener threw an error", error)
    }
  })
}

function
executeEventListeners
(
listeners
,
...
parameters
)
{
listeners
.
forEach
(
(
listener
)
=>
{
try
{
listener
.
apply
(
,
parameters
)
}
catch
(
error
)
{
console
.
error
(
"An event listener threw an error"
,
error
)
}
}
)
}

Executes the provided event listeners with the provided parameters. Any
errors thrown by the executed event listeners will be caught and logged to
the console, and then the remaining event listeners will be executed.

@param {function(...*)[]} listeners The event listeners to execute.
@param {...*} parameters The parameters to pass to the event listeners as
arguments.

Executes the provided event listeners with the provided parameters . Any errors thrown by the executed event listeners will be caught and logged to the console and then the remaining event listeners will be executed .


function resolve(instance, newState, value) {
  if (instance[FIELDS.state] !== STATE.PENDING) {
    return
  }

  instance[FIELDS.state] = newState
  instance[FIELDS.value] = value

  let listeners
  if (newState === STATE.RESOLVED) {
    listeners = instance[FIELDS.fulfillListeners]
  } else {
    listeners = instance[FIELDS.errorListeners]
  }

  for (let listener of listeners) {
    listener()
  }
}

function resolve ( instance , newState , value ) { if ( instance [ FIELDS . state ] !== STATE . PENDING ) { return } instance [ FIELDS . state ] = newState instance [ FIELDS . value ] = value let listeners if ( newState === STATE . RESOLVED ) { listeners = instance [ FIELDS . fulfillListeners ] } else { listeners = instance [ FIELDS . errorListeners ] } for ( let listener of listeners ) { listener ( ) } }

Resolves the provided promise to the specified state and result value. This
function has no effect if the provided promise has already been resolved.

@param {PromiseSync} instance The promise to resolve.
@param {Object} newState One of the {@code STATE.*} constants representing
the new state of the promise. Must not be {@code STATE.PENDING}.
@param {(*|Error)} value The new result value of the promise.

Resolves the provided promise to the specified state and result value . This function has no effect if the provided promise has already been resolved .


function runQuery(cursorFactory, filter, comparator, offset, limit, callback) {
  let records = []
  let recordIndex = -1

  return cursorFactory((cursor) => {
    if (!filter && offset && ((recordIndex + 1) < offset)) {
      recordIndex = offset - 1
      cursor.advance(offset)
      return
    }

    let primaryKey = cursor.primaryKey
    if (filter && !filter(cursor.record, primaryKey)) {
      cursor.continue()
      return
    }

    if (comparator) {
      insertSorted(records, cursor.record, primaryKey, comparator)
      if (offset || limit) {
        if (records.length > (offset + limit)) {
          records.pop()
        }
      }

      cursor.continue()
      return
    }

    recordIndex++
    if (recordIndex < offset) {
      cursor.continue()
      return
    }

    callback(cursor.record, primaryKey)

    if (!limit || ((recordIndex + 1) < (offset + limit))) {
      cursor.continue()
    }
  }).then(() => {
    if (!comparator) {
      return
    }

    records = records.slice(offset)
    for (let { record, primaryKey } of records) {
      callback(record, primaryKey)
    }
  })
}

function runQuery ( cursorFactory , filter , comparator , offset , limit , callback ) { let records = [ ] let recordIndex = - 1 return cursorFactory ( ( cursor ) => { if ( ! filter && offset && ( ( recordIndex + 1 ) < offset ) ) { recordIndex = offset - 1 cursor . advance ( offset ) return } let primaryKey = cursor . primaryKey if ( filter && ! filter ( cursor . record , primaryKey ) ) { cursor . continue ( ) return } if ( comparator ) { insertSorted ( records , cursor . record , primaryKey , comparator ) if ( offset || limit ) { if ( records . length > ( offset + limit ) ) { records . pop ( ) } } cursor . continue ( ) return } recordIndex ++ if ( recordIndex < offset ) { cursor . continue ( ) return } callback ( cursor . record , primaryKey ) if ( ! limit || ( ( recordIndex + 1 ) < ( offset + limit ) ) ) { cursor . continue ( ) } } ) . then ( ( ) => { if ( ! comparator ) { return } records = records . slice ( offset ) for ( let { record , primaryKey } of records ) { callback ( record , primaryKey ) } } ) }

Executes the specified query using the provided cursor factory.

Note that the callback will be executed on the records only after all
records have been populated if the comparator if a function so the records
are passed to the callback in the right order.

@param {function(function(ReadyOnlyCursor)): number} cursorFactory The
cursor factory to use to create a cursor for executing the query.
@param {?function(*, (number|string|Date|Array)): boolean} filter Optional
custom filter callback.
@param {?function(*, *): number} comparator Optional record comparator to
use to sort the records in the result.
@param {number} offset The index of the first record to include in the
result. The records are numbered from {@code 0}, the offset must be
a non-negative integer.
@param {?number} limit The maximum number of records to return as a result.
The limit must be a positive integer, or {@code null} if no limit
should be imposed.
@param {function(*, (number|string|Date|Array))} callback Callback to
execute on every iterated record that matches the query.
@return {PromiseSync<undefined>} A promise that resolves when the query has
been executed.

Executes the specified query using the provided cursor factory .


function insertSorted(records, record, primaryKey, comparator) {
  let index = findInsertIndex(records, record, comparator)
  records.splice(index, 0, {
    record,
    primaryKey
  })
}

function insertSorted ( records , record , primaryKey , comparator ) { let index = findInsertIndex ( records , record , comparator ) records . splice ( index , 0 , { record , primaryKey } ) }

Inserts the provided record into the sorted array of records and their
primary keys, keeping it sorted.

@param {{record: *, primaryKey: (number|string|Date|Array)}[]} records The
array of records into which the provided record should be inserted.
@param {*} record The record to insert into the records array.
@param {(number|string|Date|Array)} primaryKey The primary key of the
record.
@param {function(*, *): number} comparator Record comparator by which the
array is sorted. The comparator is a standard comparator function
compatible with the {@linkcode Array.prototype.sort} method.

Inserts the provided record into the sorted array of records and their primary keys keeping it sorted .


function findInsertIndex(records, record, comparator) {
  if (!records.length) {
    return 0
  }

  if (records.length === 1) {
    let comparison = comparator(records[0].record, record)
    return (comparison > 0) ? 0 : 1
  }

  let comparison = comparator(records[0].record, record)
  if (comparison > 0) {
    return 0
  }

  let bottom = 1
  let top = records.length - 1

  while (bottom <= top) {
    let pivotIndex = Math.floor((bottom + top) / 2)

    let comparison = comparator(records[pivotIndex].record, record)
    if (comparison > 0) {
      let previousElement = records[pivotIndex - 1].record
      if (comparator(previousElement, record) <= 0) {
        return pivotIndex
      }

      top = pivotIndex - 1
    } else {
      bottom = pivotIndex + 1
    }
  }

  return records.length
}

function findInsertIndex ( records , record , comparator ) { if ( ! records . length ) { return 0 } if ( records . length === 1 ) { let comparison = comparator ( records [ 0 ] . record , record ) return ( comparison > 0 ) ? 0 : 1 } let comparison = comparator ( records [ 0 ] . record , record ) if ( comparison > 0 ) { return 0 } let bottom = 1 let top = records . length - 1 while ( bottom <= top ) { let pivotIndex = Math . floor ( ( bottom + top ) / 2 ) let comparison = comparator ( records [ pivotIndex ] . record , record ) if ( comparison > 0 ) { let previousElement = records [ pivotIndex - 1 ] . record if ( comparator ( previousElement , record ) <= 0 ) { return pivotIndex } top = pivotIndex - 1 } else { bottom = pivotIndex + 1 } } return records . length }

Uses the binary search algorithm to find the index at which the specified
record should be inserted into the specified array of records to keep the
array sorted according to the provided comparator.

@param {{record: *, primaryKey: (number|string|Date|Array)}[]} records The
array of records into which the provided record should be inserted.
@param {*} record The record to insert into the records array.
@param {function(*, *): number} comparator Record comparator by which the
array is sorted. The comparator is a standard comparator function
compatible with the {@linkcode Array.prototype.sort} method.
@return {number} The index at which the record should be inserted to keep
the array of records sorted.

Uses the binary search algorithm to find the index at which the specified record should be inserted into the specified array of records to keep the array sorted according to the provided comparator .


function prepareQuery(thisStorage, filter, order) {
  order = normalizeKeyPath(order)

  let expectedSortingDirection = order[0].charAt(0) === "!"
  let canSortingBeOptimized
  canSortingBeOptimized = canOptimizeSorting(expectedSortingDirection, order)

  let storages = new Map()
  storages.set(normalizeKeyPath(thisStorage.keyPath), {
    storage: thisStorage,
    score: 1 // traversing storage is faster than fetching records by index
  })

  for (let indexName of thisStorage.indexNames) {
    let index = thisStorage.getIndex(indexName)
    if (!index.multiEntry) {
      storages.set(normalizeKeyPath(index.keyPath), {
        storage: index,
        score: 0
      })
    }
  }

  let simplifiedOrderFieldPaths = simplifyOrderingFieldPaths(order)

  if (canSortingBeOptimized) {
    prepareSortingOptimization(storages, simplifiedOrderFieldPaths)
  }

  prepareFilteringOptimization(storages, filter)

  return chooseStorageForQuery(
    storages,
    order,
    simplifiedOrderFieldPaths,
    canSortingBeOptimized,
    expectedSortingDirection
  )
}

Prepares the query that uses the specified filter and record order for
execution on this storage.

The method attempts uses a heuristic to determine whether the query should
be run directly on this object store or on one of its indexes for maximum
performance.

If the method cannot optimize sorting and filtering, it prefers optimizing
sorting to optimizing filtering, as optimizing sorting allows the query
executor to skip the requested amount of records and the records following
the last record that fills the required amount of records.

If the sorting cannot be optimized, the method attempts to optimize
filtering so that the matching records can be selected by providing a key
range to cursor, so that only the matching records will be traversed.

The method constructs the filtering predicate and sorting comparator if
neither the sorting nor filtering can be optimized, and prepares the query
to be executed directly on the object store.

@param {ReadOnlyObjectStore} thisStorage This object store.
@param {?(undefined|number|string|Date|Array|IDBKeyRange|Object<string, (number|string|Date|Array|IDBKeyRange)>|function(*, (number|string|Date|Array)): boolean)=} filter
The filter, restricting the records returned by this method. If a
function is provided, the first argument will be set to the record
and the second argument will be set to the primary key of the record.
@param {(string|string[])} order Field paths by which the records should be
sorted. A field path may be prefixed by an exclamation mark
({@code "!"}) for descending order.
@return {{storage: AbstractReadOnlyStorage, direction: CursorDirection, comparator: ?function(*, *): number, keyRange: (undefined|IDBKeyRange), filter: (undefined|function(*, (number|string|Date|Array)): boolean)}}
The storage on which the query should be executed, the direction in
which the cursor should be opened and the record comparator to use
to additionally sort the fetched records matching the filter.
Finally, the returned object has the {@code keyRange} and
{@code filter} fields set to the key range and custom filter to use
with the storage to run the query.

Prepares the query that uses the specified filter and record order for execution on this storage .


function prepareSortingOptimization(storages, simplifiedOrderFieldPaths) {
  let idb = idbProvider()
  for (let [keyPath, storageAndScore] of storages) {
    let keyPathSlice = keyPath.slice(0, simplifiedOrderFieldPaths.length)
    if (idb.cmp(keyPathSlice, simplifiedOrderFieldPaths) === 0) {
      storageAndScore.score += 4 // optimizing the sorting is more important
    }
  }
}

Calculates the best possible sorting optimization for the provided storages,
updating the provided map with sorting optimization scores for each storage.

@param {Map<string[], {storage: AbstractReadOnlyStorage, score: number, keyRange: (undefined|IDBKeyRange), filter: ?function(*, (number|string|Date|Array)): boolean}>} storages
Map of storage key paths to storages and information related to how
the query would be executed on each of them, including the
performance optimization score (the higher is better).
@param {string[]} simplifiedOrderFieldPaths Ordering field paths with the
exclamation mark prefix stripped from them.

Calculates the best possible sorting optimization for the provided storages updating the provided map with sorting optimization scores for each storage .


function prepareFilteringOptimization(storages, filter) {
  if (filter instanceof Function) {
    for (let [keyPath, storageAndScore] of storages) {
      storageAndScore.filter = filter
    }

    return
  }

  for (let [keyPath, storageAndScore] of storages) {
    let normalizedFilter = normalizeFilter(filter, keyPath)
    if (normalizedFilter instanceof Function) {
      let isOptimizableFilter =
          (filter instanceof Object) &&
          !(filter instanceof Date) &&
          !(filter instanceof Array) &&
          !(filter instanceof IDBKeyRange)
      if (isOptimizableFilter) {
        let partialOptimization = partiallyOptimizeFilter(filter, keyPath)
        storageAndScore.keyRange = partialOptimization.keyRange
        storageAndScore.filter = partialOptimization.filter
        if (partialOptimization.score) {
          storageAndScore.score += 1 + partialOptimization.score
        }
      } else {
        storageAndScore.filter = normalizedFilter
      }
    } else {
      storageAndScore.keyRange = normalizedFilter
      storageAndScore.score += 2
    }
  }
}

function prepareFilteringOptimization ( storages , filter ) { if ( filter instanceof Function ) { for ( let [ keyPath , storageAndScore ] of storages ) { storageAndScore . filter = filter } return } for ( let [ keyPath , storageAndScore ] of storages ) { let normalizedFilter = normalizeFilter ( filter , keyPath ) if ( normalizedFilter instanceof Function ) { let isOptimizableFilter = ( filter instanceof Object ) && ! ( filter instanceof Date ) && ! ( filter instanceof Array ) && ! ( filter instanceof IDBKeyRange ) if ( isOptimizableFilter ) { let partialOptimization = partiallyOptimizeFilter ( filter , keyPath ) storageAndScore . keyRange = partialOptimization . keyRange storageAndScore . filter = partialOptimization . filter if ( partialOptimization . score ) { storageAndScore . score += 1 + partialOptimization . score } } else { storageAndScore . filter = normalizedFilter } } else { storageAndScore . keyRange = normalizedFilter storageAndScore . score += 2 } } }

Calculates the best possible filtering optimizations for the provided
storages, updating the provided map with optimized filtering info and
optimization score for each storage.

@param {Map<string[], {storage: AbstractReadOnlyStorage, score: number, keyRange: (undefined|IDBKeyRange), filter: ?function(*, (number|string|Date|Array)): boolean}>} storages
Map of storage key paths to storages and information related to how
the query would be executed on each of them, including the
performance optimization score (the higher is better).
@param {?(undefined|number|string|Date|Array|IDBKeyRange|Object<string, (number|string|Date|Array|IDBKeyRange)>|function(*, (number|string|Date|Array)): boolean)=} filter
The filter, restricting the records returned by this method. If a
function is provided, the first argument will be set to the record
and the second argument will be set to the primary key of the record.

Calculates the best possible filtering optimizations for the provided storages updating the provided map with optimized filtering info and optimization score for each storage .


function chooseStorageForQuery(storages, order, simplifiedOrderFieldPaths,
    canSortingBeOptimized, expectedSortingDirection) {
  let sortedStorages = Array.from(storages.values())
  sortedStorages.sort((storage1, storage2) => {
    return storage2.score - storage1.score
  })

  let chosenStorageDetails = sortedStorages[0]
  let chosenStorage = chosenStorageDetails.storage
  let chosenStorageKeyPath = normalizeKeyPath(chosenStorage.keyPath)
  let storageKeyPathSlice = chosenStorageKeyPath.slice(
    0,
    simplifiedOrderFieldPaths.length
  )
  let optimizeSorting = canSortingBeOptimized &&
      (idbProvider().cmp(storageKeyPathSlice, simplifiedOrderFieldPaths) === 0)

  return {
    storage: chosenStorage,
    direction: optimizeSorting ? (
    CursorDirection[expectedSortingDirection ? "PREVIOUS" : "NEXT"]
    ) : CursorDirection.NEXT,
    comparator: optimizeSorting ? null : compileOrderingFieldPaths(order),
    keyRange: chosenStorageDetails.keyRange,
    filter: chosenStorageDetails.filter
  }
}

function chooseStorageForQuery ( storages , order , simplifiedOrderFieldPaths , canSortingBeOptimized , expectedSortingDirection ) { let sortedStorages = Array . from ( storages . values ( ) ) sortedStorages . sort ( ( storage1 , storage2 ) => { return storage2 . score - storage1 . score } ) let chosenStorageDetails = sortedStorages [ 0 ] let chosenStorage = chosenStorageDetails . storage let chosenStorageKeyPath = normalizeKeyPath ( chosenStorage . keyPath ) let storageKeyPathSlice = chosenStorageKeyPath . slice ( 0 , simplifiedOrderFieldPaths . length ) let optimizeSorting = canSortingBeOptimized && ( idbProvider ( ) . cmp ( storageKeyPathSlice , simplifiedOrderFieldPaths ) === 0 ) return { storage : chosenStorage , direction : optimizeSorting ? ( CursorDirection [ expectedSortingDirection ? "PREVIOUS" : "NEXT" ] ) : CursorDirection . NEXT , comparator : optimizeSorting ? null : compileOrderingFieldPaths ( order ) , keyRange : chosenStorageDetails . keyRange , filter : chosenStorageDetails . filter } }

Selects the storage on which the execute a query that should lead to the
best possible performance. The method returns all the data necessary to
execute the query.

@param {Map<string[], {storage: AbstractReadOnlyStorage, score: number, keyRange: (undefined|IDBKeyRange), filter: ?function(*, (number|string|Date|Array)): boolean}>} storages
Map of storage key paths to storages and information related to how
the query would be executed on each of them, including the
performance optimization score (the higher is better).
@param {string[]} order Field paths by which the records should be sorted. A
field path may be prefixed by an exclamation mark ({@code "!"}) for
descending order.
@param {string[]} simplifiedOrderFieldPaths Ordering field paths with the
exclamation mark prefix stripped from them.
@param {boolean} canSortingBeOptimized Set to {@code true} if sorting
optimization is possible.
@param {boolean} expectedSortingDirection Set to {@code true} for descending
order, set to {@code false} for ascending order.
@return {{storage: AbstractReadOnlyStorage, direction: CursorDirection, comparator: ?function(*, *): number, keyRange: (undefined|IDBKeyRange), filter: (undefined|function(*, (number|string|Date|Array)): boolean)}}
The storage on which the query should be executed, the direction in
which the cursor should be opened and the record comparator to use
to additionally sort the fetched records matching the filter.
Finally, the returned object has the {@code keyRange} and
{@code filter} fields set to the key range and custom filter to use
with the storage to run the query.

Selects the storage on which the execute a query that should lead to the best possible performance . The method returns all the data necessary to execute the query .


function prepareOrderingSpecificationForQuery(order, keyPath) {
  if (order === null) {
    order = CursorDirection.NEXT
  }

  let isCursorDirection = ((typeof order === "string") &&
      (CURSOR_DIRECTIONS.indexOf(order.toUpperCase()) > -1)) ||
      (CURSOR_DIRECTIONS.indexOf(order) > -1)
  if (isCursorDirection && (typeof order === "string")) {
    order = CursorDirection[order.toUpperCase()] || CursorDirection.PREVIOUS
  }

  if (order instanceof CursorDirection) {
    keyPath = normalizeKeyPath(keyPath)

    if (order === CursorDirection.NEXT) {
      return keyPath
    } else {
      return keyPath.map(fieldPath => `!${fieldPath}`)
    }
  }

  return order
}

function prepareOrderingSpecificationForQuery ( order , keyPath ) { if ( order === null ) { order = CursorDirection . NEXT } let isCursorDirection = ( ( typeof order === "string" ) && ( CURSOR_DIRECTIONS . indexOf ( order . toUpperCase ( ) ) > - 1 ) ) || ( CURSOR_DIRECTIONS . indexOf ( order ) > - 1 ) if ( isCursorDirection && ( typeof order === "string" ) ) { order = CursorDirection [ order . toUpperCase ( ) ] || CursorDirection . PREVIOUS } if ( order instanceof CursorDirection ) { keyPath = normalizeKeyPath ( keyPath ) if ( order === CursorDirection . NEXT ) { return keyPath } else { return keyPath . map ( fieldPath => ` ${ fieldPath } ` ) } } return order }

Preprocess the raw ordering specification into form that can be used in
query optimization.

@param {?(CursorDirection|string|string[]|function(*, *): number)} order How
the resulting records should be sorted. This can be one of the
following:
- a {@code CursorDirection} constant, either {@code NEXT} or
{@code PREVIOUS} for ascending or descending order respectively
- {@code null} as alias for {@code CursorDirection.NEXT}
- one of the {@code "NEXT"} (alias for {@code CursorDirection.NEXT}),
{@code "PREVIOUS"} or {@code "PREV"} (aliases for
{@code CursorDirection.PREVIOUS})
- a string containing a field path, meaning the records should be
sorted by the values of the denoted field (note that the field
must exist in all records and its value must be a valid IndexedDB
key value).
The order is ascending by default, use the {@code "!" prefix} for
descending order.
To sort by a field named {@code NEXT}, {@code PREVIOUS} or
{@code PREV} wrap the field path into an array containing the field
path.
- an array of field paths, as described above. The records will be
sorted by the values of the specified fields lexicographically.
- a comparator function compatible with the
{@linkcode Array.prototype.sort} method.
@param {(string|string[])} keyPath The key path of this object store.
@return {(string[]|function(*, *): number)} Prepared ordering specification
ready to be used in query optimization.

Preprocess the raw ordering specification into form that can be used in query optimization .


function openConnection(databaseName, sortedSchemaDescriptors) {
  let version = sortedSchemaDescriptors.slice().pop().version
  let request = NativeDBAccessor.indexedDB.open(databaseName, version)

  return new Promise((resolve, reject) => {
    let wasBlocked = false
    let upgradeTriggered = false

    let migrationPromiseResolver, migrationPromiseRejector
    let migrationPromise = new Promise((resolve, reject) => {
      migrationPromiseResolver = resolve
      migrationPromiseRejector = reject
    })
    // prevent leaking the same error to the console twice
    migrationPromise.catch(() => {})

    request.onsuccess = () => {
      let database = new Database(request.result)
      resolve(database)
      migrationPromiseResolver()
    }

    request.onupgradeneeded = (event) => {
      if (!wasBlocked) {
        upgradeTriggered = true
      }

      let database = request.result
      let transaction = request.transaction

      if (wasBlocked) {
        transaction.abort()
        return
      }

      upgradeDatabaseSchema(databaseName, event, migrationPromise, database,
          transaction, sortedSchemaDescriptors, migrationPromiseResolver,
          migrationPromiseRejector).catch((error) => {
        transaction.abort()
      })
    }

    request.onerror = (event) => {
      handleConnectionError(event, request.error, wasBlocked, upgradeTriggered,
          reject, migrationPromiseRejector)
    }

    request.onblocked = () => {
      wasBlocked = true

      let error = new Error("A database upgrade was needed, but could not " +
          "be performed, because the attempt was blocked by a connection " +
          "that remained opened after receiving the notification")
      reject(error)
      migrationPromiseRejector(error)
    }
  })
}

Handles opening the connection to the database and wraps the whole process
in a promise.

@param {string} databaseName The name of the Indexed DB database to connect
to.
@param {(DatabaseSchema|UpgradedDatabaseSchema|Object)[]} sortedSchemaDescriptors
The database schema descriptors, sorted in ascending order by the
schema version number.
@return {Promise<Database>} A promise that resolves to the database
connection.

Handles opening the connection to the database and wraps the whole process in a promise .


function handleConnectionError(event, error, wasBlocked, upgradeTriggered,
    reject, migrationPromiseRejector) {
  if (wasBlocked || upgradeTriggered) {
    event.preventDefault()
    return
  }

  reject(request.error)
  migrationPromiseRejector(request.error)
}

function handleConnectionError ( event , error , wasBlocked , upgradeTriggered , reject , migrationPromiseRejector ) { if ( wasBlocked || upgradeTriggered ) { event . preventDefault ( ) return } reject ( request . error ) migrationPromiseRejector ( request . error ) }

Handles a database error encountered during connection establishing.

@param {Event} event The error event.
@param {Error} error The encountered error.
@param {boolean} wasBlocked Flag signalling whether the connection has been
blocked.
@param {boolean} upgradeTriggered Flag signalling whether the database schema
upgrade process was triggered.
@param {function(Error)} reject The connection promise rejection callback.
@param {function(Error)} migrationPromiseRejector The schema migration
promise rejection callback.

Handles a database error encountered during connection establishing .


function executeMigrationListeners(databaseName, oldVersion, newVersion,
    completionPromise) {
  for (let listener of migrationListeners) {
    try {
      listener(databaseName, oldVersion, newVersion, completionPromise)
    } catch (e) {
      console.warn("A schema migration event listener threw an error", e);
    }
  }
}

Executes the currently registered database schema migration listeners with
the provided arguments.

@param {string} databaseName The name of the database being migrated.
@param {number} oldVersion The version number from which the schema is being
migrated.
@param {number} newVersion The version number to which the schema is being
migrated.
@param {Promise<undefined>} completionPromise A promise that resolves when
the migration is finished.

Executes the currently registered database schema migration listeners with the provided arguments .


function splitFilteringObject(filter, filterFieldPaths, storageKeyPath) {
  let fieldsToOptimize = {}
  let fieldsToCompile = {}

  filterFieldPaths.forEach((fieldPath) => {
    let value = getFieldValue(filter, fieldPath)
    if (storageKeyPath.indexOf(fieldPath) > -1) {
      setFieldValue(fieldsToOptimize, fieldPath, value)
    } else {
      setFieldValue(fieldsToCompile, fieldPath, value)
    }
  })

  return {
    fieldsToOptimize,
    fieldsToCompile
  }
}

function splitFilteringObject ( filter , filterFieldPaths , storageKeyPath ) { let fieldsToOptimize = { } let fieldsToCompile = { } filterFieldPaths . forEach ( ( fieldPath ) => { let value = getFieldValue ( filter , fieldPath ) if ( storageKeyPath . indexOf ( fieldPath ) > - 1 ) { setFieldValue ( fieldsToOptimize , fieldPath , value ) } else { setFieldValue ( fieldsToCompile , fieldPath , value ) } } ) return { fieldsToOptimize , fieldsToCompile } }

Splits the provided filter object to two objects according the the provided
storage key path. This is used to separate a complex filter object into an
object optimizable into a key range and a simpler object that will be
compiled into a filter predicate function.

@param {Object<string, *>} filter Filtering object to split.
@param {string[]} filterFieldPaths All field paths in the filtering object.
@param {string[]} storageKeyPath The key path(s) of the storage for which
the filtering object is being split.
@return {{fieldsToOptimize: Object<string, *>, fieldsToCompile: Object<string, *>}}
The {@code fieldsToOptimize} filter object will contain only the
field paths specified in the {@code storageKeyPath}, while the
{@code fieldsToCompile} will contain the remaining field paths of
the source filter object.

Splits the provided filter object to two objects according the the provided storage key path . This is used to separate a complex filter object into an object optimizable into a key range and a simpler object that will be compiled into a filter predicate function .


function getFieldPaths(object, stopOnKeyRange = true) {
  let fieldPaths = []
  fieldPaths.containsKeyRange = false
  generateFieldPaths(object, [])

  return fieldPaths

  function generateFieldPaths(object, parts) {
    Object.keys(object).some((fieldName) => {
      let value = object[fieldName]
      if (stopOnKeyRange && (value instanceof IDBKeyRange)) {
        fieldPaths = null
        return true
      }

      let isTerminalValue =
          !(value instanceof Object) ||
          (value instanceof Date) ||
          (value instanceof Array) ||
          (value instanceof IDBKeyRange)
      let fieldPath = parts.slice()
      fieldPath.push(fieldName)

      if (isTerminalValue) {
        fieldPaths.push(fieldPath.join("."))
      } else {
        generateFieldPaths(value, fieldPath)
      }
    })
  }
}

function getFieldPaths ( object , stopOnKeyRange = true ) { let fieldPaths = [ ] fieldPaths . containsKeyRange = false generateFieldPaths ( object , [ ] ) return fieldPaths function generateFieldPaths ( object , parts ) { Object . keys ( object ) . some ( ( fieldName ) => { let value = object [ fieldName ] if ( stopOnKeyRange && ( value instanceof IDBKeyRange ) ) { fieldPaths = null return true } let isTerminalValue = ! ( value instanceof Object ) || ( value instanceof Date ) || ( value instanceof Array ) || ( value instanceof IDBKeyRange ) let fieldPath = parts . slice ( ) fieldPath . push ( fieldName ) if ( isTerminalValue ) { fieldPaths . push ( fieldPath . join ( "." ) ) } else { generateFieldPaths ( value , fieldPath ) } } ) } }

Generates an array containing all field paths in the provided object.

The method also tests whether any of the leaf field values is an
{@linkcode IDBKeyRange} instance, in such case the method returns
{@code null}.

The method is used to determine whether the provided object can be turned
into and {@linkcode IDBKeyRange} instance for a compound record key, and if
so, retrieve all field paths in the object so that its structure can be
validated against the field paths of the storage.

@param {Object} object The object from which the field paths are to be
extracted.
@param {boolean=} stopOnKeyRange When {@code true}, the method returns
{@code null} if it encounters a key range.
@return {?string[]} All field paths in the provided objects, unless the
object contains a field set to an {@linkcode IDBKeyRange} instance.

Generates an array containing all field paths in the provided object .


function setFieldValue(object, fieldPath, value) {
  let parts = fieldPath.split(".")
  let done = []
  let currentObject = object

  while (parts.length) {
    let field = parts.shift()

    if (!parts.length) {
      if (currentObject.hasOwnProperty(field)) {
        throw new Error(`The ${fieldPath} field seems to be already present `)
      }
      currentObject[field] = value
      break
    }

    if (!currentObject.hasOwnProperty(field)) {
      currentObject[field] = {}
    }

    if (!(currentObject[field] instanceof Object)) {
      throw new Error(`The ${fieldPath} field is in a conflict with the ` +
          `${done.join(".")} field`)
    }

    currentObject = currentObject[field]

    done.push(field)
  }
}

function setFieldValue ( object , fieldPath , value ) { let parts = fieldPath . split ( "." ) let done = [ ] let currentObject = object while ( parts . length ) { let field = parts . shift ( ) if ( ! parts . length ) { if ( currentObject . hasOwnProperty ( field ) ) { throw new Error ( ` ${ fieldPath } ` ) } currentObject [ field ] = value break } if ( ! currentObject . hasOwnProperty ( field ) ) { currentObject [ field ] = { } } if ( ! ( currentObject [ field ] instanceof Object ) ) { throw new Error ( ` ${ fieldPath } ` + ` ${ done . join ( "." ) } ` ) } currentObject = currentObject [ field ] done . push ( field ) } }

Sets the specified field, denoted by the specified field path, on the
provided object to the provided value.

If the specified field path does not exist in the object, it is
automatically created by inserting new empty objects.

@param {Object} object An object on which the field should be set.
@param {string} fieldPath A field path on which the value should be set. A
field path is a sequence of field names joined by dots ({@code "."}).
@param {*} value The value to set at the specified field path.
@throws {Error} Thrown if the field path is already present or in a conflict
with another already present field path.

Sets the specified field denoted by the specified field path on the provided object to the provided value .


function getFieldValue(object, fieldPath) {
  if (!fieldPath) {
    return object
  }

  let currentObject = object
  fieldPath.split(".").forEach((fieldName) => {
    if (!currentObject.hasOwnProperty(fieldName)) {
      throw new Error(`The field path ${fieldPath} does not exist in the ` +
          "provided object")
    }

    currentObject = currentObject[fieldName]
  })

  return currentObject
}

function
getFieldValue
(
object
,
fieldPath
)
{
if
(
!
fieldPath
)
{
return
object
}
let
currentObject
=
object
fieldPath
.
split
(
"."
)
.
forEach
(
(
fieldName
)
=>
{
if
(
!
currentObject
.
hasOwnProperty
(
fieldName
)
)
{
throw
new
Error
(
`
${
fieldPath
}
`
+
"provided object"
)
}
currentObject
=
currentObject
[
fieldName
]
}
)
return
currentObject
}

Retrieves the value from the provided object at the specified field path.

@param {Object} object The object from which the field value is to be
extracted.
@param {string} fieldPath The path to the field to retrieve, specified as a
sequence of field names joined by dots ({@code "."}).
@return {*} The value of the specified field.
@throw {Error} Thrown if the field path does not exist in the provided
object.

Retrieves the value from the provided object at the specified field path .


function upgradeSchema(nativeDatabase, nativeTransaction, descriptors) {
  let objectStoreNames = Array.from(nativeDatabase.objectStoreNames)
  let newObjectStoreNames = descriptors.map((objectStore) => {
    return objectStore.name
  })
  objectStoreNames.forEach((objectStoreName) => {
    if (newObjectStoreNames.indexOf(objectStoreName) === -1) {
      nativeDatabase.deleteObjectStore(objectStoreName)
    }
  })

  descriptors.forEach((objectStoreDescriptor) => {
    let objectStoreName = objectStoreDescriptor.name
    let nativeObjectStore = objectStoreNames.indexOf(objectStoreName) > -1 ?
        nativeTransaction.objectStore(objectStoreName) : null

    let objectStoreMigrator = new ObjectStoreMigrator(nativeDatabase,
        nativeObjectStore, objectStoreDescriptor)
    objectStoreMigrator.executeMigration()
  })
}

function upgradeSchema ( nativeDatabase , nativeTransaction , descriptors ) { let objectStoreNames = Array . from ( nativeDatabase . objectStoreNames ) let newObjectStoreNames = descriptors . map ( ( objectStore ) => { return objectStore . name } ) objectStoreNames . forEach ( ( objectStoreName ) => { if ( newObjectStoreNames . indexOf ( objectStoreName ) === - 1 ) { nativeDatabase . deleteObjectStore ( objectStoreName ) } } ) descriptors . forEach ( ( objectStoreDescriptor ) => { let objectStoreName = objectStoreDescriptor . name let nativeObjectStore = objectStoreNames . indexOf ( objectStoreName ) > - 1 ? nativeTransaction . objectStore ( objectStoreName ) : null let objectStoreMigrator = new ObjectStoreMigrator ( nativeDatabase , nativeObjectStore , objectStoreDescriptor ) objectStoreMigrator . executeMigration ( ) } ) }

Updates the schema of the provided Indexed DB database to the schema
specified by the provided schema descriptors.

@param {IDBDatabase} nativeDatabase The native Indexed DB database being
migrated.
@param {IDBTransaction} nativeTransaction The native {@code versionchange}
transaction.
@param {((DatabaseSchema|UpgradedDatabaseSchema))[]} descriptors Schema
descriptor of the version to which the database is to be upgraded.

Updates the schema of the provided Indexed DB database to the schema specified by the provided schema descriptors .


function container(width, height, position, elem) {
    return new Element(new ContainerElement(position, elem)
        , width, height)
}

function container ( width , height , position , elem ) { return new Element ( new ContainerElement ( position , elem ) , width , height ) }

Int -> Int -> Position -> Element -> Element

Int - > Int - > Position - > Element - > Element


function mainSection(state) {
    var todos = state.todos
    var route = state.route

    return h("section.main", { hidden: todos.length === 0 }, [
        toggleAllPool.change(h("input#toggle-all.toggle-all", {
            type: "checkbox",
            checked: todos.every(function (todo) {
                return todo.completed
            })
        })),
        h("label", { htmlFor: "toggle-all" }, "Mark all as complete"),
        h("ul.todo-list", todos.filter(function (todo) {
            return route === "completed" && todo.completed ||
                route === "active" && !todo.completed ||
                route === "all"
        }).map(todoItem))
    ])
}

function
mainSection
(
state
)
{
var
todos
=
state
.
todos
var
route
=
state
.
route
return
h
(
"section.main"
,
{
hidden
:
todos
.
length
===
0
}
,
[
toggleAllPool
.
change
(
h
(
"input#toggle-all.toggle-all"
,
{
type
:
"checkbox"
,
checked
:
todos
.
every
(
function
(
todo
)
{
return
todo
.
completed
}
)
}
)
)
,
h
(
"label"
,
{
htmlFor
:
"toggle-all"
}
,
"Mark all as complete"
)
,
h
(
"ul.todo-list"
,
todos
.
filter
(
function
(
todo
)
{
return
route
===
"completed"
&&
todo
.
completed
||
route
===
"active"
&&
!
todo
.
completed
||
route
===
"all"
}
)
.
map
(
todoItem
)
)
]
)
}

Various template functions to render subsets of the UI


function Client() {
    EventEmitter.call(this);

    this.debug = true;  //false
    this.socket = dgram.createSocket('udp4');
    this.isSocketBound = false;
    this.devices = {};
    this.port = constants.DREAMSCREEN_PORT;
    this.discoveryTimer = null;
    this.messageHandlers = [];
    this.messageHandlerTimeout = 5000; // 45000 45 sec
    this.broadcastIp = constants.DEFAULT_BROADCAST_IP;
}

function
Client
(
)
{
EventEmitter
.
call
(
this
)
;
this
.
debug
=
true
;
//false
this
.
socket
=
dgram
.
createSocket
(
'udp4'
)
;
this
.
isSocketBound
=
false
;
this
.
devices
=
{
}
;
this
.
port
=
constants
.
DREAMSCREEN_PORT
;
this
.
discoveryTimer
=
;
this
.
messageHandlers
=
[
]
;
this
.
messageHandlerTimeout
=
5000
;
// 45000 45 sec
this
.
broadcastIp
=
constants
.
DEFAULT_BROADCAST_IP
;
}

Creates a dreamscreen client
@extends EventEmitter


function Light(constr) {
    this.client = constr.client;

    this.ipAddress = constr.ipAddress;
    this.serialNumber = constr.serialNumber;
    this.productId = constr.productId;                  //devicetype

    this.lastSeen = constr.lastSeen;
    this.isReachable = constr.isReachable;

    this.name = constr.name;                            //devicename
    this.groupName = constr.groupName;                  //groupname
    this.groupNumber = constr.groupNumber;              //groupnumber

    this.mode = constr.mode;                            //mode
    this.brightness = constr.brightness;                //brightness
    this.ambientColor = constr.ambientColor;            //ambientr ambientg ambientb
    this.ambientShow = constr.ambientShow;              //ambientscene
    this.ambientModeType = constr.ambientModeType;      //
    this.hdmiInput = constr.hdmiInput;                  //hdmiinput
    this.hdmiInputName1 = constr.hdmiInputName1;        //hdminame1
    this.hdmiInputName2 = constr.hdmiInputName2;        //hdminame2
    this.hdmiInputName3 = constr.hdmiInputName3;        //hdminame3
}

function
Light
(
constr
)
{
this
.
client
=
constr
.
client
;
this
.
ipAddress
=
constr
.
ipAddress
;
this
.
serialNumber
=
constr
.
serialNumber
;
this
.
productId
=
constr
.
productId
;
//devicetype
this
.
lastSeen
=
constr
.
lastSeen
;
this
.
isReachable
=
constr
.
isReachable
;
this
.
name
=
constr
.
name
;
//devicename
this
.
groupName
=
constr
.
groupName
;
//groupname
this
.
groupNumber
=
constr
.
groupNumber
;
//groupnumber
this
.
mode
=
constr
.
mode
;
//mode
this
.
brightness
=
constr
.
brightness
;
//brightness
this
.
ambientColor
=
constr
.
ambientColor
;
//ambientr ambientg ambientb
this
.
ambientShow
=
constr
.
ambientShow
;
//ambientscene
this
.
ambientModeType
=
constr
.
ambientModeType
;
//
this
.
hdmiInput
=
constr
.
hdmiInput
;
//hdmiinput
this
.
hdmiInputName1
=
constr
.
hdmiInputName1
;
//hdminame1
this
.
hdmiInputName2
=
constr
.
hdmiInputName2
;
//hdminame2
this
.
hdmiInputName3
=
constr
.
hdmiInputName3
;
//hdminame3
}

A representation of a light bulb
DreamScreen HD, DreamScreen 4K, SideKick

@class
@param {Obj} constr constructor object
@param {DreamScreen/Client} constr.client the client the light belongs to
@param {String} constr.ipAddress ip address of the light

A representation of a light bulb DreamScreen HD DreamScreen 4K SideKick


function plainText(content) {
    var textSize = getTextSize(content)

    return new Element(new TextElement("left", content)
        , textSize.width, textSize.height)
}

function plainText ( content ) { var textSize = getTextSize ( content ) return new Element ( new TextElement ( "left" , content ) , textSize . width , textSize . height ) }


function(json) {
      var output = '[
';
      var hasContents = false;
      for ( var prop in json ) {
        hasContents = true;
        output += '
';
        output += this.valueToHTML(json[prop]);
        output += '
';
      }
      output += '
]';

      if ( ! hasContents ) {
        output = "[ ]";
      }

      return output;
    }

function
(
json
)
{
var
output
=
'[
'
;
var
hasContents
=
false
;
for
(
var
prop
in
json
)
{
hasContents
=
true
;
output
+=
'
'
;
output
+=
this
.
valueToHTML
(
json
[
prop
]
)
;
output
+=
'
'
;
}
output
+=
'
]'
;
if
(
!
hasContents
)
{
output
=
"[ ]"
;
}
return
output
;
}


function(error, data, uri) {
        // var output = '
' +
        //    this.stringbundle.GetStringFromName('errorParsing') + '
';
        // output += '
' +
        //    this.stringbundle.GetStringFromName('docContents') + ':
';
        var output = '
Error parsing JSON: ' +
        error.message+'
'
        output += '
'+error.stack+':
';
        output += '
' + this.htmlEncode(data) + '
';
        return this.toHTML(output, uri + ' - Error');
    }

//    this.stringbundle.GetStringFromName('errorParsing') + '</div>';

//    this.stringbundle.GetStringFromName('docContents') + ':</h1>';

Produce an error document for when parsing fails.

Produce an error document for when parsing fails .


function write(chunk, encoding, callback) {
    if (typeof encoding === 'function') {
      callback = encoding
      encoding = null
    }

    if (ended) {
      throw new Error('Did not expect `write` after `end`')
    }

    chunks.push((chunk || '').toString(encoding || 'utf8'))

    if (callback) {
      callback()
    }

    // Signal succesful write.
    return true
  }

function
write
(
chunk
,
encoding
,
callback
)
{
if
(
typeof
encoding
===
'function'
)
{
callback
=
encoding
encoding
=
}
if
(
ended
)
{
throw
new
Error
(
'Did not expect `write` after `end`'
)
}
chunks
.
push
(
(
chunk
||
''
)
.
toString
(
encoding
||
'utf8'
)
)
if
(
callback
)
{
callback
(
)
}
// Signal succesful write.
return
true
}


function end() {
    write.apply(null, arguments)

    ended = true

    processor.process(chunks.join(''), done)

    return true

    function done(err, file) {
      var messages = file ? file.messages : []
      var length = messages.length
      var index = -1

      chunks = null

      // Trigger messages as warnings, except for fatal error.
      while (++index < length) {
        /* istanbul ignore else - shouldn’t happen. */
        if (messages[index] !== err) {
          emitter.emit('warning', messages[index])
        }
      }

      if (err) {
        // Don’t enter an infinite error throwing loop.
        global.setTimeout(function() {
          emitter.emit('error', err)
        }, 4)
      } else {
        emitter.emit('data', file.contents)
        emitter.emit('end')
      }
    }
  }

End the writing.  Passes all arguments to a final `write`.  Starts the process, which will trigger `error`, with a fatal error, if any; `data`, with the generated document in `string` form, if succesful.  If messages are triggered during the process, those are triggerd as `warning`s.

End the writing . Passes all arguments to a final write . Starts the process which will trigger error with a fatal error if any ; data with the generated document in string form if succesful . If messages are triggered during the process those are triggerd as warning s .


function cleanup() {
      emitter.removeListener('data', ondata)
      emitter.removeListener('end', onend)
      emitter.removeListener('error', onerror)
      emitter.removeListener('end', cleanup)
      emitter.removeListener('close', cleanup)

      dest.removeListener('error', onerror)
      dest.removeListener('close', cleanup)
    }

function cleanup ( ) { emitter . removeListener ( 'data' , ondata ) emitter . removeListener ( 'end' , onend ) emitter . removeListener ( 'error' , onerror ) emitter . removeListener ( 'end' , cleanup ) emitter . removeListener ( 'close' , cleanup ) dest . removeListener ( 'error' , onerror ) dest . removeListener ( 'close' , cleanup ) }


function onerror(err) {
      var handlers = emitter._events.error

      cleanup()

      // Cannot use `listenerCount` in node <= 0.12.
      if (!handlers || handlers.length === 0 || handlers === onerror) {
        throw err // Unhandled stream error in pipe.
      }
    }

Close dangling pipes and handle unheard errors.

Close dangling pipes and handle unheard errors .


function clean(root, name) {
  const blacklist = ['.git', 'node_modules'];
  for (const item of blacklist) {
    const pathToItem = path.join(root, item);
    if (fs.pathExistsSync(pathToItem)) {
      fs.removeSync(pathToItem);
      console.log(`${chalk.dim.redBright(item)} has been removed from ${chalk.yellow(name)}`);
    }
  }
}

function clean ( root , name ) { const blacklist = [ '.git' , 'node_modules' ] ; for ( const item of blacklist ) { const pathToItem = path . join ( root , item ) ; if ( fs . pathExistsSync ( pathToItem ) ) { fs . removeSync ( pathToItem ) ; console . log ( ` ${ chalk . dim . redBright ( item ) } ${ chalk . yellow ( name ) } ` ) ; } } }


function ls() {
  const vault = path.join(os.homedir(), '.snap');
  const list = shell.ls(vault);
  if (!list.length) {
    console.log("\nIt seems you don't have anything saved...");
    console.log(`You can run ${chalk.yellow('snap save')} to save a directory or Git repo for future use!`);
    console.log(`Run ${chalk.yellow('snap save -h')} for more info.\n`);
    return;
  }

  console.log('\nThe following boilerplates have been saved...');
  console.log(`Run ${chalk.yellow('snap <boilerplate-name> <project-directory>')} to get started with your new project!`);
  for (const bplate of list) {
    console.log(`  ○ ${bplate}`);
  }
  console.log();
}


function sessionData(req, res, session, cb){

    const now = new Date();

    if(session.continued)
        return cb(null, req, res, session);

    async.parallelLimit([
            getIp,
            getLocation,
            getSystem
        ], 2,
        function(err){
            cb(err, this.req, this.res, this.session);

            if(opts.log_all)
                log.timer('sessionData', now);

        }.bind({
            req: req,
            res: res,
            session: session
        })
    );

    // ======================

    // .ip
    function getIp(cb){
        session.ip = get_ip(req).clientIp;
        cb(null)
    }

    // .geo :: .city, .state, .country
    function getLocation(cb){
        if(!geo_lookup)
            return cb(null);

        const loc = geo_lookup.get(session.ip);

        if(!session.geo)
            session.geo = {};

        if(loc){
            try {
                if(loc.city)
                    session.geo.city = loc.city.names.en;
                if(loc.subdivisions)
                    session.geo.state = loc.subdivisions[0].iso_code;
                if(loc.country)
                    session.geo.country = loc.country.iso_code;
                if(loc.continent)
                    session.geo.continent = loc.continent.code;
                if(loc.location)
                    session.geo.time_zone = loc.location.time_zone;
            }
            catch(e){
                log.error('geoIP error:', e);
            }
        }

        cb(null)
    }

    // .system :: .os{, .broswer{ .name, .version
    function getSystem(cb){
        var agent = useragent.parse(req.headers['user-agent']);
        var os = agent.os;

        if(!session.system)
            session.system = {};

        if(!session.system.browser)
            session.system.browser = {};
        if(!session.system.os)
            session.system.os = {};

        session.system.browser.name = agent.family;
        session.system.browser.version = agent.major + '.' + agent.minor + '.' + agent.patch;

        session.system.os.name = os.family;
        session.system.os.version = os.major + '.' + os.minor + '.' + os.patch;

        cb(null)
    }
}


function newRequest(req, res, session, cb){

    const now = new Date();

    const request = {
        _id: `r${crypto.randomBytes(16).toString('hex')}${Date.now()}`,
        host: req.hostname,
        url: req.url,
        method: req.method,
        referrer: req.get('Referrer') || req.get('Referer')
    };

    // populate request query
    for(let field in req.query){
        if(field === 'ref')
            request.ref = req.query[field];
        else {
            if(!request.query)
                request.query = [];

            request.query.push({
                field: field,
                value: req.query[field]
            })
        }
    }

    // add request cookie for communication/association with socket
    res.cookie('na_req', AES.encrypt(request._id), {
        maxAge:     1000 * 60 * 15,             // 15 mins
        httpOnly:   true,
        secure:     opts.secure
    });

    // return request object: will be added at sessionSave();
    cb(null, req, res, session, request);

    if(opts.log_all)
        log.timer('newRequest', now);
}

return new request document, create req cookie

return new request document create req cookie


function(packet) {
	this.updatePayload = function(packet) {
		this.p_previous = this.p;
		this.p = packet.payload;
		this.changed = this.p_previous != this.p;
		this.retained = packet.retain;
		this.lastChange = this.currentChange;
		this.currentChange = new Date();
	};

	this.changedFromTo = function(from, to) {
		return this.changed && this.p_previous == from && this.p == to;
	};
	this.changedTo = function(to) {
		return this.changed && this.p == to;
	};
	this.changedFrom = function(from) {
		return this.changed && this.p_previous == from;
	};

	this.t = packet.topic;
	this.updatePayload(packet);
	this.currentChange = new Date();
	this.lastChange = undefined;

	//aliases
	this.payload = this.p;
	this.topic = this.t;
}

function ( packet ) { this . updatePayload = function ( packet ) { this . p_previous = this . p ; this . p = packet . payload ; this . changed = this . p_previous != this . p ; this . retained = packet . retain ; this . lastChange = this . currentChange ; this . currentChange = new Date ( ) ; } ; this . changedFromTo = function ( from , to ) { return this . changed && this . p_previous == from && this . p == to ; } ; this . changedTo = function ( to ) { return this . changed && this . p == to ; } ; this . changedFrom = function ( from ) { return this . changed && this . p_previous == from ; } ; this . t = packet . topic ; this . updatePayload ( packet ) ; this . currentChange = new Date ( ) ; this . lastChange = undefined ; //aliases this . payload = this . p ; this . topic = this . t ; }