From f07b13344aa7b4c69945af70e1fb0750a975734c Mon Sep 17 00:00:00 2001 From: Oscaner Miao Date: Wed, 22 Jan 2025 11:20:14 +0800 Subject: [PATCH 1/3] feat: find keys --- package.json | 4 +++- src/index.test.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 36 ++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) 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..eb53c4c 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 = sqliteKeyv.keys(); + + for await (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 = sqliteKeyv.keys(); + let i = 0; + for await (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,45 @@ 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 = sqliteKeyv.keys(); + let key = await keys.next(); + let k = key.value + t.expect(k).toBe("foo"); + key = await keys.next(); + k = key.value + t.expect(k).toBe("foo1"); + key = await keys.next(); + k = key.value + t.expect(k).toBe("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 = sqliteKeyv.keys("keyv"); + const key = await keys.next(); + t.expect(key.value).toBe(undefined); +}); + +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 await (const key of 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..33089fa 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, limit: number, offset: number, expiredAt: number) => CacheObject[]; 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, number, number], CacheObject>( + `SELECT cacheKey FROM ${tableName} WHERE cacheKey LIKE ? AND (expiredAt = -1 OR expiredAt > ?) LIMIT ? OFFSET ?`, + ); 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, limit, offset, expiredAt) => { + const _pattern = pattern?.replaceAll("*", "%") ?? "" + return findKeysStatement + .all(_pattern ? `%${pattern}%` : '%', expiredAt, limit, offset) + .filter((data) => data !== undefined); + }; } async get(key: string): Promise | undefined> { @@ -214,6 +225,31 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt); yield* iterate(0); } + async *keys(pattern?: string) { + const limit = Number.parseInt(this.opts.iterationLimit! as string, 10) || 10; + const time = now(); + const find = this.findKeys; + + // @ts-expect-error - iterate + const iterate = async function* (offset: number) { + const entries = find(pattern, limit, offset, time); + + if (entries.length === 0) { + return; + } + + for (const entry of entries) { + // biome-ignore lint: + offset += 1; + yield entry.cacheKey; + } + + yield* iterate(offset); + }; + + yield* iterate(0); + } + async disconnect() { this.sqlite.close(); } From 38f1eda2130b97cebc21ac15d97c130a9ffb61e7 Mon Sep 17 00:00:00 2001 From: Oscaner Miao Date: Wed, 22 Jan 2025 15:48:29 +0800 Subject: [PATCH 2/3] feat: move keys function to not generator --- src/index.test.ts | 27 +++++++++------------------ src/index.ts | 35 ++++++++--------------------------- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index eb53c4c..ad511c2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -53,9 +53,9 @@ 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 = sqliteKeyv.keys(); + const keys = await sqliteKeyv.keys(); - for await (const key of keys) { + for (const key of keys) { t.expect(key).toBe("foo"); } }); @@ -85,9 +85,9 @@ test.it("Async Keys multiple element test", async (t) => { await sqliteKeyv.set("foo2", "bar2"); const expectedKeys = ["foo", "foo1", "foo2"]; - const keys = sqliteKeyv.keys(); + const keys = await sqliteKeyv.keys(); let i = 0; - for await (const key of keys) { + for (const key of keys) { const expectedKey = expectedKeys[i++]; t.expect(key).toBe(expectedKey); } @@ -116,16 +116,8 @@ 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 = sqliteKeyv.keys(); - let key = await keys.next(); - let k = key.value - t.expect(k).toBe("foo"); - key = await keys.next(); - k = key.value - t.expect(k).toBe("foo1"); - key = await keys.next(); - k = key.value - t.expect(k).toBe("foo2"); + const keys = await sqliteKeyv.keys(); + t.expect(keys).toStrictEqual(["foo", "foo1", "foo2"]); }); test.it("Async Iterator 0 element test", async (t) => { @@ -135,9 +127,8 @@ test.it("Async Iterator 0 element test", async (t) => { }); test.it("Async Keys 0 element test", async (t) => { - const keys = sqliteKeyv.keys("keyv"); - const key = await keys.next(); - t.expect(key.value).toBe(undefined); + const keys = await sqliteKeyv.keys("keyv"); + t.expect(keys.length).toBe(0); }); test.it("Async Keys with pattern test", async (t) => { @@ -146,7 +137,7 @@ test.it("Async Keys with pattern test", async (t) => { await sqliteKeyv.set("foo2", "bar1"); await sqliteKeyv.set("foo3", "bar2"); - for await (const key of sqliteKeyv.keys("foo*")) { + for (const key of await sqliteKeyv.keys("foo*")) { t.expect(key).not.toBe("other"); } }); diff --git a/src/index.ts b/src/index.ts index 33089fa..7e67f4e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,7 +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, limit: number, offset: number, expiredAt: number) => CacheObject[]; + findKeys: (pattern: string | undefined, expiredAt: number) => {cacheKey: string}[]; constructor(options?: KeyvSqliteOptions) { super(); @@ -73,8 +73,8 @@ 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, number, number], CacheObject>( - `SELECT cacheKey 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 ?`); @@ -145,10 +145,10 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt); .filter((data) => data !== undefined); }; - this.findKeys = (pattern, limit, offset, expiredAt) => { + this.findKeys = (pattern, expiredAt) => { const _pattern = pattern?.replaceAll("*", "%") ?? "" return findKeysStatement - .all(_pattern ? `%${pattern}%` : '%', expiredAt, limit, offset) + .all(_pattern ? `%${pattern}%` : '%', expiredAt) .filter((data) => data !== undefined); }; } @@ -225,29 +225,10 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt); yield* iterate(0); } - async *keys(pattern?: string) { - const limit = Number.parseInt(this.opts.iterationLimit! as string, 10) || 10; + async keys(pattern?: string): Promise { const time = now(); - const find = this.findKeys; - - // @ts-expect-error - iterate - const iterate = async function* (offset: number) { - const entries = find(pattern, limit, offset, time); - - if (entries.length === 0) { - return; - } - - for (const entry of entries) { - // biome-ignore lint: - offset += 1; - yield entry.cacheKey; - } - - yield* iterate(offset); - }; - - yield* iterate(0); + const entries = this.findKeys(pattern, time) + return entries.map((entry) => entry.cacheKey); } async disconnect() { From d261e5e71d29cc21a93149bf7ee7bd6addf73057 Mon Sep 17 00:00:00 2001 From: Oscaner Miao Date: Wed, 22 Jan 2025 17:05:56 +0800 Subject: [PATCH 3/3] fix: findKeys statement --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7e67f4e..e8b96b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -148,7 +148,7 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt); this.findKeys = (pattern, expiredAt) => { const _pattern = pattern?.replaceAll("*", "%") ?? "" return findKeysStatement - .all(_pattern ? `%${pattern}%` : '%', expiredAt) + .all(_pattern ? `%${_pattern}%` : '%', expiredAt) .filter((data) => data !== undefined); }; }