Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"build": "tsup",
"bench": "vitest bench",
"test": "vitest --ui",
"check": "biome check --write ./src"
"check": "biome check --write ./src",
"prepare": "npm run-script build",
"prepublishOnly": "npm run build"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down
47 changes: 47 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ test.it("Async Iterator single element test", async (t) => {
}
});

test.it("Async Keys single element test", async (t) => {
await sqliteKeyv.set("foo", "bar");
const keys = await sqliteKeyv.keys();

for (const key of keys) {
t.expect(key).toBe("foo");
}
});

test.it("Async Iterator multiple element test", async (t) => {
await sqliteKeyv.set("foo", "bar");
await sqliteKeyv.set("foo1", "bar1");
Expand All @@ -70,6 +79,20 @@ test.it("Async Iterator multiple element test", async (t) => {
}
});

test.it("Async Keys multiple element test", async (t) => {
await sqliteKeyv.set("foo", "bar");
await sqliteKeyv.set("foo1", "bar1");
await sqliteKeyv.set("foo2", "bar2");

const expectedKeys = ["foo", "foo1", "foo2"];
const keys = await sqliteKeyv.keys();
let i = 0;
for (const key of keys) {
const expectedKey = expectedKeys[i++];
t.expect(key).toBe(expectedKey);
}
});

test.it("Async Iterator multiple elements with limit=1 test", async (t) => {
await sqliteKeyv.set("foo", "bar");
await sqliteKeyv.set("foo1", "bar1");
Expand All @@ -89,12 +112,36 @@ test.it("Async Iterator multiple elements with limit=1 test", async (t) => {
t.expect(v).toBe("bar2");
});

test.it("Async Keys multiple elements with limit=1 test", async (t) => {
await sqliteKeyv.set("foo", "bar");
await sqliteKeyv.set("foo1", "bar1");
await sqliteKeyv.set("foo2", "bar2");
const keys = await sqliteKeyv.keys();
t.expect(keys).toStrictEqual(["foo", "foo1", "foo2"]);
});

test.it("Async Iterator 0 element test", async (t) => {
const iterator = sqliteKeyv.iterator("keyv");
const key = await iterator.next();
t.expect(key.value).toBe(undefined);
});

test.it("Async Keys 0 element test", async (t) => {
const keys = await sqliteKeyv.keys("keyv");
t.expect(keys.length).toBe(0);
});

test.it("Async Keys with pattern test", async (t) => {
await sqliteKeyv.set("other", "bar");
await sqliteKeyv.set("foo1", "bar");
await sqliteKeyv.set("foo2", "bar1");
await sqliteKeyv.set("foo3", "bar2");

for (const key of await sqliteKeyv.keys("foo*")) {
t.expect(key).not.toBe("other");
}
});

test.it("close connection successfully", async (t) => {
t.expect(await sqliteKeyv.get("foo")).toBe(undefined);
await sqliteKeyv.set("foo", "bar");
Expand Down
17 changes: 17 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class KeyvSqlite extends EventEmitter implements KeyvStoreAdapter {
updateCatches: (args: [string, unknown][], ttl?: number) => void;
emptyCaches: () => void;
findCaches: (namespace: string | undefined, limit: number, offset: number, expiredAt: number) => CacheObject[];
findKeys: (pattern: string | undefined, expiredAt: number) => {cacheKey: string}[];

constructor(options?: KeyvSqliteOptions) {
super();
Expand Down Expand Up @@ -72,6 +73,9 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt);
const finderStatement = this.sqlite.prepare<[string, number, number, number], CacheObject>(
`SELECT * FROM ${tableName} WHERE cacheKey LIKE ? AND (expiredAt = -1 OR expiredAt > ?) LIMIT ? OFFSET ?`,
);
const findKeysStatement = this.sqlite.prepare<[string, number], {cacheKey: string}>(
`SELECT cacheKey FROM ${tableName} WHERE cacheKey LIKE ? AND (expiredAt = -1 OR expiredAt > ?)`,
);
const purgeStatement = this.sqlite.prepare(`DELETE FROM ${tableName} WHERE expiredAt != -1 AND expiredAt < ?`);
const emptyStatement = this.sqlite.prepare(`DELETE FROM ${tableName} WHERE cacheKey LIKE ?`);

Expand Down Expand Up @@ -140,6 +144,13 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt);
.all(`${namespace ? `${namespace}:` : ""}%`, expiredAt, limit, offset)
.filter((data) => data !== undefined);
};

this.findKeys = (pattern, expiredAt) => {
const _pattern = pattern?.replaceAll("*", "%") ?? ""
return findKeysStatement
.all(_pattern ? `%${_pattern}%` : '%', expiredAt)
.filter((data) => data !== undefined);
};
}

async get<Value>(key: string): Promise<StoredData<Value> | undefined> {
Expand Down Expand Up @@ -214,6 +225,12 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt);
yield* iterate(0);
}

async keys(pattern?: string): Promise<string[]> {
const time = now();
const entries = this.findKeys(pattern, time)
return entries.map((entry) => entry.cacheKey);
}

async disconnect() {
this.sqlite.close();
}
Expand Down