diff --git a/package.json b/package.json index b2550ed..32a4b7a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.test.ts b/src/index.test.ts index aa90c63..ad511c2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -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"); @@ -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"); @@ -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"); diff --git a/src/index.ts b/src/index.ts index 9f895ba..e8b96b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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(); @@ -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 ?`); @@ -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(key: string): Promise | undefined> { @@ -214,6 +225,12 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt); yield* iterate(0); } + async keys(pattern?: string): Promise { + const time = now(); + const entries = this.findKeys(pattern, time) + return entries.map((entry) => entry.cacheKey); + } + async disconnect() { this.sqlite.close(); }