@keyv/sqlite
Version:
SQLite storage adapter for Keyv
133 lines (132 loc) • 4.53 kB
JavaScript
// src/index.ts
import EventEmitter from "events";
import { promisify } from "util";
import Keyv from "keyv";
import sqlite3 from "sqlite3";
var toString = (input) => String(input).search(/^[a-zA-Z]+$/) < 0 ? "_" + input : input;
var KeyvSqlite = class extends EventEmitter {
ttlSupport;
opts;
namespace;
close;
query;
constructor(keyvOptions) {
super();
this.ttlSupport = false;
let options = {
dialect: "sqlite",
uri: "sqlite://:memory:"
};
if (typeof keyvOptions === "string") {
options.uri = keyvOptions;
} else {
options = {
...options,
...keyvOptions
};
}
options.db = options.uri.replace(/^sqlite:\/\//, "");
options.connect = async () => new Promise((resolve, reject) => {
const database = new sqlite3.Database(options.db, (error) => {
if (error) {
reject(error);
} else {
if (options.busyTimeout) {
database.configure("busyTimeout", options.busyTimeout);
}
resolve(database);
}
});
}).then((database) => ({ query: promisify(database.all).bind(database), close: promisify(database.close).bind(database) }));
this.opts = {
table: "keyv",
keySize: 255,
...options
};
this.opts.table = toString(this.opts.table);
const createTable = `CREATE TABLE IF NOT EXISTS ${this.opts.table}(key VARCHAR(${Number(this.opts.keySize)}) PRIMARY KEY, value TEXT )`;
const connected = this.opts.connect().then(async (database) => database.query(createTable).then(() => database)).catch((error) => this.emit("error", error));
this.query = async (sqlString, ...parameter) => connected.then(async (database) => database.query(sqlString, ...parameter));
this.close = async () => connected.then((database) => database.close);
}
async get(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = ?`;
const rows = await this.query(select, key);
const row = rows[0];
if (row === void 0) {
return void 0;
}
return row.value;
}
async getMany(keys) {
const select = `SELECT * FROM ${this.opts.table} WHERE key IN (SELECT value FROM json_each(?))`;
const rows = await this.query(select, JSON.stringify(keys));
return keys.map((key) => {
const row = rows.find((row2) => row2.key === key);
return row ? row.value : void 0;
});
}
async set(key, value) {
const upsert = `INSERT INTO ${this.opts.table} (key, value)
VALUES(?, ?)
ON CONFLICT(key)
DO UPDATE SET value=excluded.value;`;
return this.query(upsert, key, value);
}
async delete(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = ?`;
const del = `DELETE FROM ${this.opts.table} WHERE key = ?`;
const rows = await this.query(select, key);
const row = rows[0];
if (row === void 0) {
return false;
}
await this.query(del, key);
return true;
}
async deleteMany(keys) {
const del = `DELETE FROM ${this.opts.table} WHERE key IN (SELECT value FROM json_each(?))`;
const results = await this.getMany(keys);
if (results.every((x) => x === void 0)) {
return false;
}
await this.query(del, JSON.stringify(keys));
return true;
}
async clear() {
const del = `DELETE FROM ${this.opts.table} WHERE key LIKE ?`;
await this.query(del, this.namespace ? `${this.namespace}:%` : "%");
}
async *iterator(namespace) {
const limit = Number.parseInt(this.opts.iterationLimit, 10) || 10;
async function* iterate(offset, options, query) {
const select = `SELECT * FROM ${options.table} WHERE key LIKE ? LIMIT ? OFFSET ?`;
const iterator = await query(select, [`${namespace ? namespace + ":" : ""}%`, limit, offset]);
const entries = [...iterator];
if (entries.length === 0) {
return;
}
for (const entry of entries) {
offset += 1;
yield [entry.key, entry.value];
}
yield* iterate(offset, options, query);
}
yield* iterate(0, this.opts, this.query);
}
async has(key) {
const exists = `SELECT EXISTS ( SELECT * FROM ${this.opts.table} WHERE key = ? )`;
const result = await this.query(exists, key);
return Object.values(result[0])[0] === 1;
}
async disconnect() {
await this.close();
}
};
var createKeyv = (keyvOptions) => new Keyv({ store: new KeyvSqlite(keyvOptions) });
var index_default = KeyvSqlite;
export {
KeyvSqlite,
createKeyv,
index_default as default
};