@fedify/sqlite 2.0.0-dev.1604 → 2.0.0-dev.161

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.
@@ -0,0 +1,44 @@
1
+
2
+ const { Temporal } = require("@js-temporal/polyfill");
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ const node_sqlite = require_rolldown_runtime.__toESM(require("node:sqlite"));
6
+
7
+ //#region src/sqlite.node.ts
8
+ var SqliteDatabase = class {
9
+ constructor(db) {
10
+ this.db = db;
11
+ }
12
+ prepare(sql) {
13
+ return new SqliteStatement(this.db.prepare(sql));
14
+ }
15
+ exec(sql) {
16
+ this.db.exec(sql);
17
+ }
18
+ close() {
19
+ this.db.close();
20
+ }
21
+ };
22
+ var SqliteStatement = class {
23
+ constructor(stmt) {
24
+ this.stmt = stmt;
25
+ }
26
+ run(...params) {
27
+ const result = this.stmt.run(...params);
28
+ return {
29
+ changes: Number(result.changes),
30
+ lastInsertRowid: Number(result.lastInsertRowid)
31
+ };
32
+ }
33
+ get(...params) {
34
+ return this.stmt.get(...params);
35
+ }
36
+ all(...params) {
37
+ return this.stmt.all(...params);
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ exports.PlatformDatabase = node_sqlite.DatabaseSync;
43
+ exports.SqliteDatabase = SqliteDatabase;
44
+ exports.SqliteStatement = SqliteStatement;
@@ -0,0 +1,23 @@
1
+ import { SqliteDatabaseAdapter, SqliteStatementAdapter } from "./adapter.cjs";
2
+ import { DatabaseSync, StatementSync } from "node:sqlite";
3
+
4
+ //#region src/sqlite.node.d.ts
5
+ declare class SqliteDatabase implements SqliteDatabaseAdapter {
6
+ private readonly db;
7
+ constructor(db: DatabaseSync);
8
+ prepare(sql: string): SqliteStatementAdapter;
9
+ exec(sql: string): void;
10
+ close(): void;
11
+ }
12
+ declare class SqliteStatement implements SqliteStatementAdapter {
13
+ private readonly stmt;
14
+ constructor(stmt: StatementSync);
15
+ run(...params: unknown[]): {
16
+ changes: number;
17
+ lastInsertRowid: number;
18
+ };
19
+ get(...params: unknown[]): unknown | undefined;
20
+ all(...params: unknown[]): unknown[];
21
+ }
22
+ //#endregion
23
+ export { DatabaseSync as PlatformDatabase, StatementSync as PlatformStatement, SqliteDatabase, SqliteStatement };
@@ -1,6 +1,6 @@
1
1
 
2
- import { Temporal } from "@js-temporal/polyfill";
3
-
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
4
  import { DatabaseSync } from "node:sqlite";
5
5
 
6
6
  //#region src/sqlite.node.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/sqlite",
3
- "version": "2.0.0-dev.1604+23a1ea67",
3
+ "version": "2.0.0-dev.161+b505ad7a",
4
4
  "description": "SQLite drivers for Fedify",
5
5
  "keywords": [
6
6
  "fedify",
@@ -22,18 +22,28 @@
22
22
  "https://github.com/sponsors/dahlia"
23
23
  ],
24
24
  "type": "module",
25
- "main": "./dist/mod.js",
25
+ "main": "./dist/mod.cjs",
26
26
  "module": "./dist/mod.js",
27
27
  "types": "./dist/mod.d.ts",
28
28
  "exports": {
29
29
  ".": {
30
- "types": "./dist/mod.d.ts",
30
+ "types": {
31
+ "import": "./dist/mod.d.ts",
32
+ "require": "./dist/mod.d.cts",
33
+ "default": "./dist/mod.d.ts"
34
+ },
31
35
  "import": "./dist/mod.js",
36
+ "require": "./dist/mod.cjs",
32
37
  "default": "./dist/mod.js"
33
38
  },
34
39
  "./kv": {
35
- "types": "./dist/kv.d.ts",
40
+ "types": {
41
+ "import": "./dist/kv.d.ts",
42
+ "require": "./dist/kv.d.cts",
43
+ "default": "./dist/kv.d.ts"
44
+ },
36
45
  "import": "./dist/kv.js",
46
+ "require": "./dist/kv.cjs",
37
47
  "default": "./dist/kv.js"
38
48
  },
39
49
  "./package.json": "./package.json"
@@ -47,17 +57,17 @@
47
57
  }
48
58
  },
49
59
  "dependencies": {
50
- "@logtape/logtape": "^1.0.0",
60
+ "@js-temporal/polyfill": "^0.5.1",
61
+ "@logtape/logtape": "^1.3.5",
51
62
  "es-toolkit": "^1.31.0"
52
63
  },
53
64
  "peerDependencies": {
54
- "@fedify/fedify": "^2.0.0-dev.1604+23a1ea67"
65
+ "@fedify/fedify": "^2.0.0-dev.161+b505ad7a"
55
66
  },
56
67
  "devDependencies": {
57
- "@js-temporal/polyfill": "^0.5.1",
58
68
  "@std/async": "npm:@jsr/std__async@^1.0.13",
59
69
  "tsdown": "^0.12.9",
60
- "typescript": "^5.9.2"
70
+ "typescript": "^5.9.3"
61
71
  },
62
72
  "scripts": {
63
73
  "build": "tsdown",
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 "./kv.ts";
7
7
 
8
8
  let Temporal: typeof temporal.Temporal;
9
9
  if ("Temporal" in globalThis) {
@@ -31,7 +31,7 @@ test("SqliteKvStore.initialize()", async () => {
31
31
  try {
32
32
  await store.initialize();
33
33
  const result = await db.prepare(`
34
- SELECT name FROM sqlite_master
34
+ SELECT name FROM sqlite_master
35
35
  WHERE type='table' AND name=?
36
36
  `).get(tableName);
37
37
  assert(result !== undefined);
@@ -180,7 +180,7 @@ test("SqliteKvStore.drop()", async () => {
180
180
  try {
181
181
  await store.drop();
182
182
  const result = await db.prepare(`
183
- SELECT name FROM sqlite_master
183
+ SELECT name FROM sqlite_master
184
184
  WHERE type='table' AND name=?
185
185
  `).get(tableName);
186
186
  // Bun returns null, Node returns undefined
@@ -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,6 +1,10 @@
1
1
  import { type PlatformDatabase, SqliteDatabase } from "#sqlite";
2
- import type { KvKey, KvStore, KvStoreSetOptions } from "@fedify/fedify";
3
- import { Temporal } from "@js-temporal/polyfill";
2
+ import type {
3
+ KvKey,
4
+ KvStore,
5
+ KvStoreListEntry,
6
+ KvStoreSetOptions,
7
+ } from "@fedify/fedify";
4
8
  import { getLogger } from "@logtape/logtape";
5
9
  import { isEqual } from "es-toolkit";
6
10
  import type { SqliteDatabaseAdapter } from "./adapter.ts";
@@ -81,8 +85,8 @@ export class SqliteKvStore implements KvStore {
81
85
 
82
86
  const result = this.#db
83
87
  .prepare(`
84
- SELECT value
85
- FROM "${this.#tableName}"
88
+ SELECT value
89
+ FROM "${this.#tableName}"
86
90
  WHERE key = ? AND (expires IS NULL OR expires > ?)
87
91
  `)
88
92
  .get(encodedKey, now);
@@ -170,8 +174,8 @@ export class SqliteKvStore implements KvStore {
170
174
 
171
175
  const currentResult = this.#db
172
176
  .prepare(`
173
- SELECT value
174
- FROM "${this.#tableName}"
177
+ SELECT value
178
+ FROM "${this.#tableName}"
175
179
  WHERE key = ? AND (expires IS NULL OR expires > ?)
176
180
  `)
177
181
  .get(encodedKey, now) as { value: string } | undefined;
@@ -213,6 +217,51 @@ export class SqliteKvStore implements KvStore {
213
217
  }
214
218
  }
215
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
+
216
265
  /**
217
266
  * Creates the table used by the key–value store if it does not already exist.
218
267
  * Does nothing if the table already exists.
@@ -236,7 +285,7 @@ export class SqliteKvStore implements KvStore {
236
285
  `);
237
286
 
238
287
  this.#db.exec(`
239
- CREATE INDEX IF NOT EXISTS "idx_${this.#tableName}_expires"
288
+ CREATE INDEX IF NOT EXISTS "idx_${this.#tableName}_expires"
240
289
  ON "${this.#tableName}" (expires)
241
290
  `);
242
291
 
package/tsdown.config.ts CHANGED
@@ -4,6 +4,7 @@ export default defineConfig({
4
4
  entry: ["src/mod.ts", "src/kv.ts", "src/sqlite.node.ts", "src/sqlite.bun.ts"],
5
5
  dts: true,
6
6
  unbundle: true,
7
+ format: ["esm", "cjs"],
7
8
  platform: "node",
8
9
  inputOptions: {
9
10
  onwarn(warning, defaultHandler) {
@@ -16,9 +17,16 @@ export default defineConfig({
16
17
  defaultHandler(warning);
17
18
  },
18
19
  },
19
- outputOptions: {
20
- intro: `
21
- import { Temporal } from "@js-temporal/polyfill";
22
- `,
20
+ outputOptions(outputOptions, format) {
21
+ if (format === "cjs") {
22
+ outputOptions.intro = `
23
+ const { Temporal } = require("@js-temporal/polyfill");
24
+ `;
25
+ } else {
26
+ outputOptions.intro = `
27
+ import { Temporal } from "@js-temporal/polyfill";
28
+ `;
29
+ }
30
+ return outputOptions;
23
31
  },
24
32
  });