@fedify/sqlite 2.0.0-pr.490.2 → 2.0.0-pr.559.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +3 -3
- package/deno.json +7 -4
- package/dist/dist/sqlite.node.d.cts +2 -0
- package/dist/kv.cjs +30 -0
- package/dist/kv.d.cts +11 -6
- package/dist/kv.d.ts +8 -3
- package/dist/kv.js +30 -0
- package/dist/mod.cjs +3 -1
- package/dist/mod.d.cts +2 -1
- package/dist/mod.d.ts +2 -1
- package/dist/mod.js +2 -1
- package/dist/mq.cjs +393 -0
- package/dist/mq.d.cts +112 -0
- package/dist/mq.d.ts +113 -0
- package/dist/mq.js +392 -0
- package/package.json +22 -8
- package/src/kv.test.ts +94 -1
- package/src/kv.ts +53 -3
- package/src/mod.ts +1 -0
- package/src/mq.test.ts +24 -0
- package/src/mq.ts +623 -0
- package/tsdown.config.ts +7 -1
package/src/kv.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PlatformDatabase } from "#sqlite";
|
|
2
|
+
import { SqliteKvStore } from "@fedify/sqlite/kv";
|
|
2
3
|
import * as temporal from "@js-temporal/polyfill";
|
|
3
4
|
import { delay } from "@std/async/delay";
|
|
4
5
|
import assert from "node:assert/strict";
|
|
5
6
|
import { test } from "node:test";
|
|
6
|
-
import { SqliteKvStore } from "@fedify/sqlite/kv";
|
|
7
7
|
|
|
8
8
|
let Temporal: typeof temporal.Temporal;
|
|
9
9
|
if ("Temporal" in globalThis) {
|
|
@@ -310,3 +310,96 @@ test("SqliteKvStore.set() - preserves created timestamp on update", async () =>
|
|
|
310
310
|
await db.close();
|
|
311
311
|
}
|
|
312
312
|
});
|
|
313
|
+
|
|
314
|
+
test("SqliteKvStore.list()", async () => {
|
|
315
|
+
const { db, store } = getStore();
|
|
316
|
+
try {
|
|
317
|
+
await store.set(["prefix", "a"], "value-a");
|
|
318
|
+
await store.set(["prefix", "b"], "value-b");
|
|
319
|
+
await store.set(["prefix", "nested", "c"], "value-c");
|
|
320
|
+
await store.set(["other", "x"], "value-x");
|
|
321
|
+
|
|
322
|
+
const entries: { key: readonly string[]; value: unknown }[] = [];
|
|
323
|
+
for await (const entry of store.list(["prefix"])) {
|
|
324
|
+
entries.push({ key: entry.key, value: entry.value });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
assert.strictEqual(entries.length, 3);
|
|
328
|
+
assert(
|
|
329
|
+
entries.some((e) => e.key[1] === "a" && e.value === "value-a"),
|
|
330
|
+
);
|
|
331
|
+
assert(entries.some((e) => e.key[1] === "b"));
|
|
332
|
+
assert(entries.some((e) => e.key[1] === "nested"));
|
|
333
|
+
} finally {
|
|
334
|
+
await store.drop();
|
|
335
|
+
await db.close();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("SqliteKvStore.list() - excludes expired", async () => {
|
|
340
|
+
const { db, tableName, store } = getStore();
|
|
341
|
+
try {
|
|
342
|
+
await store.initialize();
|
|
343
|
+
const now = Temporal.Now.instant().epochMilliseconds;
|
|
344
|
+
|
|
345
|
+
// Insert expired entry directly
|
|
346
|
+
db.prepare(`
|
|
347
|
+
INSERT INTO "${tableName}" (key, value, created, expires)
|
|
348
|
+
VALUES (?, ?, ?, ?)
|
|
349
|
+
`).run(
|
|
350
|
+
JSON.stringify(["list-test", "expired"]),
|
|
351
|
+
JSON.stringify("expired-value"),
|
|
352
|
+
now - 1000,
|
|
353
|
+
now - 500,
|
|
354
|
+
);
|
|
355
|
+
await store.set(["list-test", "valid"], "valid-value");
|
|
356
|
+
|
|
357
|
+
const entries: { key: readonly string[]; value: unknown }[] = [];
|
|
358
|
+
for await (const entry of store.list(["list-test"])) {
|
|
359
|
+
entries.push({ key: entry.key, value: entry.value });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
assert.strictEqual(entries.length, 1);
|
|
363
|
+
assert.deepStrictEqual(entries[0].key, ["list-test", "valid"]);
|
|
364
|
+
} finally {
|
|
365
|
+
await store.drop();
|
|
366
|
+
await db.close();
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("SqliteKvStore.list() - single element key", async () => {
|
|
371
|
+
const { db, store } = getStore();
|
|
372
|
+
try {
|
|
373
|
+
await store.set(["a"], "value-a");
|
|
374
|
+
await store.set(["b"], "value-b");
|
|
375
|
+
|
|
376
|
+
const entries: { key: readonly string[]; value: unknown }[] = [];
|
|
377
|
+
for await (const entry of store.list(["a"])) {
|
|
378
|
+
entries.push({ key: entry.key, value: entry.value });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
assert.strictEqual(entries.length, 1);
|
|
382
|
+
} finally {
|
|
383
|
+
await store.drop();
|
|
384
|
+
await db.close();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("SqliteKvStore.list() - empty prefix", async () => {
|
|
389
|
+
const { db, store } = getStore();
|
|
390
|
+
try {
|
|
391
|
+
await store.set(["a"], "value-a");
|
|
392
|
+
await store.set(["b", "c"], "value-bc");
|
|
393
|
+
await store.set(["d", "e", "f"], "value-def");
|
|
394
|
+
|
|
395
|
+
const entries: { key: readonly string[]; value: unknown }[] = [];
|
|
396
|
+
for await (const entry of store.list()) {
|
|
397
|
+
entries.push({ key: entry.key, value: entry.value });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
assert.strictEqual(entries.length, 3);
|
|
401
|
+
} finally {
|
|
402
|
+
await store.drop();
|
|
403
|
+
await db.close();
|
|
404
|
+
}
|
|
405
|
+
});
|
package/src/kv.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type PlatformDatabase, SqliteDatabase } from "#sqlite";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
KvKey,
|
|
4
|
+
KvStore,
|
|
5
|
+
KvStoreListEntry,
|
|
6
|
+
KvStoreSetOptions,
|
|
7
|
+
} from "@fedify/fedify";
|
|
3
8
|
import { getLogger } from "@logtape/logtape";
|
|
4
9
|
import { isEqual } from "es-toolkit";
|
|
5
10
|
import type { SqliteDatabaseAdapter } from "./adapter.ts";
|
|
@@ -16,13 +21,13 @@ export interface SqliteKvStoreOptions {
|
|
|
16
21
|
* `"fedify_kv"` by default.
|
|
17
22
|
* @default `"fedify_kv"`
|
|
18
23
|
*/
|
|
19
|
-
tableName?: string;
|
|
24
|
+
readonly tableName?: string;
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* Whether the table has been initialized. `false` by default.
|
|
23
28
|
* @default `false`
|
|
24
29
|
*/
|
|
25
|
-
initialized?: boolean;
|
|
30
|
+
readonly initialized?: boolean;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -212,6 +217,51 @@ export class SqliteKvStore implements KvStore {
|
|
|
212
217
|
}
|
|
213
218
|
}
|
|
214
219
|
|
|
220
|
+
/**
|
|
221
|
+
* {@inheritDoc KvStore.list}
|
|
222
|
+
* @since 1.10.0
|
|
223
|
+
*/
|
|
224
|
+
async *list(prefix?: KvKey): AsyncIterable<KvStoreListEntry> {
|
|
225
|
+
this.initialize();
|
|
226
|
+
|
|
227
|
+
const now = Temporal.Now.instant().epochMilliseconds;
|
|
228
|
+
|
|
229
|
+
let results: { key: string; value: string }[];
|
|
230
|
+
|
|
231
|
+
if (prefix == null || prefix.length === 0) {
|
|
232
|
+
// Empty prefix: return all entries
|
|
233
|
+
results = this.#db
|
|
234
|
+
.prepare(`
|
|
235
|
+
SELECT key, value
|
|
236
|
+
FROM "${this.#tableName}"
|
|
237
|
+
WHERE expires IS NULL OR expires > ?
|
|
238
|
+
ORDER BY key
|
|
239
|
+
`)
|
|
240
|
+
.all(now) as { key: string; value: string }[];
|
|
241
|
+
} else {
|
|
242
|
+
// JSON pattern: '["prefix","' matches keys starting with prefix
|
|
243
|
+
const pattern = JSON.stringify(prefix).slice(0, -1) + ",%";
|
|
244
|
+
const exactKey = JSON.stringify(prefix);
|
|
245
|
+
|
|
246
|
+
results = this.#db
|
|
247
|
+
.prepare(`
|
|
248
|
+
SELECT key, value
|
|
249
|
+
FROM "${this.#tableName}"
|
|
250
|
+
WHERE (key LIKE ? ESCAPE '\\' OR key = ?)
|
|
251
|
+
AND (expires IS NULL OR expires > ?)
|
|
252
|
+
ORDER BY key
|
|
253
|
+
`)
|
|
254
|
+
.all(pattern, exactKey, now) as { key: string; value: string }[];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const row of results) {
|
|
258
|
+
yield {
|
|
259
|
+
key: this.#decodeKey(row.key),
|
|
260
|
+
value: this.#decodeValue(row.value),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
215
265
|
/**
|
|
216
266
|
* Creates the table used by the key–value store if it does not already exist.
|
|
217
267
|
* Does nothing if the table already exists.
|
package/src/mod.ts
CHANGED
package/src/mq.test.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PlatformDatabase } from "#sqlite";
|
|
2
|
+
import { test } from "@fedify/fixture";
|
|
3
|
+
import { SqliteMessageQueue } from "@fedify/sqlite/mq";
|
|
4
|
+
import { getRandomKey, testMessageQueue } from "@fedify/testing";
|
|
5
|
+
import { mkdtemp } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
const dbDir = await mkdtemp(join(tmpdir(), "fedify-sqlite-"));
|
|
10
|
+
const dbPath = join(dbDir, `${getRandomKey("sqlite")}.db`);
|
|
11
|
+
const db = new PlatformDatabase(dbPath);
|
|
12
|
+
const tableName = getRandomKey("message").replaceAll("-", "_");
|
|
13
|
+
|
|
14
|
+
test("SqliteMessageQueue", () =>
|
|
15
|
+
testMessageQueue(
|
|
16
|
+
() => new SqliteMessageQueue(db, { tableName }),
|
|
17
|
+
({ mq1, mq2, controller }) => {
|
|
18
|
+
controller.abort();
|
|
19
|
+
mq1.drop();
|
|
20
|
+
mq1[Symbol.dispose]();
|
|
21
|
+
mq2[Symbol.dispose]();
|
|
22
|
+
},
|
|
23
|
+
{ testOrderingKey: true },
|
|
24
|
+
));
|