Spaces:
Paused
Paused
| ; | |
| const EventEmitter = require('events'); | |
| const JSONB = require('json-buffer'); | |
| const loadStore = options => { | |
| const adapters = { | |
| redis: '@keyv/redis', | |
| rediss: '@keyv/redis', | |
| mongodb: '@keyv/mongo', | |
| mongo: '@keyv/mongo', | |
| sqlite: '@keyv/sqlite', | |
| postgresql: '@keyv/postgres', | |
| postgres: '@keyv/postgres', | |
| mysql: '@keyv/mysql', | |
| etcd: '@keyv/etcd', | |
| offline: '@keyv/offline', | |
| tiered: '@keyv/tiered', | |
| }; | |
| if (options.adapter || options.uri) { | |
| const adapter = options.adapter || /^[^:+]*/.exec(options.uri)[0]; | |
| return new (require(adapters[adapter]))(options); | |
| } | |
| return new Map(); | |
| }; | |
| const iterableAdapters = [ | |
| 'sqlite', | |
| 'postgres', | |
| 'mysql', | |
| 'mongo', | |
| 'redis', | |
| 'tiered', | |
| ]; | |
| class Keyv extends EventEmitter { | |
| constructor(uri, {emitErrors = true, ...options} = {}) { | |
| super(); | |
| this.opts = { | |
| namespace: 'keyv', | |
| serialize: JSONB.stringify, | |
| deserialize: JSONB.parse, | |
| ...((typeof uri === 'string') ? {uri} : uri), | |
| ...options, | |
| }; | |
| if (!this.opts.store) { | |
| const adapterOptions = {...this.opts}; | |
| this.opts.store = loadStore(adapterOptions); | |
| } | |
| if (this.opts.compression) { | |
| const compression = this.opts.compression; | |
| this.opts.serialize = compression.serialize.bind(compression); | |
| this.opts.deserialize = compression.deserialize.bind(compression); | |
| } | |
| if (typeof this.opts.store.on === 'function' && emitErrors) { | |
| this.opts.store.on('error', error => this.emit('error', error)); | |
| } | |
| this.opts.store.namespace = this.opts.namespace; | |
| const generateIterator = iterator => async function * () { | |
| for await (const [key, raw] of typeof iterator === 'function' | |
| ? iterator(this.opts.store.namespace) | |
| : iterator) { | |
| const data = await this.opts.deserialize(raw); | |
| if (this.opts.store.namespace && !key.includes(this.opts.store.namespace)) { | |
| continue; | |
| } | |
| if (typeof data.expires === 'number' && Date.now() > data.expires) { | |
| this.delete(key); | |
| continue; | |
| } | |
| yield [this._getKeyUnprefix(key), data.value]; | |
| } | |
| }; | |
| // Attach iterators | |
| if (typeof this.opts.store[Symbol.iterator] === 'function' && this.opts.store instanceof Map) { | |
| this.iterator = generateIterator(this.opts.store); | |
| } else if (typeof this.opts.store.iterator === 'function' && this.opts.store.opts | |
| && this._checkIterableAdaptar()) { | |
| this.iterator = generateIterator(this.opts.store.iterator.bind(this.opts.store)); | |
| } | |
| } | |
| _checkIterableAdaptar() { | |
| return iterableAdapters.includes(this.opts.store.opts.dialect) | |
| || iterableAdapters.findIndex(element => this.opts.store.opts.url.includes(element)) >= 0; | |
| } | |
| _getKeyPrefix(key) { | |
| return `${this.opts.namespace}:${key}`; | |
| } | |
| _getKeyPrefixArray(keys) { | |
| return keys.map(key => `${this.opts.namespace}:${key}`); | |
| } | |
| _getKeyUnprefix(key) { | |
| return key | |
| .split(':') | |
| .splice(1) | |
| .join(':'); | |
| } | |
| get(key, options) { | |
| const {store} = this.opts; | |
| const isArray = Array.isArray(key); | |
| const keyPrefixed = isArray ? this._getKeyPrefixArray(key) : this._getKeyPrefix(key); | |
| if (isArray && store.getMany === undefined) { | |
| const promises = []; | |
| for (const key of keyPrefixed) { | |
| promises.push(Promise.resolve() | |
| .then(() => store.get(key)) | |
| .then(data => (typeof data === 'string') ? this.opts.deserialize(data) : (this.opts.compression ? this.opts.deserialize(data) : data)) | |
| .then(data => { | |
| if (data === undefined || data === null) { | |
| return undefined; | |
| } | |
| if (typeof data.expires === 'number' && Date.now() > data.expires) { | |
| return this.delete(key).then(() => undefined); | |
| } | |
| return (options && options.raw) ? data : data.value; | |
| }), | |
| ); | |
| } | |
| return Promise.allSettled(promises) | |
| .then(values => { | |
| const data = []; | |
| for (const value of values) { | |
| data.push(value.value); | |
| } | |
| return data; | |
| }); | |
| } | |
| return Promise.resolve() | |
| .then(() => isArray ? store.getMany(keyPrefixed) : store.get(keyPrefixed)) | |
| .then(data => (typeof data === 'string') ? this.opts.deserialize(data) : (this.opts.compression ? this.opts.deserialize(data) : data)) | |
| .then(data => { | |
| if (data === undefined || data === null) { | |
| return undefined; | |
| } | |
| if (isArray) { | |
| return data.map((row, index) => { | |
| if ((typeof row === 'string')) { | |
| row = this.opts.deserialize(row); | |
| } | |
| if (row === undefined || row === null) { | |
| return undefined; | |
| } | |
| if (typeof row.expires === 'number' && Date.now() > row.expires) { | |
| this.delete(key[index]).then(() => undefined); | |
| return undefined; | |
| } | |
| return (options && options.raw) ? row : row.value; | |
| }); | |
| } | |
| if (typeof data.expires === 'number' && Date.now() > data.expires) { | |
| return this.delete(key).then(() => undefined); | |
| } | |
| return (options && options.raw) ? data : data.value; | |
| }); | |
| } | |
| set(key, value, ttl) { | |
| const keyPrefixed = this._getKeyPrefix(key); | |
| if (typeof ttl === 'undefined') { | |
| ttl = this.opts.ttl; | |
| } | |
| if (ttl === 0) { | |
| ttl = undefined; | |
| } | |
| const {store} = this.opts; | |
| return Promise.resolve() | |
| .then(() => { | |
| const expires = (typeof ttl === 'number') ? (Date.now() + ttl) : null; | |
| if (typeof value === 'symbol') { | |
| this.emit('error', 'symbol cannot be serialized'); | |
| } | |
| value = {value, expires}; | |
| return this.opts.serialize(value); | |
| }) | |
| .then(value => store.set(keyPrefixed, value, ttl)) | |
| .then(() => true); | |
| } | |
| delete(key) { | |
| const {store} = this.opts; | |
| if (Array.isArray(key)) { | |
| const keyPrefixed = this._getKeyPrefixArray(key); | |
| if (store.deleteMany === undefined) { | |
| const promises = []; | |
| for (const key of keyPrefixed) { | |
| promises.push(store.delete(key)); | |
| } | |
| return Promise.allSettled(promises) | |
| .then(values => values.every(x => x.value === true)); | |
| } | |
| return Promise.resolve() | |
| .then(() => store.deleteMany(keyPrefixed)); | |
| } | |
| const keyPrefixed = this._getKeyPrefix(key); | |
| return Promise.resolve() | |
| .then(() => store.delete(keyPrefixed)); | |
| } | |
| clear() { | |
| const {store} = this.opts; | |
| return Promise.resolve() | |
| .then(() => store.clear()); | |
| } | |
| has(key) { | |
| const keyPrefixed = this._getKeyPrefix(key); | |
| const {store} = this.opts; | |
| return Promise.resolve() | |
| .then(async () => { | |
| if (typeof store.has === 'function') { | |
| return store.has(keyPrefixed); | |
| } | |
| const value = await store.get(keyPrefixed); | |
| return value !== undefined; | |
| }); | |
| } | |
| disconnect() { | |
| const {store} = this.opts; | |
| if (typeof store.disconnect === 'function') { | |
| return store.disconnect(); | |
| } | |
| } | |
| } | |
| module.exports = Keyv; | |