UNPKG

upstash-redis-level

Version:

@upstash/redis backed abstract-level database for Node.js

248 lines (247 loc) 9.27 kB
(function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("abstract-level"), require("module-error"), require("@upstash/redis")) : typeof define === "function" && define.amd ? define(["exports", "abstract-level", "module-error", "@upstash/redis"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["upstash-redis-level"] = {}, global.NOOP, global.NOOP, global.NOOP)); })(this, function(exports2, abstractLevel, ModuleError, redis) { "use strict"; function _interopDefaultLegacy(e) { return e && typeof e === "object" && "default" in e ? e : { "default": e }; } var ModuleError__default = /* @__PURE__ */ _interopDefaultLegacy(ModuleError); const DEFAULT_LIMIT = 50; const encode = (value) => { return encodeURIComponent(value); }; const decode = (value) => { return decodeURIComponent(value); }; const encodeFilter = (filter) => { if (filter.endsWith("\uFFFF")) { return encodeURIComponent(filter.slice(0, -1)) + "\uFFFF"; } return encodeURIComponent(filter); }; const queryFromOptions = (key, options) => { let query = [key, "-", "+", { byLex: true, rev: options.reverse }]; const lowerBound = options.gte !== void 0 ? options.gte : options.gt !== void 0 ? options.gt : "-"; const exclusiveLowerBound = options.gte === void 0 && options.gt !== void 0; const upperBound = options.lte !== void 0 ? options.lte : options.lt !== void 0 ? options.lt : "+"; const exclusiveUpperBound = options.lte === void 0 && options.lt !== void 0; const noLowerBound = lowerBound === "-" || lowerBound === "+"; const noUpperBound = upperBound === "-" || upperBound === "+"; const encodedUpperBound = encodeFilter(upperBound); const encodedLowerBound = encodeFilter(lowerBound); if (options.reverse) { query[1] = noUpperBound ? upperBound : exclusiveUpperBound ? `(${encodedUpperBound}` : `[${encodedUpperBound}`; query[2] = noLowerBound ? lowerBound : exclusiveLowerBound ? `(${encodedLowerBound}` : `[${encodedLowerBound}`; } else { query[1] = noLowerBound ? lowerBound : exclusiveLowerBound ? `(${encodedLowerBound}` : `[${encodedLowerBound}`; query[2] = noUpperBound ? upperBound : exclusiveUpperBound ? `(${encodedUpperBound}` : `[${encodedUpperBound}`; } query[3] = { byLex: true, rev: options.reverse }; if (options.limit !== Infinity) { query[3] = { ...query[3], count: options.limit, offset: options.offset }; } return query; }; class RedisIterator extends abstractLevel.AbstractIterator { constructor(db, options) { super(db, options); this.redis = db.redis; this.options = options; this.resultLimit = options.limit !== Infinity && options.limit >= 0 ? options.limit : DEFAULT_LIMIT; this.offset = options.offset || 0; this.results = []; this.finished = false; this.debug = options.debug || false; } async _next(callback) { if (this.finished) { return this.db.nextTick(callback, null); } if (this.results.length === 0) { const getKeys = this.options.keys; const getValues = this.options.values; const query = queryFromOptions(this.db.zKey, { ...this.options, offset: this.offset, limit: this.resultLimit }); if (this.debug) { console.log("query", query); } let keys = []; try { keys = await this.redis.zrange(...query); } catch (e) { console.log(e); } if (this.debug) { console.log("keys", keys); } if (!keys || keys.length === 0) { this.finished = true; return this.db.nextTick(callback, null); } const values = getValues ? await this.redis.hmget(this.db.hKey, ...keys) : null; for (const key of keys) { const decodedKey = decode(key); const result2 = []; result2.push(getKeys ? decodedKey : void 0); if (getValues) { if (!values) { result2.push(void 0); } else { result2.push(values[key] !== void 0 ? decode(String(values[key])) : void 0); } } this.results.push(result2); } this.offset += this.resultLimit; } const result = this.results.shift(); if (this.debug) { console.log("result", result); } return this.db.nextTick(callback, null, ...result); } } class RedisLevel extends abstractLevel.AbstractLevel { constructor(options) { super({ encodings: { utf8: true }, snapshots: false }, options); this.redis = typeof options.redis === "object" && "hmget" in options.redis && typeof options.redis.hmget === "function" ? options.redis : new redis.Redis(options.redis); const namespace = options.namespace || "level"; this.hKey = `${namespace}:h`; this.zKey = `${namespace}:z`; this.debug = options.debug || false; } get type() { return "@upstash/redis"; } async _open(options, callback) { if (this.debug) { console.log("RedisLevel#_open"); } this.nextTick(callback); } async _close(callback) { if (this.debug) { console.log("RedisLevel#_close"); } this.nextTick(callback); } async _get(key, options, callback) { if (this.debug) { console.log("RedisLevel#_get", key); } const data = await this.redis.hget(this.hKey, encode(key)); if (data !== null) { if (this.debug) { console.log("RedisLevel#_get", key, "found", decode(data)); } return this.nextTick(callback, null, decode(data)); } else { return this.nextTick( callback, new ModuleError__default["default"](`Key '${key}' was not found`, { code: "LEVEL_NOT_FOUND" }) ); } } async _getMany(keys, options, callback) { if (this.debug) { console.log("RedisLevel#_getMany", keys); } try { const data = await this.redis.hmget(this.hKey, ...keys.map((key) => encode(key))); if (data) { return this.nextTick(callback, null, keys.map((key) => data[key] ? decode(String(data[key])) : void 0)); } else { return this.nextTick(callback, null, keys.map((key) => void 0)); } } catch (e) { console.log(e); return this.nextTick( callback, new ModuleError__default["default"](`Unexpected error in getMany`, { code: "LEVEL_NOT_FOUND" }) ); } } async _put(key, value, options, callback) { if (this.debug) { console.log("RedisLevel#_put", key, value); } await this.redis.hset(this.hKey, { [encode(key)]: encode(value) }); await this.redis.zadd(this.zKey, { score: 0, member: encode(key) }); this.nextTick(callback); } async _del(key, options, callback) { if (this.debug) { console.log("RedisLevel#_del", key); } await this.redis.hdel(this.hKey, encode(key)); await this.redis.zrem(this.zKey, encode(key)); this.nextTick(callback); } async _batch(batch, options, callback) { if (this.debug) { console.log("RedisLevel#_batch", batch); } if (batch.length === 0) return this.nextTick(callback); const p = this.redis.pipeline(); for (const op of batch) { if (op.type === "put") { p.hset(this.hKey, { [encode(op.key)]: encode(op.value) }); p.zadd(this.zKey, { score: 0, member: encode(op.key) }); } else if (op.type === "del") { p.hdel(this.hKey, encode(op.key)); p.zrem(this.zKey, encode(op.key)); } } await p.exec(); this.nextTick(callback); } async _clear(options, callback) { if (this.debug) { console.log("RedisLevel#_clear", options); } let limit = options.limit !== Infinity && options.limit >= 0 ? options.limit : Infinity; let offset = 0; const fetchLimit = 100; let total = 0; while (true) { const query = queryFromOptions(this.zKey, { debug: false, keys: true, values: false, ...options, offset, limit: fetchLimit }); let keys = []; try { keys = await this.redis.zrange(...query); } catch (e) { console.log(e); } if (!keys || keys.length === 0) { break; } if (keys.length + total > limit) { keys = keys.slice(0, limit - total); } await this.redis.hdel(this.hKey, ...keys); await this.redis.zrem(this.zKey, ...keys); offset += fetchLimit; total += keys.length; if (total >= limit) { break; } } this.nextTick(callback); } _iterator(options) { return new RedisIterator(this, { ...options, debug: this.debug }); } } exports2.RedisLevel = RedisLevel; Object.defineProperties(exports2, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); });