diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..2dbad97 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..d23208f --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..56d7bc8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/queue-that-ts.iml b/.idea/queue-that-ts.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/queue-that-ts.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index e5b397c..aa8ca6f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ bootstrap: @npm install test: - @$(BIN)/standard @./node_modules/karma/bin/karma start --single-run=true watch: diff --git a/dist/global-variable-adapter.js b/dist/global-variable-adapter.js new file mode 100644 index 0000000..a429702 --- /dev/null +++ b/dist/global-variable-adapter.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createGlobalVariableAdapter = void 0; +const local_storage_adapter_1 = require("./local-storage-adapter"); +function createGlobalVariableAdapter(queueName) { + window.__queueThat__ = window.__queueThat__ || {}; + const localStorageAdapter = (0, local_storage_adapter_1.createLocalStorageAdapter)(queueName); + localStorageAdapter.save = save; + localStorageAdapter.load = load; + localStorageAdapter.remove = remove; + localStorageAdapter.type = 'globalVariable'; + return localStorageAdapter; + function save(key, data) { + window.__queueThat__[key] = String(data); + } + function load(key) { + return window.__queueThat__[key]; + } + function remove(key) { + delete window.__queueThat__[key]; + } +} +exports.createGlobalVariableAdapter = createGlobalVariableAdapter; diff --git a/dist/local-storage-adapter.js b/dist/local-storage-adapter.js new file mode 100644 index 0000000..c908b31 --- /dev/null +++ b/dist/local-storage-adapter.js @@ -0,0 +1,125 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createLocalStorageAdapter = void 0; +const QUEUE_KEY = '* - Queue'; +const ACTIVE_QUEUE_KEY = '* - Active Queue'; +const BACKOFF_TIME_KEY = '* - Backoff Time'; +const ERROR_COUNT_KEY = '* - Error Count'; +const QUEUE_PROCESSING_KEY = '* - Queue Processing'; +function createLocalStorageAdapter(queueName) { + const queueKey = QUEUE_KEY.replace('*', queueName); + const activeQueueKey = ACTIVE_QUEUE_KEY.replace('*', queueName); + const backoffTimeKey = BACKOFF_TIME_KEY.replace('*', queueName); + const errorCountKey = ERROR_COUNT_KEY.replace('*', queueName); + const queueProcessingKey = QUEUE_PROCESSING_KEY.replace('*', queueName); + let dirtyCache = true; + let setPending = false; + let queueCache = []; + const adapter = { + getQueue: getQueue, + setQueue: setQueue, + getErrorCount: getErrorCount, + getBackoffTime: getBackoffTime, + setErrorCount: setErrorCount, + setBackoffTime: setBackoffTime, + getActiveQueue: getActiveQueue, + setActiveQueue: setActiveQueue, + clearActiveQueue: clearActiveQueue, + getQueueProcessing: getQueueProcessing, + setQueueProcessing: setQueueProcessing, + save: save, + load: load, + works: works, + reset: reset, + remove: remove, + type: 'localStorage', + flush: flush + }; + return adapter; + function flush() { + dirtyCache = true; + if (setPending) { + adapter.save(queueKey, JSON.stringify(queueCache)); + setPending = false; + } + } + function getQueue() { + if (dirtyCache) { + queueCache = JSON.parse(adapter.load(queueKey) || '[]'); + dirtyCache = false; + setTimeout(flush, 0); + } + return queueCache; + } + function setQueue(queue) { + queueCache = queue; + dirtyCache = false; + setPending = true; + setTimeout(flush, 0); + } + function getErrorCount() { + const count = adapter.load(errorCountKey); + return count === undefined ? 0 : Number(count); + } + function getBackoffTime() { + const time = adapter.load(backoffTimeKey); + return time === undefined ? 0 : Number(time); + } + function setErrorCount(n) { + adapter.save(errorCountKey, n); + } + function setBackoffTime(n) { + adapter.save(backoffTimeKey, n); + } + function getActiveQueue() { + if (adapter.load(activeQueueKey) === undefined) { + return; + } + return JSON.parse(adapter.load(activeQueueKey)); + } + function setActiveQueue(id) { + adapter.save(activeQueueKey, JSON.stringify({ + id: id, + ts: now() + })); + } + function clearActiveQueue() { + adapter.remove(activeQueueKey); + } + function getQueueProcessing() { + return Boolean(Number(adapter.load(queueProcessingKey))); + } + function setQueueProcessing(isProcessing) { + adapter.save(queueProcessingKey, Number(isProcessing)); + } + function works() { + let works = false; + try { + adapter.save('queue-that-works', 'anything'); + works = adapter.load('queue-that-works') === 'anything'; + adapter.remove('queue-that-works'); + } + catch (e) { /* empty */ } + return works; + } + function reset() { + adapter.remove(activeQueueKey); + adapter.remove(backoffTimeKey); + adapter.remove(errorCountKey); + adapter.remove(queueKey); + adapter.remove(queueProcessingKey); + } +} +exports.createLocalStorageAdapter = createLocalStorageAdapter; +function save(key, data) { + window.localStorage[key] = String(data); +} +function load(key) { + return window.localStorage[key]; +} +function remove(key) { + window.localStorage.removeItem(key); +} +function now() { + return (new Date()).getTime(); +} diff --git a/dist/queue-that.js b/dist/queue-that.js new file mode 100644 index 0000000..7502c02 --- /dev/null +++ b/dist/queue-that.js @@ -0,0 +1,206 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const local_storage_adapter_1 = require("./local-storage-adapter"); +const global_variable_adapter_js_1 = require("./global-variable-adapter.js"); +const consola_1 = __importDefault(require("consola")); +const DEFAULT_QUEUE_LABEL = 'Queue That'; +const BACKOFF_TIME = 1000; +const QUEUE_GROUP_TIME = 100; +const PROCESS_TIMEOUT = 2000; +const DEFAULT_BATCH_SIZE = 20; +const ACTIVE_QUEUE_TIMEOUT = 2500; +function createQueueThat(options) { + if (!options.process) { + throw new Error('a process function is required'); + } + options.batchSize = options.batchSize || DEFAULT_BATCH_SIZE; + options.label = options.label || DEFAULT_QUEUE_LABEL; + options.trim = options.trim || identity; + options.queueGroupTime = options.queueGroupTime || QUEUE_GROUP_TIME; + options.backoffTime = options.backoffTime || BACKOFF_TIME; + options.processTimeout = options.processTimeout || PROCESS_TIMEOUT; + options.activeQueueTimeout = options.activeQueueTimeout || ACTIVE_QUEUE_TIMEOUT; + if (options.processTimeout > options.activeQueueTimeout) { + throw new Error('active queue timeout must be greater than process timeout'); + } + // eslint-disable-next-line prefer-const -- we need to assign to this later + let checkTimer = null; + let newQueueTimer = null; + let processTimer = null; + let flushTimer = null; + let processingTasks = false; + let checkScheduled = false; + const queueId = Math.random() + now(); + let flushScheduled = false; + let destroyed = false; + let storageAdapter = (0, local_storage_adapter_1.createLocalStorageAdapter)(options.label); + if (!storageAdapter.works()) { + storageAdapter = (0, global_variable_adapter_js_1.createGlobalVariableAdapter)(options.label); + } + queueThat.storageAdapter = storageAdapter; + queueThat.options = options; + queueThat.flush = flush; + queueThat.destroy = function destroy() { + destroyed = true; + if (checkTimer) + clearTimeout(checkTimer); + if (processTimer) + clearTimeout(processTimer); + if (newQueueTimer) + clearTimeout(newQueueTimer); + if (flushTimer) + clearTimeout(flushTimer); + }; + queueThat.flushQueueCache = queueThat.storageAdapter.flush; + deactivateOnUnload(queueId); + consola_1.default.info('Initialized with queue ID ' + queueId); + checkQueueDebounce(); + /** + * This check is in case the queue is initialised quickly after + * the queue from the previous page expires. + */ + newQueueTimer = setTimeout(checkQueue, ACTIVE_QUEUE_TIMEOUT); + return queueThat; + function queueThat(item) { + const queue = storageAdapter.getQueue(); + queue.push(item); + storageAdapter.setQueue(options.trim(queue)); + consola_1.default.info('Item queued'); + checkQueueDebounce(); + } + function flush() { + if (flushScheduled) + return; + checkScheduled = true; + flushScheduled = true; + if (checkTimer) + clearTimeout(checkTimer); + flushTimer = setTimeout(function checkQueueAndReset() { + checkQueue(); + checkScheduled = false; + flushScheduled = false; + }); + } + function checkQueueDebounce() { + if (checkScheduled) + return; + checkScheduled = true; + checkTimer = setTimeout(function checkQueueAndReset() { + checkQueue(); + checkScheduled = false; + }, options.queueGroupTime); + } + function checkQueue() { + consola_1.default.info('Checking queue'); + if (processingTasks) + return; + const backoffTime = storageAdapter.getBackoffTime() - now(); + if (backoffTime > 0) { + setTimeout(checkQueue, backoffTime); + return; + } + const lastActiveQueue = getLastActiveQueueInfo(); + if (lastActiveQueue.active && lastActiveQueue.id !== queueId) + return; + if (lastActiveQueue.id !== queueId) + consola_1.default.info('Switching active queue to ' + queueId); + // Need to always do this to keep active + storageAdapter.setActiveQueue(queueId); + const batch = storageAdapter.getQueue().slice(0, options.batchSize); + if (batch.length === 0) { + return; + } + const batchContainer = { + containsRepeatedItems: storageAdapter.getQueueProcessing(), + batch: batch + }; + consola_1.default.info('Processing queue batch of ' + batch.length + ' items'); + if (batchContainer.containsRepeatedItems) + consola_1.default.info('Batch contains repeated items'); + else + consola_1.default.info('Batch does not contain repeated items'); + const itemsProcessing = batch.length; + let timeout = false; + let finished = false; + options.process(batch, function (err) { + if (timeout || destroyed) + return; + processingTasks = false; + finished = true; + if (err) { + processError(err); + checkQueueDebounce(); + return; + } + storageAdapter.setErrorCount(0); + const queue = rest(storageAdapter.getQueue(), itemsProcessing); + storageAdapter.setQueue(queue); + storageAdapter.setQueueProcessing(false); + storageAdapter.flush(); + consola_1.default.info('Queue processed, ' + queue.length + ' remaining items'); + checkQueueDebounce(); + }); + processTimer = setTimeout(function () { + if (finished || destroyed) + return; + timeout = true; + processingTasks = false; + processError(new Error('Task timeout')); + }, options.processTimeout); + processingTasks = true; + storageAdapter.setQueueProcessing(true); + storageAdapter.flush(); + } + function processError(err) { + consola_1.default.error('Process error, backing off (' + err.message + ')'); + const errorCount = storageAdapter.getErrorCount() + 1; + storageAdapter.setErrorCount(errorCount); + storageAdapter.setBackoffTime(now() + options.backoffTime * Math.pow(2, errorCount - 1)); + consola_1.default.warn('backoff time ' + (storageAdapter.getBackoffTime() - now()) + 'ms'); + } + function getLastActiveQueueInfo() { + const info = { + active: false + }; + const activeinstance = storageAdapter.getActiveQueue(); + if (activeinstance === undefined) { + info.active = false; + return info; + } + info.id = activeinstance.id; + const timeSinceActive = now() - activeinstance.ts; + info.active = !(timeSinceActive >= ACTIVE_QUEUE_TIMEOUT); + return info; + } + function now() { + return (new Date()).getTime(); + } + /** + * Deactivating the queue on beforeunload is not + * necessary but is better/quicker than waiting for a + * few seconds for the queue to be unresponsive. + */ + function deactivateOnUnload(queueId) { + if (window.addEventListener) { + window.addEventListener('beforeunload', deactivate); + } + function deactivate() { + const activeQueue = storageAdapter.getActiveQueue(); + if (activeQueue && activeQueue.id === queueId) { + queueThat.destroy(); + storageAdapter.clearActiveQueue(); + consola_1.default.info('deactivated on page unload'); + } + } + } +} +exports.default = createQueueThat; +function identity(input) { + return input; +} +function rest(array, n) { + return Array.prototype.slice.call(array, n); +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..197aeef --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,8 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config(eslint.configs.recommended, + ...tseslint.configs.recommended +); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 8f3f82e..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['mocha', 'expect', 'sinon'], - files: [ - './node_modules/sinon/pkg/sinon-ie-1.12.2.js', - './test/attach-event-ie.js', - 'test/**/test-*.js' - ], - exclude: [ - 'karma.conf.js' - ], - preprocessors: { - 'test/**/test-*.js': ['webpack', 'sourcemap'] - }, - reporters: ['spec'], - logLevel: config.LOG_INFO, - browsers: ['Chrome'], - webpack: { - devtool: 'inline-source-map' - }, - webpackServer: { - noInfo: true - } - }) -} diff --git a/lib/global-variable-adapter.js b/lib/global-variable-adapter.ts similarity index 52% rename from lib/global-variable-adapter.js rename to lib/global-variable-adapter.ts index 9ef11e3..695ec66 100644 --- a/lib/global-variable-adapter.js +++ b/lib/global-variable-adapter.ts @@ -1,11 +1,12 @@ -var createLocalStorageAdapter = require('./local-storage-adapter') +import { createLocalStorageAdapter } from './local-storage-adapter' -module.exports = createGlobalVariableAdapter -function createGlobalVariableAdapter (queueName) { + + +export function createGlobalVariableAdapter(queueName: string) { window.__queueThat__ = window.__queueThat__ || {} - var localStorageAdapter = createLocalStorageAdapter(queueName) + const localStorageAdapter = createLocalStorageAdapter(queueName) localStorageAdapter.save = save localStorageAdapter.load = load localStorageAdapter.remove = remove @@ -13,15 +14,15 @@ function createGlobalVariableAdapter (queueName) { return localStorageAdapter - function save (key, data) { + function save (key: string, data: string|number|boolean|object) { window.__queueThat__[key] = String(data) } - function load (key) { + function load (key:string) { return window.__queueThat__[key] } - function remove (key) { + function remove (key:string) { delete window.__queueThat__[key] } } diff --git a/lib/local-storage-adapter.js b/lib/local-storage-adapter.ts similarity index 57% rename from lib/local-storage-adapter.js rename to lib/local-storage-adapter.ts index 6e9b600..7a0d336 100644 --- a/lib/local-storage-adapter.js +++ b/lib/local-storage-adapter.ts @@ -1,25 +1,21 @@ -var json = require('json-bourne') - -var QUEUE_KEY = '* - Queue' -var ACTIVE_QUEUE_KEY = '* - Active Queue' -var BACKOFF_TIME_KEY = '* - Backoff Time' -var ERROR_COUNT_KEY = '* - Error Count' -var QUEUE_PROCESSING_KEY = '* - Queue Processing' - -module.exports = createLocalStorageAdapter - -function createLocalStorageAdapter (queueName) { - var queueKey = QUEUE_KEY.replace('*', queueName) - var activeQueueKey = ACTIVE_QUEUE_KEY.replace('*', queueName) - var backoffTimeKey = BACKOFF_TIME_KEY.replace('*', queueName) - var errorCountKey = ERROR_COUNT_KEY.replace('*', queueName) - var queueProcessingKey = QUEUE_PROCESSING_KEY.replace('*', queueName) - - var dirtyCache = true - var setPending = false - var queueCache = [] - - var adapter = { +const QUEUE_KEY = '* - Queue'; +const ACTIVE_QUEUE_KEY = '* - Active Queue' +const BACKOFF_TIME_KEY = '* - Backoff Time' +const ERROR_COUNT_KEY = '* - Error Count' +const QUEUE_PROCESSING_KEY = '* - Queue Processing' + +export function createLocalStorageAdapter(queueName: string) { + const queueKey = QUEUE_KEY.replace('*', queueName) + const activeQueueKey = ACTIVE_QUEUE_KEY.replace('*', queueName) + const backoffTimeKey = BACKOFF_TIME_KEY.replace('*', queueName) + const errorCountKey = ERROR_COUNT_KEY.replace('*', queueName) + const queueProcessingKey = QUEUE_PROCESSING_KEY.replace('*', queueName) + + let dirtyCache = true + let setPending = false + let queueCache: T[] = [] + + const adapter = { getQueue: getQueue, setQueue: setQueue, getErrorCount: getErrorCount, @@ -45,21 +41,21 @@ function createLocalStorageAdapter (queueName) { function flush () { dirtyCache = true if (setPending) { - adapter.save(queueKey, json.stringify(queueCache)) + adapter.save(queueKey, JSON.stringify(queueCache)) setPending = false } } - function getQueue () { + function getQueue () : T[]{ if (dirtyCache) { - queueCache = json.parse(adapter.load(queueKey) || '[]') + queueCache = JSON.parse(adapter.load(queueKey) || '[]') dirtyCache = false setTimeout(flush, 0) } return queueCache } - function setQueue (queue) { + function setQueue (queue: T[]) { queueCache = queue dirtyCache = false setPending = true @@ -67,20 +63,20 @@ function createLocalStorageAdapter (queueName) { } function getErrorCount () { - var count = adapter.load(errorCountKey) + const count = adapter.load(errorCountKey) return count === undefined ? 0 : Number(count) } function getBackoffTime () { - var time = adapter.load(backoffTimeKey) + const time = adapter.load(backoffTimeKey) return time === undefined ? 0 : Number(time) } - function setErrorCount (n) { + function setErrorCount (n: number) { adapter.save(errorCountKey, n) } - function setBackoffTime (n) { + function setBackoffTime (n: number) { adapter.save(backoffTimeKey, n) } @@ -88,11 +84,11 @@ function createLocalStorageAdapter (queueName) { if (adapter.load(activeQueueKey) === undefined) { return } - return json.parse(adapter.load(activeQueueKey)) + return JSON.parse(adapter.load(activeQueueKey)) } - function setActiveQueue (id) { - adapter.save(activeQueueKey, json.stringify({ + function setActiveQueue (id: number) { + adapter.save(activeQueueKey, JSON.stringify({ id: id, ts: now() })) @@ -106,17 +102,17 @@ function createLocalStorageAdapter (queueName) { return Boolean(Number(adapter.load(queueProcessingKey))) } - function setQueueProcessing (isProcessing) { + function setQueueProcessing (isProcessing:boolean) { adapter.save(queueProcessingKey, Number(isProcessing)) } function works () { - var works = false + let works = false try { adapter.save('queue-that-works', 'anything') works = adapter.load('queue-that-works') === 'anything' adapter.remove('queue-that-works') - } catch (e) {} + } catch (e) { /* empty */ } return works } @@ -129,18 +125,25 @@ function createLocalStorageAdapter (queueName) { } } -function save (key, data) { - window.localStorage[key] = data + + + + + + +function save (key: string, data: string|number|boolean|object) { + window.localStorage[key] = String(data) } -function load (key) { +function load (key: string) { return window.localStorage[key] } -function remove (key) { +function remove (key: string) { window.localStorage.removeItem(key) } -function now () { +function now () : number{ return (new Date()).getTime() } + diff --git a/lib/log.js b/lib/log.js deleted file mode 100644 index ceea9a0..0000000 --- a/lib/log.js +++ /dev/null @@ -1,2 +0,0 @@ -var createLogger = require('driftwood') -module.exports = createLogger('queue-that') diff --git a/lib/queue-that.js b/lib/queue-that.js deleted file mode 100644 index e29320e..0000000 --- a/lib/queue-that.js +++ /dev/null @@ -1,219 +0,0 @@ -var log = require('./log') -var createLocalStorageAdapter = require('./local-storage-adapter') -var createGlobalVariableAdapter = require('./global-variable-adapter') - -var DEFAULT_QUEUE_LABEL = 'Queue That' -var BACKOFF_TIME = 1000 -var QUEUE_GROUP_TIME = 100 -var PROCESS_TIMEOUT = 2000 -var DEFAULT_BATCH_SIZE = 20 -var ACTIVE_QUEUE_TIMEOUT = 2500 - -module.exports = createQueueThat - -function createQueueThat (options) { - if (!options.process) { - throw new Error('a process function is required') - } - options.batchSize = options.batchSize || DEFAULT_BATCH_SIZE - options.label = options.label || DEFAULT_QUEUE_LABEL - options.trim = options.trim || identity - options.queueGroupTime = options.queueGroupTime || QUEUE_GROUP_TIME - options.backoffTime = options.backoffTime || BACKOFF_TIME - options.processTimeout = options.processTimeout || PROCESS_TIMEOUT - options.activeQueueTimeout = options.activeQueueTimeout || ACTIVE_QUEUE_TIMEOUT - - if (options.processTimeout > options.activeQueueTimeout) { - throw new Error('active queue timeout must be greater than process timeout') - } - - var checkTimer, processTimer, newQueueTimer, flushTimer - var processingTasks = false - var checkScheduled = false - var queueId = Math.random() + now() - var flushScheduled = false - var destroyed = false - - var storageAdapter = createLocalStorageAdapter(options.label) - if (!storageAdapter.works()) { - storageAdapter = createGlobalVariableAdapter(options.label) - } - - queueThat.storageAdapter = storageAdapter - queueThat.options = options - queueThat.flush = flush - queueThat.destroy = function destroy () { - destroyed = true - clearTimeout(checkTimer) - clearTimeout(processTimer) - clearTimeout(newQueueTimer) - clearTimeout(flushTimer) - } - queueThat.flushQueueCache = queueThat.storageAdapter.flush - deactivateOnUnload(queueId) - - log.info('Initialized with queue ID ' + queueId) - - checkQueueDebounce() - /** - * This check is in case the queue is initialised quickly after - * the queue from the previous page expires. - */ - newQueueTimer = setTimeout(checkQueue, ACTIVE_QUEUE_TIMEOUT) - - return queueThat - - function queueThat (item) { - var queue = storageAdapter.getQueue() - queue.push(item) - storageAdapter.setQueue(options.trim(queue)) - - log.info('Item queued') - - checkQueueDebounce() - } - - function flush () { - if (flushScheduled) return - - checkScheduled = true - flushScheduled = true - clearTimeout(checkTimer) - - flushTimer = setTimeout(function checkQueueAndReset () { - checkQueue() - checkScheduled = false - flushScheduled = false - }) - } - - function checkQueueDebounce () { - if (checkScheduled) return - checkScheduled = true - checkTimer = setTimeout(function checkQueueAndReset () { - checkQueue() - checkScheduled = false - }, options.queueGroupTime) - } - - function checkQueue () { - log.info('Checking queue') - - if (processingTasks) return - - var backoffTime = storageAdapter.getBackoffTime() - now() - if (backoffTime > 0) { - setTimeout(checkQueue, backoffTime) - return - } - - var lastActiveQueue = getLastActiveQueueInfo() - if (lastActiveQueue.active && lastActiveQueue.id !== queueId) return - if (lastActiveQueue.id !== queueId) log.info('Switching active queue to ' + queueId) - - // Need to always do this to keep active - storageAdapter.setActiveQueue(queueId) - - var batch = storageAdapter.getQueue().slice(0, options.batchSize) - if (batch.length === 0) { - return - } - - log.info('Processing queue batch of ' + batch.length + ' items') - batch.containsRepeatedItems = storageAdapter.getQueueProcessing() - if (batch.containsRepeatedItems) log.info('Batch contains repeated items') - else log.info('Batch does not contain repeated items') - - var itemsProcessing = batch.length - var timeout = false - var finished = false - - options.process(batch, function (err) { - if (timeout || destroyed) return - processingTasks = false - finished = true - if (err) { - processError(err) - checkQueueDebounce() - return - } - - storageAdapter.setErrorCount(0) - var queue = rest(storageAdapter.getQueue(), itemsProcessing) - storageAdapter.setQueue(queue) - - storageAdapter.setQueueProcessing(false) - storageAdapter.flush() - - log.info('Queue processed, ' + queue.length + ' remaining items') - - checkQueueDebounce() - }) - - processTimer = setTimeout(function () { - if (finished || destroyed) return - timeout = true - processingTasks = false - processError(new Error('Task timeout')) - }, options.processTimeout) - - processingTasks = true - storageAdapter.setQueueProcessing(true) - storageAdapter.flush() - } - - function processError (err) { - log.error('Process error, backing off (' + err.message + ')') - var errorCount = storageAdapter.getErrorCount() + 1 - storageAdapter.setErrorCount(errorCount) - storageAdapter.setBackoffTime(now() + options.backoffTime * Math.pow(2, errorCount - 1)) - log.warn('backoff time ' + (storageAdapter.getBackoffTime() - now()) + 'ms') - } - - function getLastActiveQueueInfo () { - var info = {} - var activeinstance = storageAdapter.getActiveQueue() - if (activeinstance === undefined) { - info.active = false - return info - } - info.id = activeinstance.id - var timeSinceActive = now() - activeinstance.ts - info.active = !(timeSinceActive >= ACTIVE_QUEUE_TIMEOUT) - return info - } - - function now () { - return (new Date()).getTime() - } - - /** - * Deactivating the queue on beforeunload is not - * necessary but is better/quicker than waiting for a - * few seconds for the queue to be unresponsive. - */ - function deactivateOnUnload (queueId) { - if (window.addEventListener) { - window.addEventListener('beforeunload', deactivate) - } else if (window.attachEvent) { - window.attachEvent('onbeforeunload', deactivate) - } - - function deactivate () { - var activeQueue = storageAdapter.getActiveQueue() - if (activeQueue && activeQueue.id === queueId) { - queueThat.destroy() - storageAdapter.clearActiveQueue() - log.info('deactivated on page unload') - } - } - } -} - -function identity (input) { - return input -} - -function rest (array, n) { - return Array.prototype.slice.call(array, n) -} diff --git a/lib/queue-that.ts b/lib/queue-that.ts new file mode 100644 index 0000000..f97c0e2 --- /dev/null +++ b/lib/queue-that.ts @@ -0,0 +1,251 @@ +import {createLocalStorageAdapter} from "./local-storage-adapter"; +import {createGlobalVariableAdapter} from "./global-variable-adapter.js"; +import consola from "consola"; + +const DEFAULT_QUEUE_LABEL = 'Queue That' +const BACKOFF_TIME = 1000 +const QUEUE_GROUP_TIME = 100 +const PROCESS_TIMEOUT = 2000 +const DEFAULT_BATCH_SIZE = 20 +const ACTIVE_QUEUE_TIMEOUT = 2500 + + +declare global { + interface Window { + __queueThat__: Record + } +} + +export interface QueueOptions { + process: (batch: T[], callback: (err?: Error) => void) => void + batchSize?: number + label?: string + trim?: (queue: T[]) => T[] + queueGroupTime?: number + backoffTime?: number + processTimeout?: number + activeQueueTimeout?: number +} + +export default function createQueueThat (options: QueueOptions) { + if (!options.process) { + throw new Error('a process function is required') + } + options.batchSize = options.batchSize || DEFAULT_BATCH_SIZE + options.label = options.label || DEFAULT_QUEUE_LABEL + options.trim = options.trim || identity + options.queueGroupTime = options.queueGroupTime || QUEUE_GROUP_TIME + options.backoffTime = options.backoffTime || BACKOFF_TIME + options.processTimeout = options.processTimeout || PROCESS_TIMEOUT + options.activeQueueTimeout = options.activeQueueTimeout || ACTIVE_QUEUE_TIMEOUT + + if (options.processTimeout > options.activeQueueTimeout) { + throw new Error('active queue timeout must be greater than process timeout') + } + + // eslint-disable-next-line prefer-const -- we need to assign to this later + let checkTimer: number | null = null; + let newQueueTimer: number | null = null; + let processTimer: number | null = null; + let flushTimer: number | null = null; + let processingTasks = false + let checkScheduled = false + const queueId = Math.random() + now() + let flushScheduled = false + let destroyed = false + + let storageAdapter = createLocalStorageAdapter(options.label) + if (!storageAdapter.works()) { + storageAdapter = createGlobalVariableAdapter(options.label) + } + + queueThat.storageAdapter = storageAdapter + queueThat.options = options + queueThat.flush = flush + queueThat.destroy = function destroy () { + destroyed = true + if(checkTimer) clearTimeout(checkTimer) + if(processTimer) clearTimeout(processTimer) + if(newQueueTimer) clearTimeout(newQueueTimer) + if(flushTimer) clearTimeout(flushTimer) + } + queueThat.flushQueueCache = queueThat.storageAdapter.flush + deactivateOnUnload(queueId) + + consola.info('Initialized with queue ID ' + queueId) + + checkQueueDebounce() + /** + * This check is in case the queue is initialised quickly after + * the queue from the previous page expires. + */ + newQueueTimer = setTimeout(checkQueue, ACTIVE_QUEUE_TIMEOUT) + + return queueThat + + function queueThat (item: T) { + const queue = storageAdapter.getQueue() + queue.push(item) + storageAdapter.setQueue(options.trim!(queue)) + + consola.info('Item queued') + + checkQueueDebounce() + } + + function flush () { + if (flushScheduled) return + + checkScheduled = true + flushScheduled = true + if(checkTimer) clearTimeout(checkTimer) + + flushTimer = setTimeout(function checkQueueAndReset () { + checkQueue() + checkScheduled = false + flushScheduled = false + }) + } + + function checkQueueDebounce () { + if (checkScheduled) return + checkScheduled = true + checkTimer = setTimeout(function checkQueueAndReset () { + checkQueue() + checkScheduled = false + }, options.queueGroupTime!) + } + + function checkQueue () { + consola.info('Checking queue') + + if (processingTasks) return + + const backoffTime = storageAdapter.getBackoffTime() - now() + if (backoffTime > 0) { + setTimeout(checkQueue, backoffTime) + return + } + + const lastActiveQueue = getLastActiveQueueInfo() + if (lastActiveQueue.active && lastActiveQueue.id !== queueId) return + if (lastActiveQueue.id !== queueId) consola.info('Switching active queue to ' + queueId) + + // Need to always do this to keep active + storageAdapter.setActiveQueue(queueId) + + const batch = storageAdapter.getQueue().slice(0, options.batchSize!) + if (batch.length === 0) { + return + } + + const batchContainer: { + containsRepeatedItems: boolean, + batch: T[] + } = { + containsRepeatedItems: storageAdapter.getQueueProcessing(), + batch: batch + } + + consola.info('Processing queue batch of ' + batch.length + ' items') + if (batchContainer.containsRepeatedItems) consola.info('Batch contains repeated items') + else consola.info('Batch does not contain repeated items') + + const itemsProcessing = batch.length + let timeout = false + let finished = false + + options.process(batch, function (err?: Error) { + if (timeout || destroyed) return + processingTasks = false + finished = true + if (err) { + processError(err) + checkQueueDebounce() + return + } + + storageAdapter.setErrorCount(0) + const queue = rest(storageAdapter.getQueue(), itemsProcessing) + storageAdapter.setQueue(queue) + + storageAdapter.setQueueProcessing(false) + storageAdapter.flush() + + consola.info('Queue processed, ' + queue.length + ' remaining items') + + checkQueueDebounce() + }) + + processTimer = setTimeout(function () { + if (finished || destroyed) return + timeout = true + processingTasks = false + processError(new Error('Task timeout')) + }, options.processTimeout!) + + processingTasks = true + storageAdapter.setQueueProcessing(true) + storageAdapter.flush() + } + + function processError (err: Error) { + consola.error('Process error, backing off (' + err.message + ')') + const errorCount = storageAdapter.getErrorCount() + 1 + storageAdapter.setErrorCount(errorCount) + storageAdapter.setBackoffTime(now() + options.backoffTime! * Math.pow(2, errorCount - 1)) + consola.warn('backoff time ' + (storageAdapter.getBackoffTime() - now()) + 'ms') + } + + function getLastActiveQueueInfo () { + const info: { + id?: number + active: boolean + } = { + active: false + } + const activeinstance = storageAdapter.getActiveQueue() + if (activeinstance === undefined) { + info.active = false + return info + } + info.id = activeinstance.id + const timeSinceActive = now() - activeinstance.ts + info.active = !(timeSinceActive >= ACTIVE_QUEUE_TIMEOUT) + return info + } + + function now () { + return (new Date()).getTime() + } + + /** + * Deactivating the queue on beforeunload is not + * necessary but is better/quicker than waiting for a + * few seconds for the queue to be unresponsive. + */ + function deactivateOnUnload (queueId: number) { + if (window.addEventListener) { + window.addEventListener('beforeunload', deactivate) + } + + function deactivate () { + const activeQueue = storageAdapter.getActiveQueue() + if (activeQueue && activeQueue.id === queueId) { + queueThat.destroy() + storageAdapter.clearActiveQueue() + consola.info('deactivated on page unload') + } + } + } + + function identity (input: T[]) { + return input + } + + function rest(array: T[], n: number) { + return Array.prototype.slice.call(array, n) + } + +} + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0d4fe3c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1639 @@ +{ + "name": "queue-that", + "version": "2.9.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "queue-that", + "version": "2.9.0", + "license": "ISC", + "dependencies": { + "consola": "^3.2.3", + "underscore": "^1.13.6" + }, + "devDependencies": { + "@eslint/js": "^9.7.0", + "@types/eslint__js": "^8.42.3", + "eslint": "^8.57.0", + "typescript": "^5.5.3", + "typescript-eslint": "^7.16.1" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", + "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "dependencies": { + "@types/eslint": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", + "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/type-utils": "7.16.1", + "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", + "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/typescript-estree": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", + "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", + "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.16.1", + "@typescript-eslint/utils": "7.16.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", + "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", + "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", + "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/typescript-estree": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", + "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.16.1.tgz", + "integrity": "sha512-889oE5qELj65q/tGeOSvlreNKhimitFwZqQ0o7PcWC7/lgRkAMknznsCsV8J8mZGTP/Z+cIbX8accf2DE33hrA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.16.1", + "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/utils": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index f3b75b8..218ef8b 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,11 @@ "name": "queue-that", "version": "2.9.0", "description": "A queue managed in localStorage for async tasks that may run immediately before page unload.", - "main": "lib/queue-that.js", + "main": "lib/queue-that.ts", + "type": "module", "scripts": { - "test": "make test" + "test": "make test", + "dev": "tsc -w" }, "repository": { "type": "git", @@ -20,24 +22,14 @@ }, "homepage": "https://github.com/QubitProducts/queue-that#readme", "dependencies": { - "driftwood": "^1.1.0", - "json-bourne": "^1.0.0" + "consola": "^3.2.3", + "underscore": "^1.13.6" }, "devDependencies": { - "inject-loader": "^2.0.1", - "karma": "^0.13.15", - "karma-chrome-launcher": "^2.0.0", - "karma-expect": "^1.1.1", - "karma-mocha": "^0.2.1", - "karma-safari-private-launcher": "^1.0.0", - "karma-sinon": "^1.0.4", - "karma-sourcemap-loader": "^0.3.6", - "karma-spec-reporter": "0.0.23", - "karma-webpack": "^1.7.0", - "phantomjs-prebuilt": "^2.1.6", - "sinon": "1.17.3", - "standard": "^8.0.0", - "underscore": "^1.8.3", - "webpack": "^1.12.9" + "@eslint/js": "^9.7.0", + "@types/eslint__js": "^8.42.3", + "eslint": "^8.57.0", + "typescript": "^5.5.3", + "typescript-eslint": "^7.16.1" } } diff --git a/test/attach-event-ie.js b/test/attach-event-ie.js deleted file mode 100644 index b0a5c7b..0000000 --- a/test/attach-event-ie.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable no-inner-declarations, no-unused-vars */ -/** - * Allows attachEvent to be stubbed in IE8. - */ -var __attachEvent__ = window.attachEvent -if (window.attachEvent) { - function attachEvent () {} - window.attachEvent = __attachEvent__ -} diff --git a/test/functional/test-queue-that-functional.js b/test/functional/test-queue-that-functional.js deleted file mode 100644 index a1a7871..0000000 --- a/test/functional/test-queue-that-functional.js +++ /dev/null @@ -1,366 +0,0 @@ -/* global describe, it, expect, beforeEach, afterEach, sinon */ -var _ = require('underscore') -var createQueueThat = require('../../lib/queue-that') -describe('queueThat (functional)', function () { - var queueThat - - beforeEach(function () { - queueThat = createQueueThat({ - process: sinon.stub(), - label: 'A label' - }) - }) - - afterEach(function () { - queueThat.destroy() - queueThat.storageAdapter.reset() - }) - - it('should debounce tasks', function (done) { - queueThat.options.process = sinon.spy(function (items) { - expect(items).to.eql(arrayWithoutRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }, { - task: 'c' - }])) - done() - }) - - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - setTimeout(function () { - queueThat({ - task: 'c' - }) - }, 10) - }) - - it('should flush tasks when flush is called', function (done) { - var callCount = 0 - queueThat.options.process = sinon.spy(function (items, next) { - callCount++ - if (callCount === 1) { - expect(items).to.eql(arrayWithoutRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }])) - setTimeout(next, 50) - } - - if (callCount === 2) { - expect(items).to.eql(arrayWithoutRepeatedItems([{ - task: 'c' - }])) - done() - } - }) - - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - queueThat.flush() - - setTimeout(function () { - queueThat({ - task: 'c' - }) - }, 10) - }) - - it('should batch tasks', function (done) { - queueThat.options.batchSize = 4 - - queueThat.options.process = sinon.spy(function (items, next) { - if (queueThat.options.process.callCount === 2) { - check() - } else { - setTimeout(next, 10) - } - }) - - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - setTimeout(function () { - queueThat({ - task: 'c' - }) - queueThat({ - task: 'd' - }) - queueThat({ - task: 'e' - }) - }, 10) - - function check () { - expect(queueThat.options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }, { - task: 'c' - }, { - task: 'd' - }])) - - expect(queueThat.options.process.getCall(1).args[0]).to.eql(arrayWithoutRepeatedItems([{ - task: 'e' - }])) - - done() - } - }) - - it('should retry tasks', function (done) { - queueThat.options.batchSize = 4 - - queueThat.options.process = sinon.spy(function (items, next) { - if (queueThat.options.process.callCount === 1) { - return setTimeout(function () { - next(new Error('Failed')) - }, 10) - } - if (queueThat.options.process.callCount === 3) { - check() - } else { - setTimeout(next, 10) - } - }) - - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - setTimeout(function () { - queueThat({ - task: 'c' - }) - queueThat({ - task: 'd' - }) - queueThat({ - task: 'e' - }) - }, 10) - - function check () { - expect(queueThat.options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }, { - task: 'c' - }, { - task: 'd' - }])) - - expect(queueThat.options.process.getCall(1).args[0]).to.eql(arrayWithRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }, { - task: 'c' - }, { - task: 'd' - }])) - - expect(queueThat.options.process.getCall(2).args[0]).to.eql(arrayWithoutRepeatedItems([{ - task: 'e' - }])) - - done() - } - }) - - describe('with two queues', function () { - var anotherQueueThat - beforeEach(function () { - anotherQueueThat = createQueueThat({ - label: 'Another label', - process: sinon.stub() - }) - }) - - afterEach(function () { - anotherQueueThat.destroy() - anotherQueueThat.storageAdapter.reset() - }) - - it('should work with two queues on the page', function (done) { - this.timeout(10000) - queueThat.options.process = sinon.spy(function (items, next) { - if (queueThat.options.process.callCount !== 3) { - return setTimeout(function () { - next(new Error('Failed')) - }, 10) - } else { - check() - } - }) - - anotherQueueThat.options.process = sinon.spy(function (items, next) { - if (anotherQueueThat.options.process.callCount === 1) { - return setTimeout(function () { - next(new Error('Failed')) - }, 10) - } else { - check() - } - }) - - queueThat('A') - queueThat('B') - queueThat('C') - - setTimeout(function () { - queueThat('D') - queueThat('E') - - anotherQueueThat('F') - anotherQueueThat('G') - anotherQueueThat('H') - anotherQueueThat('I') - anotherQueueThat('J') - }) - - function check () { - var queueThatDone = queueThat.options.process.callCount === 3 - var anotherQueueThatDone = anotherQueueThat.options.process.callCount === 2 - if (queueThatDone && anotherQueueThatDone) { - expect(queueThat.options.process.getCall(0).args[0]).to.eql( - arrayWithoutRepeatedItems(['A', 'B', 'C', 'D', 'E']) - ) - expect(queueThat.options.process.getCall(1).args[0]).to.eql( - arrayWithRepeatedItems(['A', 'B', 'C', 'D', 'E']) - ) - expect(queueThat.options.process.getCall(2).args[0]).to.eql( - arrayWithRepeatedItems(['A', 'B', 'C', 'D', 'E']) - ) - - expect(anotherQueueThat.options.process.getCall(0).args[0]).to.eql( - arrayWithoutRepeatedItems(['F', 'G', 'H', 'I', 'J']) - ) - expect(anotherQueueThat.options.process.getCall(1).args[0]).to.eql( - arrayWithRepeatedItems(['F', 'G', 'H', 'I', 'J']) - ) - done() - } - } - }) - }) - - it('should trim tasks', function (done) { - queueThat.options.trim = function (items) { - return _.filter(items, function (item) { - return item.task !== 'b' - }) - } - - queueThat.options.process = sinon.spy(function (items) { - expect(items).to.eql(arrayWithoutRepeatedItems([{ - task: 'a' - }, { - task: 'c' - }])) - done() - }) - - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - setTimeout(function () { - queueThat({ - task: 'b' - }) - queueThat({ - task: 'b' - }) - queueThat({ - task: 'c' - }) - }, 10) - }) - - describe('after a page is closed', function () { - var freshQueueThat - beforeEach(function (done) { - this.timeout(5000) - queueThat({ - task: 'a' - }) - - queueThat({ - task: 'b' - }) - - setTimeout(function () { - queueThat.destroy() - }, 200) - - setTimeout(function () { - freshQueueThat = createQueueThat({ - process: sinon.stub(), - label: 'A label' - }) - done() - }, 3000) - }) - - afterEach(function () { - freshQueueThat.destroy() - freshQueueThat.storageAdapter.reset() - }) - - it('should pick up old events and flag as repeated items', function (done) { - freshQueueThat.options.process = sinon.spy(function (items) { - expect(items).to.eql(arrayWithRepeatedItems([{ - task: 'a' - }, { - task: 'b' - }])) - - queueThat.storageAdapter.reset() - done() - }) - }) - }) -}) - -function arrayWithoutRepeatedItems (list) { - list.containsRepeatedItems = false - return list -} - -function arrayWithRepeatedItems (list) { - list.containsRepeatedItems = true - return list -} diff --git a/test/test-global-variable-storage-adapter.js b/test/test-global-variable-storage-adapter.js deleted file mode 100644 index 71f62d8..0000000 --- a/test/test-global-variable-storage-adapter.js +++ /dev/null @@ -1,192 +0,0 @@ -/* global describe, it, expect, beforeEach, afterEach, sinon */ - -var _ = require('underscore') -var globalVariableInjector = require('inject!../lib/global-variable-adapter') -var createLocalStorageAdapter = require('../lib/local-storage-adapter') - -describe('globalVariableAdapter', function () { - var clock - var globalVariableAdapter - var QUEUE_KEY - var ACTIVE_QUEUE_KEY - var BACKOFF_TIME_KEY - var ERROR_COUNT_KEY - var QUEUE_PROCESSING_KEY - - beforeEach(function () { - clock = sinon.useFakeTimers() - var createGlobalVariableAdapter = globalVariableInjector({ - './local-storage-adapter': createLocalStorageAdapter - }) - globalVariableAdapter = createGlobalVariableAdapter('Some Name') - QUEUE_KEY = 'Some Name - Queue' - ACTIVE_QUEUE_KEY = 'Some Name - Active Queue' - BACKOFF_TIME_KEY = 'Some Name - Backoff Time' - ERROR_COUNT_KEY = 'Some Name - Error Count' - QUEUE_PROCESSING_KEY = 'Some Name - Queue Processing' - }) - - afterEach(function () { - window.__queueThat__ = undefined - clock.restore() - }) - - describe('getQueue', function () { - var data - beforeEach(function () { - data = ['a', 'b'] - window.__queueThat__[QUEUE_KEY] = JSON.stringify(data) - }) - it('should be okay with an uninitialized queue', function () { - window.__queueThat__[QUEUE_KEY] = undefined - expect(globalVariableAdapter.getQueue()).to.eql([]) - }) - it('should get the queue from global variable on first get', function () { - expect(globalVariableAdapter.getQueue()).to.eql(data) - }) - it('should get the queue cache if set has been called', function () { - globalVariableAdapter.setQueue(['new thing']) - expect(globalVariableAdapter.getQueue()).to.eql(['new thing']) - expect(JSON.parse(window.__queueThat__[QUEUE_KEY])).to.eql(['a', 'b']) - }) - it('should get the queue from global variable once flush is called', function () { - globalVariableAdapter.setQueue(['new thing']) - globalVariableAdapter.flush() - expect(JSON.parse(window.__queueThat__[QUEUE_KEY])).to.eql(['new thing']) - - window.__queueThat__[QUEUE_KEY] = JSON.stringify(['other', 'thing']) - expect(globalVariableAdapter.getQueue()).to.eql(['other', 'thing']) - }) - it('should get from global variable after defer', function () { - clock.tick(10) - expect(globalVariableAdapter.getQueue()).to.eql(['a', 'b']) - window.__queueThat__[QUEUE_KEY] = JSON.stringify(['c', 'd']) - expect(globalVariableAdapter.getQueue()).to.eql(['a', 'b']) - clock.tick(10) - expect(globalVariableAdapter.getQueue()).to.eql(['c', 'd']) - }) - }) - - describe('setQueue', function () { - it('should set the queue cache', function () { - var queue = _.range(5) - globalVariableAdapter.setQueue(queue) - expect(window.__queueThat__[QUEUE_KEY]).to.be(undefined) - expect(globalVariableAdapter.getQueue()).to.eql(queue) - }) - it('should set to global variable after a flush', function () { - var queue = _.range(5) - globalVariableAdapter.setQueue(queue) - globalVariableAdapter.flush() - - expect(window.__queueThat__[QUEUE_KEY]).to.be(JSON.stringify(queue)) - expect(globalVariableAdapter.getQueue()).to.eql(queue) - }) - }) - - describe('flush', function () { - it('should be called once after a number of gets and sets', function () { - globalVariableAdapter.setQueue([1, 2, 3]) - globalVariableAdapter.getQueue() - globalVariableAdapter.getQueue() - globalVariableAdapter.setQueue([3, 4, 5]) - globalVariableAdapter.setQueue([6, 7, 8]) - expect(globalVariableAdapter.getQueue()).to.eql([6, 7, 8]) - expect(window.__queueThat__[QUEUE_KEY]).to.eql(undefined) - - clock.tick(10) - expect(globalVariableAdapter.getQueue()).to.eql([6, 7, 8]) - expect(JSON.parse(window.__queueThat__[QUEUE_KEY])).to.eql([6, 7, 8]) - }) - }) - - describe('getErrorCount', function () { - it('should return 0 when undefined', function () { - expect(globalVariableAdapter.getErrorCount()).to.be(0) - }) - it('should get the error count', function () { - window.__queueThat__[ERROR_COUNT_KEY] = 1 - expect(globalVariableAdapter.getErrorCount()).to.be(1) - }) - }) - - describe('getBackoffTime', function () { - it('should return 0 when undefined', function () { - expect(globalVariableAdapter.getBackoffTime()).to.be(0) - }) - it('should get the backoff time', function () { - window.__queueThat__[BACKOFF_TIME_KEY] = 1 - expect(globalVariableAdapter.getBackoffTime()).to.be(1) - }) - }) - - describe('setErrorCount', function () { - it('should set the error count', function () { - globalVariableAdapter.setErrorCount(5) - expect(window.__queueThat__[ERROR_COUNT_KEY]).to.be('5') - }) - }) - - describe('setBackoffTime', function () { - it('should set the backoff time', function () { - globalVariableAdapter.setBackoffTime(5) - expect(window.__queueThat__[BACKOFF_TIME_KEY]).to.be('5') - }) - }) - - describe('getActiveQueue', function () { - it('should return undefined when undefined', function () { - expect(globalVariableAdapter.getActiveQueue()).to.be(undefined) - }) - it('should return the parsed active queue details', function () { - window.__queueThat__[ACTIVE_QUEUE_KEY] = JSON.stringify({ - id: 'the active queue id', - ts: now() - }) - - expect(globalVariableAdapter.getActiveQueue()).to.eql({ - id: 'the active queue id', - ts: now() - }) - }) - }) - - describe('setActiveQueue', function () { - it('should set the stringified active queue details', function () { - globalVariableAdapter.setActiveQueue('the active queue id') - - expect(JSON.parse(window.__queueThat__[ACTIVE_QUEUE_KEY])).to.eql({ - id: 'the active queue id', - ts: now() - }) - }) - }) - - describe('getQueueProcessing', function () { - it('should be false by default', function () { - expect(globalVariableAdapter.getQueueProcessing()).to.be(false) - }) - - it('should parse numeric string to boolean', function () { - window.__queueThat__[QUEUE_PROCESSING_KEY] = '0' - expect(globalVariableAdapter.getQueueProcessing()).to.be(false) - - window.__queueThat__[QUEUE_PROCESSING_KEY] = '1' - expect(globalVariableAdapter.getQueueProcessing()).to.be(true) - }) - }) - - describe('setQueueProcessing', function () { - it('should encode a boolean as a numeric string', function () { - globalVariableAdapter.setQueueProcessing(false) - expect(window.__queueThat__[QUEUE_PROCESSING_KEY]).to.be('0') - - globalVariableAdapter.setQueueProcessing(true) - expect(window.__queueThat__[QUEUE_PROCESSING_KEY]).to.be('1') - }) - }) -}) - -function now () { - return (new Date()).getTime() -} diff --git a/test/test-local-storage-adapter.js b/test/test-local-storage-adapter.js deleted file mode 100644 index 9f49f18..0000000 --- a/test/test-local-storage-adapter.js +++ /dev/null @@ -1,220 +0,0 @@ -/* global describe, it, expect, beforeEach, afterEach, sinon */ - -var _ = require('underscore') -var createLocalStorageAdapter = require('../lib/local-storage-adapter') - -var localStorage = window.localStorage - -var spec = describe -try { - localStorage.a = 'b' -} catch (e) { - spec = describe.skip -} - -spec('localStorageAdapter', function () { - var clock - var localStorageAdapter - var QUEUE_KEY - var ACTIVE_QUEUE_KEY - var BACKOFF_TIME_KEY - var ERROR_COUNT_KEY - var QUEUE_PROCESSING_KEY - - beforeEach(function () { - clock = sinon.useFakeTimers() - localStorageAdapter = createLocalStorageAdapter('Some Name') - QUEUE_KEY = 'Some Name - Queue' - ACTIVE_QUEUE_KEY = 'Some Name - Active Queue' - BACKOFF_TIME_KEY = 'Some Name - Backoff Time' - ERROR_COUNT_KEY = 'Some Name - Error Count' - QUEUE_PROCESSING_KEY = 'Some Name - Queue Processing' - }) - - afterEach(function () { - localStorage.clear() - clock.restore() - }) - - describe('works', function () { - it('should return false if saving throws', function () { - localStorageAdapter.save = function () { - throw new Error('I ain\'t havin no data today m8.') - } - - expect(localStorageAdapter.works).to.not.throwError() - expect(localStorageAdapter.works()).to.be(false) - }) - it('should return false if saving does not persist', function () { - localStorageAdapter.save = sinon.stub() - - expect(localStorageAdapter.works()).to.be(false) - }) - it('should return true if saving persists', function () { - expect(localStorageAdapter.works()).to.be(true) - }) - }) - - describe('getQueue', function () { - var data - beforeEach(function () { - data = ['a', 'b'] - localStorage[QUEUE_KEY] = JSON.stringify(data) - }) - it('should be okay with an uninitialized queue', function () { - localStorage.removeItem(QUEUE_KEY) - expect(localStorageAdapter.getQueue()).to.eql([]) - }) - it('should get the queue from localStorage on first get', function () { - expect(localStorageAdapter.getQueue()).to.eql(data) - }) - it('should get the queue cache if set has been called', function () { - localStorageAdapter.setQueue(['new thing']) - expect(localStorageAdapter.getQueue()).to.eql(['new thing']) - expect(JSON.parse(localStorage[QUEUE_KEY])).to.eql(['a', 'b']) - }) - it('should get the queue from localStorage once flush is called', function () { - localStorageAdapter.setQueue(['new thing']) - localStorageAdapter.flush() - expect(JSON.parse(localStorage[QUEUE_KEY])).to.eql(['new thing']) - - localStorage[QUEUE_KEY] = JSON.stringify(['other', 'thing']) - expect(localStorageAdapter.getQueue()).to.eql(['other', 'thing']) - }) - it('should get from localStorage after defer', function () { - clock.tick(10) - expect(localStorageAdapter.getQueue()).to.eql(['a', 'b']) - localStorage[QUEUE_KEY] = JSON.stringify(['c', 'd']) - expect(localStorageAdapter.getQueue()).to.eql(['a', 'b']) - clock.tick(10) - expect(localStorageAdapter.getQueue()).to.eql(['c', 'd']) - }) - }) - - describe('setQueue', function () { - it('should set the queue cache', function () { - var queue = _.range(5) - localStorageAdapter.setQueue(queue) - expect(localStorage[QUEUE_KEY]).to.be(undefined) - expect(localStorageAdapter.getQueue()).to.eql(queue) - }) - it('should set to localStorage after a flush', function () { - var queue = _.range(5) - localStorageAdapter.setQueue(queue) - localStorageAdapter.flush() - - expect(localStorage[QUEUE_KEY]).to.be(JSON.stringify(queue)) - expect(localStorageAdapter.getQueue()).to.eql(queue) - }) - }) - - describe('flush', function () { - it('should be called once after a number of gets and sets', function () { - localStorageAdapter.setQueue([1, 2, 3]) - localStorageAdapter.getQueue() - localStorageAdapter.getQueue() - localStorageAdapter.setQueue([3, 4, 5]) - localStorageAdapter.setQueue([6, 7, 8]) - expect(localStorageAdapter.getQueue()).to.eql([6, 7, 8]) - expect(localStorage[QUEUE_KEY]).to.eql(undefined) - - clock.tick(10) - expect(localStorageAdapter.getQueue()).to.eql([6, 7, 8]) - expect(JSON.parse(localStorage[QUEUE_KEY])).to.eql([6, 7, 8]) - - localStorageAdapter.setQueue([10, 11, 12]) - clock.tick(10) - expect(JSON.parse(localStorage[QUEUE_KEY])).to.eql([10, 11, 12]) - }) - }) - - describe('getErrorCount', function () { - it('should return 0 when undefined', function () { - expect(localStorageAdapter.getErrorCount()).to.be(0) - }) - it('should get the error count', function () { - localStorage[ERROR_COUNT_KEY] = '1' - expect(localStorageAdapter.getErrorCount()).to.be(1) - }) - }) - - describe('getBackoffTime', function () { - it('should return 0 when undefined', function () { - expect(localStorageAdapter.getBackoffTime()).to.be(0) - }) - it('should get the backoff time', function () { - localStorage[BACKOFF_TIME_KEY] = '1' - expect(localStorageAdapter.getBackoffTime()).to.be(1) - }) - }) - - describe('setErrorCount', function () { - it('should set the error count', function () { - localStorageAdapter.setErrorCount(5) - expect(localStorage[ERROR_COUNT_KEY]).to.be('5') - }) - }) - - describe('setBackoffTime', function () { - it('should set the backoff time', function () { - localStorageAdapter.setBackoffTime(5) - expect(localStorage[BACKOFF_TIME_KEY]).to.be('5') - }) - }) - - describe('getActiveQueue', function () { - it('should return undefined when undefined', function () { - expect(localStorageAdapter.getActiveQueue()).to.be(undefined) - }) - it('should return the parsed active queue details', function () { - localStorage[ACTIVE_QUEUE_KEY] = JSON.stringify({ - id: 'the active queue id', - ts: now() - }) - - expect(localStorageAdapter.getActiveQueue()).to.eql({ - id: 'the active queue id', - ts: now() - }) - }) - }) - - describe('setActiveQueue', function () { - it('should set the stringified active queue details', function () { - localStorageAdapter.setActiveQueue('the active queue id') - - expect(JSON.parse(localStorage[ACTIVE_QUEUE_KEY])).to.eql({ - id: 'the active queue id', - ts: now() - }) - }) - }) - - describe('getQueueProcessing', function () { - it('should be false by default', function () { - expect(localStorageAdapter.getQueueProcessing()).to.be(false) - }) - - it('should parse numeric string to boolean', function () { - localStorage[QUEUE_PROCESSING_KEY] = '0' - expect(localStorageAdapter.getQueueProcessing()).to.be(false) - - localStorage[QUEUE_PROCESSING_KEY] = '1' - expect(localStorageAdapter.getQueueProcessing()).to.be(true) - }) - }) - - describe('setQueueProcessing', function () { - it('should encode a boolean as a numeric string', function () { - localStorageAdapter.setQueueProcessing(false) - expect(localStorage[QUEUE_PROCESSING_KEY]).to.be('0') - - localStorageAdapter.setQueueProcessing(true) - expect(localStorage[QUEUE_PROCESSING_KEY]).to.be('1') - }) - }) -}) - -function now () { - return (new Date()).getTime() -} diff --git a/test/test-queue-that.js b/test/test-queue-that.js deleted file mode 100644 index c696ef7..0000000 --- a/test/test-queue-that.js +++ /dev/null @@ -1,583 +0,0 @@ -/* global describe, it, expect, beforeEach, afterEach, sinon */ - -var _ = require('underscore') -var createQueueThatInjector = require('inject!../lib/queue-that') - -var QUEUE_GROUP_TIME = 100 -var ACTIVE_QUEUE_TIMEOUT = 2500 -var BACKOFF_TIME = 1000 -var PROCESS_TIMEOUT = 2000 - -describe('createQueueThat', function () { - var createQueueThat - var createLocalStorageAdapter - var createGlobalVariableAdapter - var localStorageAdapter - var globalVariableAdapter - var clock - beforeEach(function () { - clock = sinon.useFakeTimers(1000) - - if (window.attachEvent) { - sinon.stub(window, 'attachEvent') - } - if (window.addEventListener) { - sinon.stub(window, 'addEventListener') - } - - localStorageAdapter = createAdapter() - createLocalStorageAdapter = sinon.stub().returns(localStorageAdapter) - globalVariableAdapter = createAdapter() - createGlobalVariableAdapter = sinon.stub().returns(globalVariableAdapter) - createQueueThat = sinon.spy(createQueueThatInjector({ - './local-storage-adapter': createLocalStorageAdapter, - './global-variable-adapter': createGlobalVariableAdapter - })) - }) - - afterEach(function () { - _.each(createQueueThat.getCalls(), function (call) { - if (call.returnValue) { - call.returnValue.destroy() - } - }) - clock.restore() - if (window.attachEvent) { - window.attachEvent.restore() - } - if (window.addEventListener) { - window.addEventListener.restore() - } - }) - - it('should require a process option', function () { - expect(createQueueThat).withArgs({ - process: sinon.stub() - }).to.not.throwException() - expect(createQueueThat).withArgs({}).to.throwException() - expect(createLocalStorageAdapter.withArgs('Queue That').callCount).to.be(1) - }) - - it('should create an adapter with the label option', function () { - var queueThat = createQueueThat({ - process: sinon.stub(), - label: 'A label' - }) - - expect(createLocalStorageAdapter.withArgs('A label').callCount).to.be(1) - expect(createGlobalVariableAdapter.withArgs('A label').callCount).to.be(0) - expect(queueThat.storageAdapter).to.be(localStorageAdapter) - }) - - it('should use the global variable adapter if localStorage does not work', function () { - localStorageAdapter.works.returns(false) - var queueThat = createQueueThat({ - process: sinon.stub(), - label: 'A label' - }) - - expect(createGlobalVariableAdapter.withArgs('A label').callCount).to.be(1) - expect(queueThat.storageAdapter).to.be(globalVariableAdapter) - }) - - describe('queueThat', function () { - var queueThat - var options - - beforeEach(function () { - options = { - process: sinon.stub() - } - queueThat = createQueueThat(options) - }) - - it('should not change the active queue if the active queue hasn\'t expired', function () { - localStorageAdapter.getActiveQueue.returns({ - id: '123', - ts: now() - }) - queueThat('A') - localStorageAdapter.getActiveQueue.returns({ - id: '123', - ts: (now() - ACTIVE_QUEUE_TIMEOUT) + QUEUE_GROUP_TIME + 1 - }) - queueThat('A') - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(0) - }) - - it('should change the active queue if there is not an active queue defined', function () { - queueThat('A') - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - }) - - it('should change the active queue if the active queue has expired', function () { - localStorageAdapter.getActiveQueue.returns({ - id: 123, - ts: now() - ACTIVE_QUEUE_TIMEOUT - }) - queueThat('A') - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - }) - - it('should check the active queue after initialisation and again after ACTIVE_QUEUE_TIMEOUT ms', function () { - localStorageAdapter.getActiveQueue.returns({ - id: 123, - ts: now() - ACTIVE_QUEUE_TIMEOUT - }) - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - - clock.tick(ACTIVE_QUEUE_TIMEOUT - QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(2) - }) - - it('should update the active timestamp on activity', function () { - queueThat('A') - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - clock.tick(QUEUE_GROUP_TIME) - - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - queueThat('B') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(1) - - expect(options.process.callCount).to.be(1) - - // a callback will keep the queue active - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(2) - - // an error should also keep the queue active - options.process.getCall(1).args[1](new Error()) - clock.tick(QUEUE_GROUP_TIME) - clock.tick(BACKOFF_TIME) - expect(localStorageAdapter.setActiveQueue.callCount).to.be(3) - }) - - it('should not read the queue while tasks are processing', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.getQueue.callCount).to.be(2) - - clock.tick(QUEUE_GROUP_TIME) - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.getQueue.callCount).to.be(2) - - options.process.getCall(0).args[1]() - expect(localStorageAdapter.getQueue.callCount).to.be(3) - - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.getQueue.callCount).to.be(4) - }) - - it('should save tasks to the queue', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setQueue.getCall(0).args[0]).to.eql(['A']) - }) - - it('should add tasks to the end of the queue', function () { - localStorageAdapter.getQueue.returns(['A']) - queueThat('B') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.setQueue.callCount).to.be(1) - expect(localStorageAdapter.setQueue.getCall(0).args[0]).to.eql(['A', 'B']) - }) - - it('should group multiple tasks every ' + QUEUE_GROUP_TIME + 'ms', function () { - options.process = sinon.spy(function (task, done) { - setTimeout(done, 10) - }) - localStorageAdapter.setQueue(['A']) - queueThat('B') - clock.tick(QUEUE_GROUP_TIME / 2) - queueThat('C') - clock.tick(QUEUE_GROUP_TIME / 2) - - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems(['A', 'B', 'C'])) - - queueThat('D') - clock.tick(QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0]).to.eql(arrayWithoutRepeatedItems(['D'])) - }) - - it('should process tasks on defer when flush is called', function () { - queueThat.flush() - queueThat('A') - queueThat('B') - - /* - * Flushing should only happen once with - * multiple calls. - */ - queueThat.flush() - queueThat.flush() - queueThat('C') - - clock.tick(1) - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems(['A', 'B', 'C'])) - - queueThat('D') - clock.tick(QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(1) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0]).to.eql(arrayWithoutRepeatedItems(['D'])) - }) - - it('should not process new tasks added to the active queue until processing has finished', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - queueThat('B') - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems(['A'])) - - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - }) - - it('should process new tasks added to the active queue after processing', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - queueThat('B') - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems(['A'])) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0]).to.eql(arrayWithoutRepeatedItems(['B'])) - }) - - it('should have a default batch size of 20', function () { - localStorageAdapter.setQueue(_.range(50)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0].length).to.be(20) - expect(options.process.getCall(0).args[0][0]).to.be(0) - expect(options.process.getCall(0).args[0][1]).to.be(1) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0].length).to.be(20) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(3) - expect(options.process.getCall(2).args[0].length).to.be(11) - }) - - it('should use a custom batch size option', function () { - options.batchSize = 10 - localStorageAdapter.setQueue(_.range(14)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0].length).to.be(10) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0].length).to.be(5) - }) - - it('should allow an unlimited batch size option', function () { - options.batchSize = Infinity - localStorageAdapter.setQueue(_.range(1000)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0].length).to.be(1001) - - options.process.getCall(0).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - }) - - it('should allow a trim function option', function () { - options.trim = function (items) { - return _.filter(items, function (item) { - return item !== 'B' - }) - } - - queueThat('A') - queueThat('B') - queueThat('C') - queueThat('B') - queueThat('D') - queueThat('B') - queueThat('C') - - expect(localStorageAdapter.getQueue()).to.eql(['A', 'C', 'D', 'C']) - - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(1) - expect(options.process.getCall(0).args[0]).to.eql(arrayWithoutRepeatedItems(['A', 'C', 'D', 'C'])) - }) - - it('should backoff exponentially on process error', function () { - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(2) - options.process.getCall(1).args[1]('error') - - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(2) - - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(3) - }) - - it('should backoff on timeout', function () { - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - clock.tick(PROCESS_TIMEOUT - 1) - - expect(localStorageAdapter.setErrorCount.withArgs(1).callCount).to.be(0) - - clock.tick(1) - expect(localStorageAdapter.setErrorCount.withArgs(1).callCount).to.be(1) - expect(options.process.callCount).to.be(1) - - // a success should not affect anything - options.process.getCall(0).args[1]() - - clock.tick(BACKOFF_TIME) - expect(options.process.callCount).to.be(2) - expect(options.process.getCall(1).args[0]).to.eql(arrayWithRepeatedItems([0, 1, 2, 3, 'A'])) - }) - - it('should report repeated items on process error', function () { - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - - expect(options.process.getCall(1).args[0]).to.eql(arrayWithRepeatedItems([0, 1, 2, 3, 'A'])) - }) - - it('should report repeated items on process error followed by a new item', function () { - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - queueThat('B') - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - - expect(options.process.getCall(1).args[0]).to.eql(arrayWithRepeatedItems([0, 1, 2, 3, 'A', 'B'])) - }) - - it('should not report repeated items on process error followed by success', function () { - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - queueThat('B') - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - - options.process.getCall(1).args[1]() - clock.tick(QUEUE_GROUP_TIME) - queueThat('C') - queueThat('D') - - clock.tick(QUEUE_GROUP_TIME) - expect(options.process.getCall(2).args[0]).to.eql(arrayWithoutRepeatedItems(['C', 'D'])) - }) - - it('should not report repeated items on success batch after error batch', function () { - options.batchSize = 5 - localStorageAdapter.setQueue(_.range(8)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - - clock.tick(BACKOFF_TIME + QUEUE_GROUP_TIME) - - expect(options.process.getCall(1).args[0]).to.eql(arrayWithRepeatedItems([0, 1, 2, 3, 4])) - - options.process.getCall(1).args[1]() - clock.tick(QUEUE_GROUP_TIME) - - expect(options.process.getCall(2).args[0]).to.eql(arrayWithoutRepeatedItems([5, 6, 7, 'A'])) - }) - - it('should allow a backoff option', function () { - options.backoffTime = 30000 - localStorageAdapter.setQueue(_.range(4)) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - options.process.getCall(0).args[1]('error') - clock.tick(options.backoffTime + QUEUE_GROUP_TIME) - - expect(options.process.callCount).to.be(2) - options.process.getCall(1).args[1]('error') - - clock.tick(options.backoffTime + QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(2) - - clock.tick(options.backoffTime + QUEUE_GROUP_TIME) - expect(options.process.callCount).to.be(3) - }) - - it('should use localStorage as the back off timer', function () { - localStorageAdapter.setBackoffTime(now() + 3000) - localStorageAdapter.setErrorCount(3) - queueThat('A') - - clock.tick(QUEUE_GROUP_TIME) - - clock.tick(3000 - QUEUE_GROUP_TIME - 1) - - expect(options.process.callCount).to.be(0) - - clock.tick(1) - expect(options.process.callCount).to.be(1) - - options.process.getCall(0).args[1](new Error(404)) - expect(localStorageAdapter.setErrorCount.withArgs(4).callCount).to.be(1) - expect(localStorageAdapter.setBackoffTime.withArgs(now() + BACKOFF_TIME * Math.pow(2, 3)).callCount).to.be(1) - - clock.tick((BACKOFF_TIME * Math.pow(2, 3))) - clock.tick(QUEUE_GROUP_TIME) - options.process.getCall(1).args[1]() - expect(options.process.callCount).to.be(2) - - expect(localStorageAdapter.setErrorCount.withArgs(0).callCount).to.be(1) - }) - - it('should not increment backoff when options.process succeeds', function () { - localStorageAdapter.setBackoffTime(now() + 3000) - localStorageAdapter.setErrorCount(1) - - expect(localStorageAdapter.setBackoffTime.callCount).to.be(1) - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - - clock.tick(3000 + QUEUE_GROUP_TIME) - expect(localStorageAdapter.setBackoffTime.callCount).to.be(1) - options.process.getCall(0).args[1]() - - clock.tick(BACKOFF_TIME * Math.pow(2, 6)) - expect(localStorageAdapter.setBackoffTime.callCount).to.be(1) - }) - - it('should deactivate on beforeunload if it is the active queue', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.clearActiveQueue.callCount).to.be(0) - if (window.addEventListener) { - expect(window.addEventListener.getCall(0).args[0]).to.be('beforeunload') - window.addEventListener.getCall(0).args[1]() - } else { - expect(window.attachEvent.getCall(0).args[0]).to.be('onbeforeunload') - window.attachEvent.getCall(0).args[1]() - } - - expect(localStorageAdapter.clearActiveQueue.callCount).to.be(1) - }) - - it('should not deactivate on beforeunload if it is not the active queue', function () { - queueThat('A') - clock.tick(QUEUE_GROUP_TIME) - expect(localStorageAdapter.clearActiveQueue.callCount).to.be(0) - localStorageAdapter.setActiveQueue(1234) - if (window.addEventListener) { - expect(window.addEventListener.getCall(0).args[0]).to.be('beforeunload') - window.addEventListener.getCall(0).args[1]() - } else { - expect(window.attachEvent.getCall(0).args[0]).to.be('onbeforeunload') - window.attachEvent.getCall(0).args[1]() - } - - expect(localStorageAdapter.clearActiveQueue.callCount).to.be(0) - }) - }) -}) - -function arrayWithoutRepeatedItems (list) { - list.containsRepeatedItems = false - return list -} - -function arrayWithRepeatedItems (list) { - list.containsRepeatedItems = true - return list -} - -function now () { - return (new Date()).getTime() -} - -function createAdapter () { - var adapter = { - getQueue: sinon.stub().returns([]), - setQueue: sinon.spy(function (q) { - adapter.getQueue.returns(q) - }), - getErrorCount: sinon.stub().returns(0), - getBackoffTime: sinon.stub().returns(0), - setErrorCount: sinon.spy(function (n) { - adapter.getErrorCount.returns(n) - }), - setBackoffTime: sinon.spy(function (t) { - adapter.getBackoffTime.returns(t) - }), - getActiveQueue: sinon.stub(), - setActiveQueue: sinon.spy(function (id) { - adapter.getActiveQueue.returns({ - id: id, - ts: now() - }) - }), - clearActiveQueue: sinon.spy(function (id) { - adapter.getActiveQueue.returns(undefined) - }), - getQueueProcessing: sinon.stub().returns(false), - setQueueProcessing: sinon.spy(function (isProcessing) { - adapter.getQueueProcessing.returns(isProcessing) - }), - works: sinon.stub().returns(true), - flush: sinon.stub() - } - return adapter -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cdf5e10 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}