Skip to content

Commit 8882d39

Browse files
committed
feat: find keys
1 parent 3d14e80 commit 8882d39

File tree

3 files changed

+95
-1
lines changed

3 files changed

+95
-1
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"build": "tsup",
1616
"bench": "vitest bench",
1717
"test": "vitest --ui",
18-
"check": "biome check --write ./src"
18+
"check": "biome check --write ./src",
19+
"prepare": "npm run-script build",
20+
"prepublishOnly": "npm run build"
1921
},
2022
"main": "./dist/index.cjs",
2123
"module": "./dist/index.js",

src/index.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ test.it("Async Iterator single element test", async (t) => {
5151
}
5252
});
5353

54+
test.it("Async Keys single element test", async (t) => {
55+
await sqliteKeyv.set("foo", "bar");
56+
const keys = sqliteKeyv.keys();
57+
58+
for await (const key of keys) {
59+
t.expect(key).toBe("foo");
60+
}
61+
});
62+
5463
test.it("Async Iterator multiple element test", async (t) => {
5564
await sqliteKeyv.set("foo", "bar");
5665
await sqliteKeyv.set("foo1", "bar1");
@@ -70,6 +79,20 @@ test.it("Async Iterator multiple element test", async (t) => {
7079
}
7180
});
7281

82+
test.it("Async Keys multiple element test", async (t) => {
83+
await sqliteKeyv.set("foo", "bar");
84+
await sqliteKeyv.set("foo1", "bar1");
85+
await sqliteKeyv.set("foo2", "bar2");
86+
87+
const expectedKeys = ["foo", "foo1", "foo2"];
88+
const keys = sqliteKeyv.keys();
89+
let i = 0;
90+
for await (const key of keys) {
91+
const expectedKey = expectedKeys[i++];
92+
t.expect(key).toBe(expectedKey);
93+
}
94+
});
95+
7396
test.it("Async Iterator multiple elements with limit=1 test", async (t) => {
7497
await sqliteKeyv.set("foo", "bar");
7598
await sqliteKeyv.set("foo1", "bar1");
@@ -89,12 +112,45 @@ test.it("Async Iterator multiple elements with limit=1 test", async (t) => {
89112
t.expect(v).toBe("bar2");
90113
});
91114

115+
test.it("Async Keys multiple elements with limit=1 test", async (t) => {
116+
await sqliteKeyv.set("foo", "bar");
117+
await sqliteKeyv.set("foo1", "bar1");
118+
await sqliteKeyv.set("foo2", "bar2");
119+
const keys = sqliteKeyv.keys();
120+
let key = await keys.next();
121+
let k = key.value
122+
t.expect(k).toBe("foo");
123+
key = await keys.next();
124+
k = key.value
125+
t.expect(k).toBe("foo1");
126+
key = await keys.next();
127+
k = key.value
128+
t.expect(k).toBe("foo2");
129+
});
130+
92131
test.it("Async Iterator 0 element test", async (t) => {
93132
const iterator = sqliteKeyv.iterator("keyv");
94133
const key = await iterator.next();
95134
t.expect(key.value).toBe(undefined);
96135
});
97136

137+
test.it("Async Keys 0 element test", async (t) => {
138+
const keys = sqliteKeyv.keys("keyv");
139+
const key = await keys.next();
140+
t.expect(key.value).toBe(undefined);
141+
});
142+
143+
test.it("Async Keys with pattern test", async (t) => {
144+
await sqliteKeyv.set("other", "bar");
145+
await sqliteKeyv.set("foo1", "bar");
146+
await sqliteKeyv.set("foo2", "bar1");
147+
await sqliteKeyv.set("foo3", "bar2");
148+
149+
for await (const key of sqliteKeyv.keys("foo*")) {
150+
t.expect(key).not.toBe("other");
151+
}
152+
});
153+
98154
test.it("close connection successfully", async (t) => {
99155
t.expect(await sqliteKeyv.get("foo")).toBe(undefined);
100156
await sqliteKeyv.set("foo", "bar");

src/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class KeyvSqlite extends EventEmitter implements KeyvStoreAdapter {
3333
updateCatches: (args: [string, unknown][], ttl?: number) => void;
3434
emptyCaches: () => void;
3535
findCaches: (namespace: string | undefined, limit: number, offset: number, expiredAt: number) => CacheObject[];
36+
findKeys: (pattern: string | undefined, limit: number, offset: number, expiredAt: number) => CacheObject[];
3637

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

@@ -140,6 +144,13 @@ CREATE INDEX IF NOT EXISTS idx_expired_caches ON ${tableName}(expiredAt);
140144
.all(`${namespace ? `${namespace}:` : ""}%`, expiredAt, limit, offset)
141145
.filter((data) => data !== undefined);
142146
};
147+
148+
this.findKeys = (pattern, limit, offset, expiredAt) => {
149+
const _pattern = pattern?.replaceAll("*", "%") ?? "%"
150+
return finderStatement
151+
.all(_pattern, expiredAt, limit, offset)
152+
.filter((data) => data !== undefined);
153+
};
143154
}
144155

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

228+
async *keys(pattern?: string) {
229+
const limit = Number.parseInt(this.opts.iterationLimit! as string, 10) || 10;
230+
const time = now();
231+
const find = this.findKeys;
232+
233+
// @ts-expect-error - iterate
234+
const iterate = async function* (offset: number) {
235+
const entries = find(pattern, limit, offset, time);
236+
237+
if (entries.length === 0) {
238+
return;
239+
}
240+
241+
for (const entry of entries) {
242+
// biome-ignore lint: <explanation>
243+
offset += 1;
244+
yield entry.cacheKey;
245+
}
246+
247+
yield* iterate(offset);
248+
};
249+
250+
yield* iterate(0);
251+
}
252+
217253
async disconnect() {
218254
this.sqlite.close();
219255
}

0 commit comments

Comments
 (0)