/* global: localStorage */ const JSData = require('js-data') const guid = require('mout/random/guid') const { Query, utils } = JSData const { addHiddenPropsToTarget, deepMixIn, extend, fillIn, forEachRelation, forOwn, fromJson, get, isArray, isUndefined, resolve, reject, set, toJson } = utils function isValidString (value) { return (value != null && value !== '') } function join (items, separator) { separator || (separator = '') return items.filter(isValidString).join(separator) } function makePath (...args) { let result = join(args, '/') return result.replace(/([^:\/]|^)\/{2,}/g, '$1/') } function unique (array) { const seen = {} const final = [] array.forEach(function (item) { if (item in seen) { return } final.push(item) seen[item] = 0 }) return final } let queue = [] let taskInProcess = false function enqueue (task) { queue.push(task) } function dequeue () { if (queue.length && !taskInProcess) { taskInProcess = true queue[0]() } } function queueTask (task) { if (!queue.length) { enqueue(task) dequeue() } else { enqueue(task) } } function createTask (fn) { return new Promise(fn).then(function (result) { taskInProcess = false queue.shift() setTimeout(dequeue, 0) return result }, function (err) { taskInProcess = false queue.shift() setTimeout(dequeue, 0) return reject(err) }) } const noop = function (...args) { const self = this const opts = args[args.length - 1] self.dbg(opts.op, ...args) return resolve() } const noop2 = function (...args) { const self = this const opts = args[args.length - 2] self.dbg(opts.op, ...args) return resolve() } const DEFAULTS = { /** * TODO * * @name LocalStorageAdapter#basePath * @type {string} */ basePath: '', /** * TODO * * @name LocalStorageAdapter#debug * @type {boolean} * @default false */ debug: false, /** * TODO * * @name LocalStorageAdapter#returnDeletedIds * @type {boolean} * @default false */ returnDeletedIds: false, /** * TODO * * @name LocalStorageAdapter#storage * @type {Object} * @default localStorage */ storage: localStorage } /** * LocalStorageAdapter class. * * @example * import {DataStore} from 'js-data' * import LocalStorageAdapter from 'js-data-localstorage' * const store = new DataStore() * const adapter = new LocalStorageAdapter() * store.registerAdapter('ls', adapter, { 'default': true }) * * @class LocalStorageAdapter * @param {Object} [opts] Configuration opts. * @param {string} [opts.basePath=''] TODO * @param {boolean} [opts.debug=false] TODO * @param {Object} [opts.storeage=localStorage] TODO */ function LocalStorageAdapter (opts) { fillIn(this, opts || {}) fillIn(this, DEFAULTS) } /** * Alternative to ES6 class syntax for extending `LocalStorageAdapter`. * * @name LocalStorageAdapter.extend * @method * @param {Object} [instanceProps] Properties that will be added to the * prototype of the subclass. * @param {Object} [classProps] Properties that will be added as static * properties to the subclass itself. * @return {Object} Subclass of `LocalStorageAdapter`. */ LocalStorageAdapter.extend = extend addHiddenPropsToTarget(LocalStorageAdapter.prototype, { /** * @name LocalStorageAdapter#afterCreate * @method */ afterCreate: noop2, /** * @name LocalStorageAdapter#afterCreateMany * @method */ afterCreateMany: noop2, /** * @name LocalStorageAdapter#afterDestroy * @method */ afterDestroy: noop2, /** * @name LocalStorageAdapter#afterDestroyAll * @method */ afterDestroyAll: noop2, /** * @name LocalStorageAdapter#afterFind * @method */ afterFind: noop2, /** * @name LocalStorageAdapter#afterFindAll * @method */ afterFindAll: noop2, /** * @name LocalStorageAdapter#afterUpdate * @method */ afterUpdate: noop2, /** * @name LocalStorageAdapter#afterUpdateAll * @method */ afterUpdateAll: noop2, /** * @name LocalStorageAdapter#afterUpdateMany * @method */ afterUpdateMany: noop2, /** * @name LocalStorageAdapter#beforeCreate * @method */ beforeCreate: noop, /** * @name LocalStorageAdapter#beforeCreateMany * @method */ beforeCreateMany: noop, /** * @name LocalStorageAdapter#beforeDestroy * @method */ beforeDestroy: noop, /** * @name LocalStorageAdapter#beforeDestroyAll * @method */ beforeDestroyAll: noop, /** * @name LocalStorageAdapter#beforeFind * @method */ beforeFind: noop, /** * @name LocalStorageAdapter#beforeFindAll * @method */ beforeFindAll: noop, /** * @name LocalStorageAdapter#beforeUpdate * @method */ beforeUpdate: noop, /** * @name LocalStorageAdapter#beforeUpdateAll * @method */ beforeUpdateAll: noop, /** * @name LocalStorageAdapter#beforeUpdateMany * @method */ beforeUpdateMany: noop, _create (mapper, props, opts) { const self = this const _props = {} const relationFields = mapper.relationFields || [] forOwn(props, function (value, key) { if (relationFields.indexOf(key) === -1) { _props[key] = value } }) const id = get(_props, mapper.idAttribute) || guid() set(_props, mapper.idAttribute, id) const key = self.getIdPath(mapper, opts, id) // Create the record // TODO: Create related records when the "with" option is provided self.storage.setItem(key, toJson(_props)) self.ensureId(id, mapper, opts) return fromJson(self.storage.getItem(key)) }, /** * Create a new record. * * @name LocalStorageAdapter#create * @method * @param {Object} mapper The mapper. * @param {Object} props The record to be created. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ create (mapper, props, opts) { const self = this props || (props = {}) opts || (opts = {}) return createTask(function (success, failure) { queueTask(function () { let op // beforeCreate lifecycle hook op = opts.op = 'beforeCreate' return resolve(self[op](mapper, props, opts)).then(function (_props) { // Allow for re-assignment from lifecycle hook let record = isUndefined(_props) ? props : _props record = self._create(mapper, record, opts) // afterCreate lifecycle hook op = opts.op = 'afterCreate' return self[op](mapper, props, opts, record).then(function (_record) { // Allow for re-assignment from lifecycle hook record = isUndefined(_record) ? record : _record return opts.raw ? { data: record, created: 1 } : record }) }).then(success, failure) }) }) }, /** * Create multiple records in a single batch. * * @name LocalStorageAdapter#createMany * @method * @param {Object} mapper The mapper. * @param {Array} props Array of records to be created. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ createMany (mapper, props, opts) { const self = this props || (props = {}) opts || (opts = {}) return createTask(function (success, failure) { queueTask(function () { let op // beforeCreateMany lifecycle hook op = opts.op = 'beforeCreateMany' return resolve(self[op](mapper, props, opts)).then(function (_props) { // Allow for re-assignment from lifecycle hook let records = isUndefined(_props) ? props : _props records = records.map(function (record) { return self._create(mapper, record, opts) }) // afterCreateMany lifecycle hook op = opts.op = 'afterCreateMany' return self[op](mapper, props, opts, records).then(function (_records) { // Allow for re-assignment from lifecycle hook records = isUndefined(_records) ? records : _records return opts.raw ? { data: records, created: records.length } : records }) }).then(success, failure) }) }) }, /** * @name LocalStorageAdapter#dbg * @method */ dbg (...args) { this.log('debug', ...args) }, /** * Destroy the record with the given primary key. * * @name LocalStorageAdapter#destroy * @method * @param {Object} mapper The mapper. * @param {(string|number)} id Primary key of the record to destroy. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ destroy (mapper, id, opts) { const self = this opts || (opts = {}) const returnDeletedIds = isUndefined(opts.returnDeletedIds) ? self.returnDeletedIds : !!opts.returnDeletedIds return createTask(function (success, failure) { queueTask(function () { let op // beforeDestroy lifecycle hook op = opts.op = 'beforeDestroy' return resolve(self[op](mapper, id, opts)).then(function () { op = opts.op = 'destroy' self.dbg(op, id, opts) // Destroy the record // TODO: Destroy related records when the "with" option is provided const key = self.getIdPath(mapper, opts, id) const recordJson = self.storage.getItem(key) let deleted = 0 if (recordJson) { self.storage.removeItem(key) self.removeId(id, mapper, opts) deleted++ } // afterDestroy lifecycle hook op = opts.op = 'afterDestroy' return resolve(self[op](mapper, id, opts, deleted && returnDeletedIds ? id : undefined)).then(function (_id) { // Allow for re-assignment from lifecycle hook id = isUndefined(_id) && returnDeletedIds ? id : _id const result = { data: id, deleted } return self.getRaw(opts) ? result : result.data }) }).then(success, failure) }) }) }, /** * Destroy the records that match the selection `query`. * * @name LocalStorageAdapter#destroyAll * @method * @param {Object} mapper The mapper. * @param {Object} query Selection query. * @param {Object} [opts] Configuration opts. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ destroyAll (mapper, query, opts) { const self = this query || (query = {}) opts || (opts = {}) const returnDeletedIds = isUndefined(opts.returnDeletedIds) ? self.returnDeletedIds : !!opts.returnDeletedIds return createTask(function (success, failure) { queueTask(function () { let op // beforeDestroyAll lifecycle hook op = opts.op = 'beforeDestroyAll' return resolve(self[op](mapper, query, opts)).then(function () { op = opts.op = 'destroyAll' self.dbg(op, query, opts) // Find the records that are to be destroyed return self.findAll(mapper, query, { raw: false }) }).then(function (records) { const idAttribute = mapper.idAttribute // Gather IDs of records to be destroyed let ids = records.map(function (record) { return get(record, idAttribute) }) // Destroy each record // TODO: Destroy related records when the "with" option is provided ids.forEach(function (id) { self.storage.removeItem(self.getIdPath(mapper, opts, id)) }) self.removeId(ids, mapper, opts) // afterDestroyAll lifecycle hook op = opts.op = 'afterDestroyAll' return self[op](mapper, query, opts, returnDeletedIds ? ids : undefined).then(function (_ids) { // Allow for re-assignment from lifecycle hook ids = isUndefined(_ids) && returnDeletedIds ? ids : _ids const result = { data: ids, deleted: records.length } return self.getRaw(opts) ? result : result.data }) }).then(success, failure) }) }) }, /** * TODO * * @name LocalStorageAdapter#ensureId * @method */ ensureId (id, mapper, opts) { const ids = this.getIds(mapper, opts) if (isArray(id)) { if (!id.length) { return } id.forEach(function (_id) { ids[_id] = 1 }) } else { ids[id] = 1 } this.saveKeys(ids, mapper, opts) }, /** * Retrieve the record with the given primary key. * * @name LocalStorageAdapter#find * @method * @param {Object} mapper The mapper. * @param {(string|number)} id Primary key of the record to retrieve. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @param {string[]} [opts.with=[]] TODO * @return {Promise} */ find (mapper, id, opts) { const self = this let record, op opts || (opts = {}) opts.with || (opts.with = []) // beforeFind lifecycle hook op = opts.op = 'beforeFind' return resolve(self[op](mapper, id, opts)).then(function () { op = opts.op = 'find' self.dbg(op, id, opts) const key = self.getIdPath(mapper, opts, id) record = self.storage.getItem(key) if (!record) { record = undefined return } record = fromJson(record) const tasks = [] forEachRelation(mapper, opts, function (def, __opts) { const relatedMapper = def.getRelation() let task if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { task = self.findAll(relatedMapper, { [def.foreignKey]: get(record, mapper.idAttribute) }, __opts).then(function (relatedItems) { if (def.type === 'hasOne' && relatedItems.length) { set(record, def.localField, relatedItems[0]) } else { set(record, def.localField, relatedItems) } return relatedItems }) } else if (def.type === 'hasMany' && def.localKeys) { let localKeys = [] let itemKeys = get(record, def.localKeys) || [] itemKeys = isArray(itemKeys) ? itemKeys : Object.keys(itemKeys) localKeys = localKeys.concat(itemKeys) task = self.findAll(relatedMapper, { where: { [relatedMapper.idAttribute]: { 'in': unique(localKeys).filter(function (x) { return x }) } } }, __opts).then(function (relatedItems) { set(record, def.localField, relatedItems) return relatedItems }) } else if (def.type === 'hasMany' && def.foreignKeys) { task = self.findAll(relatedMapper, { where: { [def.foreignKeys]: { 'contains': get(record, mapper.idAttribute) } } }, __opts).then(function (relatedItems) { set(record, def.localField, relatedItems) return relatedItems }) } else if (def.type === 'belongsTo') { task = self.find(relatedMapper, get(record, def.foreignKey), __opts).then(function (relatedItem) { set(record, def.localField, relatedItem) return relatedItem }) } if (task) { tasks.push(task) } }) return Promise.all(tasks) }).then(function () { // afterFind lifecycle hook op = opts.op = 'afterFind' return resolve(self[op](mapper, id, opts, record)).then(function (_record) { // Allow for re-assignment from lifecycle hook record = isUndefined(_record) ? record : _record return opts.raw ? { data: record, found: record ? 1 : 0 } : record }) }) }, /** * Retrieve the records that match the selection `query`. * * @name LocalStorageAdapter#findAll * @method * @param {Object} mapper The mapper. * @param {Object} query Selection query. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @param {string[]} [opts.with=[]] TODO * @return {Promise} */ findAll (mapper, query, opts) { const self = this let records = [] let op opts || (opts = {}) opts.with || (opts.with = []) // beforeFindAll lifecycle hook op = opts.op = 'beforeFindAll' return resolve(self[op](mapper, query, opts)).then(function () { op = opts.op = 'findAll' self.dbg(op, query, opts) // Load all records into memory... const ids = self.getIds(mapper, opts) forOwn(ids, function (value, id) { const json = self.storage.getItem(self.getIdPath(mapper, opts, id)) if (json) { records.push(fromJson(json)) } }) const idAttribute = mapper.idAttribute // TODO: Verify that this collection gets properly garbage collected // TODO: Or, find a way to filter without using Collection const _query = new Query({ index: { getAll () { return records } } }) records = _query.filter(query).run() const tasks = [] forEachRelation(mapper, opts, function (def, __opts) { const relatedMapper = def.getRelation() let task if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { task = self.findAll(relatedMapper, { where: { [def.foreignKey]: { 'in': records.map(function (item) { return get(item, idAttribute) }).filter(function (x) { return x }) } } }, __opts).then(function (relatedItems) { records.forEach(function (item) { const attached = [] relatedItems.forEach(function (relatedItem) { if (get(relatedItem, def.foreignKey) === get(item, idAttribute)) { attached.push(relatedItem) } }) if (def.type === 'hasOne' && attached.length) { set(item, def.localField, attached[0]) } else { set(item, def.localField, attached) } }) return relatedItems }) } else if (def.type === 'hasMany' && def.localKeys) { let localKeys = [] records.forEach(function (item) { let itemKeys = get(item, def.localKeys) || [] itemKeys = Array.isArray(itemKeys) ? itemKeys : Object.keys(itemKeys) localKeys = localKeys.concat(itemKeys) }) task = self.findAll(relatedMapper, { where: { [relatedMapper.idAttribute]: { 'in': unique(localKeys).filter(function (x) { return x }) } } }, __opts).then(function (relatedItems) { records.forEach(function (item) { const attached = [] let itemKeys = get(item, def.localKeys) || [] itemKeys = Array.isArray(itemKeys) ? itemKeys : Object.keys(itemKeys) relatedItems.forEach(function (relatedItem) { if (itemKeys && itemKeys.indexOf(relatedItem[relatedMapper.idAttribute]) !== -1) { attached.push(relatedItem) } }) set(item, def.localField, attached) }) return relatedItems }) } else if (def.type === 'belongsTo') { task = self.findAll(relatedMapper, { where: { [relatedMapper.idAttribute]: { 'in': records.map(function (item) { return get(item, def.foreignKey) }).filter(function (x) { return x }) } } }, __opts).then(function (relatedItems) { records.forEach(function (item) { relatedItems.forEach(function (relatedItem) { if (relatedItem[relatedMapper.idAttribute] === get(item, def.foreignKey)) { set(item, def.localField, relatedItem) } }) }) return relatedItems }) } if (task) { tasks.push(task) } }) return Promise.all(tasks) }).then(function () { // afterFindAll lifecycle hook op = opts.op = 'afterFindAll' return resolve(self[op](mapper, query, opts, records)).then(function (_records) { // Allow for re-assignment from lifecycle hook records = isUndefined(_records) ? records : _records return opts.raw ? { data: records, found: records.length } : records }) }) }, /** * TODO * * @name LocalStorageAdapter#getPath * @method */ getPath (mapper, opts) { opts = opts || {} return makePath(opts.basePath === undefined ? (mapper.basePath === undefined ? this.basePath : mapper.basePath) : opts.basePath, mapper.name) }, /** * TODO * * @name LocalStorageAdapter#getIdPath * @method */ getIdPath (mapper, opts, id) { opts = opts || {} return makePath(opts.basePath || this.basePath || mapper.basePath, mapper.endpoint, id) }, /** * TODO * * @name LocalStorageAdapter#getIds * @method */ getIds (mapper, opts) { let ids const idsPath = this.getPath(mapper, opts) const idsJson = this.storage.getItem(idsPath) if (idsJson) { ids = fromJson(idsJson) } else { ids = {} } return ids }, /** * TODO * * @name LocalStorageAdapter#getRaw * @method */ getRaw (opts) { opts || (opts = {}) return !!(isUndefined(opts.raw) ? this.raw : opts.raw) }, /** * TODO * * @name LocalStorageAdapter#log * @method */ log (level, ...args) { if (level && !args.length) { args.push(level) level = 'debug' } if (level === 'debug' && !this.debug) { return } const prefix = `${level.toUpperCase()}: (LocalStorageAdapter)` if (console[level]) { console[level](prefix, ...args) } else { console.log(prefix, ...args) } }, /** * TODO * * @name LocalStorageAdapter#removeId * @method */ removeId (id, mapper, opts) { const ids = this.getIds(mapper, opts) if (isArray(id)) { if (!id.length) { return } id.forEach(function (_id) { delete ids[_id] }) } else { delete ids[id] } this.saveKeys(ids, mapper, opts) }, /** * TODO * * @name LocalStorageAdapter#saveKeys * @method */ saveKeys (ids, mapper, opts) { ids = ids || {} const idsPath = this.getPath(mapper, opts) if (Object.keys(ids).length) { this.storage.setItem(idsPath, toJson(ids)) } else { this.storage.removeItem(idsPath) } }, /** * Update the records that match the selection `query`. If a record with the * specified primary key cannot be found then no update is performed and the * promise is resolved with `undefined`. * * @name LocalStorageAdapter#update * @method * @param {Object} mapper The mapper. * @param {(string|number)} id The primary key of the record to be updated. * @param {Object} props The update to apply to the record. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ update (mapper, id, props, opts) { const self = this props || (props = {}) opts || (opts = {}) return createTask(function (success, failure) { queueTask(function () { let op // beforeUpdate lifecycle hook op = opts.op = 'beforeUpdate' return resolve(self[op](mapper, id, props, opts)).then(function (_props) { // Allow for re-assignment from lifecycle hook props = isUndefined(_props) ? props : _props const key = self.getIdPath(mapper, opts, id) let record = self.storage.getItem(key) record = record ? fromJson(record) : undefined let updated = 0 // Update the record // TODO: Update related records when the "with" option is provided if (record) { deepMixIn(record, props) self.storage.setItem(key, toJson(record)) updated++ } else { throw new Error('Not Found') } // afterUpdate lifecycle hook op = opts.op = 'afterUpdate' return resolve(self[op](mapper, id, props, opts, record)).then(function (_record) { // Allow for re-assignment from lifecycle hook record = isUndefined(_record) ? record : _record return opts.raw ? { data: record, updated } : record }) }).then(success, failure) }) }) }, /** * Update the records that match the selection `query`. * * @name LocalStorageAdapter#updateAll * @method * @param {Object} mapper The mapper. * @param {Object} props The update to apply to the selected records. * @param {Object} query Selection query. * @param {Object} [opts] Configuration options. * @return {Promise} */ updateAll (mapper, props, query, opts) { const self = this props || (props = {}) query || (query = {}) opts || (opts = {}) return createTask(function (success, failure) { queueTask(function () { let op // beforeUpdateAll lifecycle hook op = opts.op = 'beforeUpdateAll' return resolve(self[op](mapper, props, query, opts)).then(function (_props) { // Allow for re-assignment from lifecycle hook props = isUndefined(_props) ? props : _props op = opts.op = 'updateAll' self.dbg(op, query, opts) // Find the records that are to be updated return self.findAll(mapper, query, opts) }).then(function (records) { const idAttribute = mapper.idAttribute let updated = 0 // Update each record // TODO: Update related records when the "with" option is provided records.forEach(function (record) { record || (record = {}) const id = get(record, idAttribute) const key = self.getIdPath(mapper, opts, id) deepMixIn(record, props) self.storage.setItem(key, toJson(record)) updated++ }) // afterUpdateAll lifecycle hook op = opts.op = 'afterUpdateAll' return self[op](mapper, props, query, opts, records).then(function (_records) { // Allow for re-assignment from lifecycle hook records = isUndefined(_records) ? records : _records return opts.raw ? { data: records, updated } : records }) }).then(success, failure) }) }) }, /** * Update the given records in a single batch. * * @name LocalStorageAdapter#updateMany * @method * @param {Object} mapper The mapper. * @param {Object} records The records to update. * @param {Object} [opts] Configuration options. * @param {boolean} [opts.raw=false] TODO * @return {Promise} */ updateMany (mapper, records, opts) { const self = this records || (records = []) opts || (opts = {}) return createTask(function (success, failure) { queueTask(function () { let op let updatedRecords = [] // beforeUpdateMany lifecycle hook op = opts.op = 'beforeUpdateMany' return resolve(self[op](mapper, records, opts)).then(function (_records) { // Allow for re-assignment from lifecycle hook records = isUndefined(_records) ? records : _records op = opts.op = 'updateMany' self.dbg(op, records, opts) const idAttribute = mapper.idAttribute // Update each record // TODO: Update related records when the "with" option is provided records.forEach(function (record) { if (!record) { return } const id = get(record, idAttribute) if (isUndefined(id)) { return } const key = self.getIdPath(mapper, opts, id) let json = self.storage.getItem(key) const existingRecord = json ? fromJson(json) : undefined if (!existingRecord) { return } deepMixIn(existingRecord, record) self.storage.setItem(key, toJson(existingRecord)) updatedRecords.push(existingRecord) }) // afterUpdateMany lifecycle hook op = opts.op = 'afterUpdateMany' return self[op](mapper, records, opts, updatedRecords).then(function (_records) { // Allow for re-assignment from lifecycle hook records = isUndefined(_records) ? updatedRecords : _records return opts.raw ? { data: records, updated: updatedRecords.length } : records }) }).then(success, failure) }) }) } }) /** * Details of the current version of the `js-data-localstorage` module. * * @name LocalStorageAdapter.version * @type {Object} * @property {string} version.full The full semver value. * @property {number} version.major The major version number. * @property {number} version.minor The minor version number. * @property {number} version.patch The patch version number. * @property {(string|boolean)} version.alpha The alpha version value, * otherwise `false` if the current version is not alpha. * @property {(string|boolean)} version.beta The beta version value, * otherwise `false` if the current version is not beta. */ LocalStorageAdapter.version = { full: '<%= pkg.version %>', major: parseInt('<%= major %>', 10), minor: parseInt('<%= minor %>', 10), patch: parseInt('<%= patch %>', 10), alpha: '<%= alpha %>' !== 'false' ? '<%= alpha %>' : false, beta: '<%= beta %>' !== 'false' ? '<%= beta %>' : false } /** * Registered as `js-data-localstorage` in NPM and Bower. * * __Script tag__: * ```javascript * window.LocalStorageAdapter * ``` * __CommonJS__: * ```javascript * var LocalStorageAdapter = require('js-data-localstorage') * ``` * __ES6 Modules__: * ```javascript * import LocalStorageAdapter from 'js-data-localstorage' * ``` * __AMD__: * ```javascript * define('myApp', ['js-data-localstorage'], function (LocalStorageAdapter) { ... }) * ``` * * @module js-data-localstorage */ module.exports = LocalStorageAdapter