UNPKG

6.77 kBJavaScriptView Raw
1'use strict';
2
3const EventEmitter = require('events');
4const JSONB = require('json-buffer');
5const compressBrotli = require('compress-brotli');
6
7const loadStore = options => {
8 const adapters = {
9 redis: '@keyv/redis',
10 mongodb: '@keyv/mongo',
11 mongo: '@keyv/mongo',
12 sqlite: '@keyv/sqlite',
13 postgresql: '@keyv/postgres',
14 postgres: '@keyv/postgres',
15 mysql: '@keyv/mysql',
16 etcd: '@keyv/etcd',
17 };
18 if (options.adapter || options.uri) {
19 const adapter = options.adapter || /^[^:]*/.exec(options.uri)[0];
20 return new (require(adapters[adapter]))(options);
21 }
22
23 return new Map();
24};
25
26const iterableAdapters = [
27 'sqlite',
28 'postgres',
29 'mysql',
30 'mongo',
31 'redis',
32];
33
34class Keyv extends EventEmitter {
35 constructor(uri, options) {
36 super();
37 this.opts = {
38 namespace: 'keyv',
39 serialize: JSONB.stringify,
40 deserialize: JSONB.parse,
41 ...((typeof uri === 'string') ? {uri} : uri),
42 ...options,
43 };
44
45 if (!this.opts.store) {
46 const adapterOptions = {...this.opts};
47 this.opts.store = loadStore(adapterOptions);
48 }
49
50 if (this.opts.compress) {
51 const brotli = compressBrotli(this.opts.compress.opts);
52 this.opts.serialize = async ({value, expires}) => brotli.serialize({value: await brotli.compress(value), expires});
53 this.opts.deserialize = async data => {
54 const {value, expires} = brotli.deserialize(data);
55 return {value: await brotli.decompress(value), expires};
56 };
57 }
58
59 if (typeof this.opts.store.on === 'function') {
60 this.opts.store.on('error', error => this.emit('error', error));
61 }
62
63 this.opts.store.namespace = this.opts.namespace;
64
65 const generateIterator = iterator =>
66 async function * () {
67 for await (const [key, raw] of typeof iterator === 'function'
68 ? iterator(this.opts.store.namespace)
69 : iterator) {
70 const data = typeof raw === 'string' ? this.opts.deserialize(raw) : raw;
71 if (this.opts.store.namespace && !key.includes(this.opts.store.namespace)) {
72 continue;
73 }
74
75 if (typeof data.expires === 'number' && Date.now() > data.expires) {
76 this.delete(key);
77 continue;
78 }
79
80 yield [this._getKeyUnprefix(key), data.value];
81 }
82 };
83
84 // Attach iterators
85 if (typeof this.opts.store[Symbol.iterator] === 'function' && this.opts.store instanceof Map) {
86 this.iterator = generateIterator(this.opts.store);
87 } else if (typeof this.opts.store.iterator === 'function' && this.opts.store.opts
88 && this._checkIterableAdaptar()) {
89 this.iterator = generateIterator(this.opts.store.iterator.bind(this.opts.store));
90 }
91 }
92
93 _checkIterableAdaptar() {
94 return iterableAdapters.includes(this.opts.store.opts.dialect)
95 || iterableAdapters.findIndex(element => this.opts.store.opts.url.includes(element)) >= 0;
96 }
97
98 _getKeyPrefix(key) {
99 return `${this.opts.namespace}:${key}`;
100 }
101
102 _getKeyPrefixArray(keys) {
103 return keys.map(key => `${this.opts.namespace}:${key}`);
104 }
105
106 _getKeyUnprefix(key) {
107 return this.opts.store.namespace
108 ? key
109 .split(':')
110 .splice(1)
111 .join(':')
112 : key;
113 }
114
115 get(key, options) {
116 const {store} = this.opts;
117 const isArray = Array.isArray(key);
118 const keyPrefixed = isArray ? this._getKeyPrefixArray(key) : this._getKeyPrefix(key);
119 if (isArray && store.getMany === undefined) {
120 const promises = [];
121 for (const key of keyPrefixed) {
122 promises.push(Promise.resolve()
123 .then(() => store.get(key))
124 .then(data => (typeof data === 'string') ? this.opts.deserialize(data) : data)
125 .then(data => {
126 if (data === undefined || data === null) {
127 return undefined;
128 }
129
130 if (typeof data.expires === 'number' && Date.now() > data.expires) {
131 return this.delete(key).then(() => undefined);
132 }
133
134 return (options && options.raw) ? data : data.value;
135 }),
136 );
137 }
138
139 return Promise.allSettled(promises)
140 .then(values => {
141 const data = [];
142 for (const value of values) {
143 data.push(value.value);
144 }
145
146 return data.every(x => x === undefined) ? [] : data;
147 });
148 }
149
150 return Promise.resolve()
151 .then(() => isArray ? store.getMany(keyPrefixed) : store.get(keyPrefixed))
152 .then(data => (typeof data === 'string') ? this.opts.deserialize(data) : data)
153 .then(data => {
154 // Console.log('get', data);
155 if (data === undefined || data === null) {
156 return undefined;
157 }
158
159 if (isArray) {
160 const result = [];
161 if (data.length === 0) {
162 return [];
163 }
164
165 for (let row of data) {
166 if ((typeof row === 'string')) {
167 row = this.opts.deserialize(row);
168 }
169
170 if (row === undefined || row === null) {
171 result.push(undefined);
172 continue;
173 }
174
175 if (typeof row.expires === 'number' && Date.now() > row.expires) {
176 this.delete(key).then(() => undefined);
177 result.push(undefined);
178 } else {
179 result.push((options && options.raw) ? row : row.value);
180 }
181 }
182
183 return result.every(x => x === undefined) ? [] : result;
184 }
185
186 if (typeof data.expires === 'number' && Date.now() > data.expires) {
187 return this.delete(key).then(() => undefined);
188 }
189
190 return (options && options.raw) ? data : data.value;
191 });
192 }
193
194 set(key, value, ttl) {
195 const keyPrefixed = this._getKeyPrefix(key);
196 if (typeof ttl === 'undefined') {
197 ttl = this.opts.ttl;
198 }
199
200 if (ttl === 0) {
201 ttl = undefined;
202 }
203
204 const {store} = this.opts;
205
206 return Promise.resolve()
207 .then(() => {
208 const expires = (typeof ttl === 'number') ? (Date.now() + ttl) : null;
209 if (typeof value === 'symbol') {
210 this.emit('error', 'symbol cannot be serialized');
211 }
212
213 value = {value, expires};
214 return this.opts.serialize(value);
215 })
216 .then(value => store.set(keyPrefixed, value, ttl))
217 .then(() => true);
218 }
219
220 delete(key) {
221 const {store} = this.opts;
222 if (Array.isArray(key)) {
223 const keyPrefixed = this._getKeyPrefixArray(key);
224 if (store.deleteMany === undefined) {
225 const promises = [];
226 for (const key of keyPrefixed) {
227 promises.push(store.delete(key));
228 }
229
230 return Promise.allSettled(promises)
231 .then(values => values.every(x => x.value === true));
232 }
233
234 return Promise.resolve()
235 .then(() => store.deleteMany(keyPrefixed));
236 }
237
238 const keyPrefixed = this._getKeyPrefix(key);
239 return Promise.resolve()
240 .then(() => store.delete(keyPrefixed));
241 }
242
243 clear() {
244 const {store} = this.opts;
245 return Promise.resolve()
246 .then(() => store.clear());
247 }
248
249 has(key) {
250 const keyPrefixed = this._getKeyPrefix(key);
251 const {store} = this.opts;
252 return Promise.resolve()
253 .then(async () => {
254 if (typeof store.has === 'function') {
255 return store.has(keyPrefixed);
256 }
257
258 const value = await store.get(keyPrefixed);
259 return value !== undefined;
260 });
261 }
262}
263
264module.exports = Keyv;