upstash-redis-level
Version:
@upstash/redis backed abstract-level database for Node.js
248 lines (247 loc) • 9.27 kB
JavaScript
(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" } });
});