@monlite/kv 0.1.1 → 0.2.1
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/README.md +26 -20
- package/dist/index.cjs +10 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @monlite/kv
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A Redis-like key-value cache for [`@monlite/core`](https://www.npmjs.com/package/@monlite/core),
|
|
4
|
+
backed by SQLite. Synchronous `get`/`set`/`incr` with TTLs — part of the local AI-agent harness
|
|
5
|
+
(cache + queue + cron, replacing Redis locally).
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
8
|
npm install @monlite/core @monlite/kv
|
|
@@ -16,40 +18,44 @@ const db = createDb("app.db");
|
|
|
16
18
|
const cache = kv(db);
|
|
17
19
|
|
|
18
20
|
cache.set("session:42", { user: "ali" }, { ttl: 60_000 }); // expires in 60s
|
|
19
|
-
cache.get("session:42");
|
|
20
|
-
cache.incr("hits");
|
|
21
|
-
cache.incr("hits", 5);
|
|
22
|
-
cache.ttl("session:42");
|
|
21
|
+
cache.get("session:42"); // { user: "ali" } — synchronous, no await
|
|
22
|
+
cache.incr("hits"); // 1
|
|
23
|
+
cache.incr("hits", 5); // 6
|
|
24
|
+
cache.ttl("session:42"); // ~60000 (ms remaining)
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
restarts), and
|
|
27
|
-
|
|
27
|
+
The cache is **synchronous** (local SQLite — no network, no await), **durable** (survives
|
|
28
|
+
restarts), and stored in your app's database. Use `:memory:` if you want a purely ephemeral
|
|
29
|
+
cache.
|
|
28
30
|
|
|
29
31
|
## API
|
|
30
32
|
|
|
31
33
|
| Method | Description |
|
|
32
|
-
|
|
34
|
+
|---|---|
|
|
33
35
|
| `get(key)` | Value, or `undefined` (also if expired). |
|
|
34
|
-
| `set(key, value, { ttl })` | Store any JSON value; `ttl` in ms (optional). |
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `keys
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
36
|
+
| `set(key, value, { ttl? })` | Store any JSON-serializable value; `ttl` in ms (optional). |
|
|
37
|
+
| `setNX(key, value, { ttl? })` | Atomic set-if-absent (Redis `SET NX`); returns `true` if acquired. The lock/nonce primitive. |
|
|
38
|
+
| `has(key)` / `delete(key)` | Existence check / removal. |
|
|
39
|
+
| `incr(key, by?)` / `decr(key, by?)` | Atomic numeric increment/decrement; returns the new value. |
|
|
40
|
+
| `mget(keys)` | Array of values (`undefined` per missing/expired key). |
|
|
41
|
+
| `keys(prefix?)` | Live keys in the namespace, optionally filtered by prefix. |
|
|
42
|
+
| `expire(key, ttl)` | Set or refresh the TTL (ms); returns `false` if the key is absent. |
|
|
43
|
+
| `ttl(key)` | Remaining ms; `-1` = no expiry, `-2` = key absent (Redis convention). |
|
|
44
|
+
| `size()` / `flush()` | Count / clear all keys in the namespace. |
|
|
45
|
+
| `stop()` | Clear the sweep timer (if `sweepIntervalMs` was set). |
|
|
42
46
|
|
|
43
47
|
## Options
|
|
44
48
|
|
|
45
49
|
```ts
|
|
46
50
|
kv(db, {
|
|
47
|
-
namespace: "sessions",
|
|
51
|
+
namespace: "sessions", // isolate multiple caches in one db (default: "default")
|
|
48
52
|
sweepIntervalMs: 60_000, // periodically purge expired keys (default: lazy-only)
|
|
49
53
|
});
|
|
50
54
|
```
|
|
51
55
|
|
|
52
|
-
Expired keys are removed lazily on read
|
|
53
|
-
|
|
56
|
+
Expired keys are removed lazily on read. Set `sweepIntervalMs` to also purge them on a timer.
|
|
57
|
+
Call `cache.stop()` to clear the timer when shutting down.
|
|
58
|
+
|
|
59
|
+
## License
|
|
54
60
|
|
|
55
61
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,15 @@ function kv(db, options = {}) {
|
|
|
36
36
|
const expires = opts?.ttl != null ? now() + opts.ttl : null;
|
|
37
37
|
setRaw(key, JSON.stringify(value), expires);
|
|
38
38
|
},
|
|
39
|
+
setNX(key, value, opts) {
|
|
40
|
+
return driver.transaction(() => {
|
|
41
|
+
const row = getRow(key);
|
|
42
|
+
if (fresh(row)) return false;
|
|
43
|
+
const expires = opts?.ttl != null ? now() + opts.ttl : null;
|
|
44
|
+
setRaw(key, JSON.stringify(value), expires);
|
|
45
|
+
return true;
|
|
46
|
+
}, true);
|
|
47
|
+
},
|
|
39
48
|
has(key) {
|
|
40
49
|
return api.get(key) !== void 0;
|
|
41
50
|
},
|
|
@@ -58,7 +67,7 @@ function kv(db, options = {}) {
|
|
|
58
67
|
const next = n + by;
|
|
59
68
|
setRaw(key, JSON.stringify(next), expires);
|
|
60
69
|
return next;
|
|
61
|
-
});
|
|
70
|
+
}, true);
|
|
62
71
|
},
|
|
63
72
|
decr(key, by = 1) {
|
|
64
73
|
return api.incr(key, -by);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Monlite } from \"@monlite/core\";\n\nexport interface KVOptions {\n /** Logical namespace so multiple caches can share one database. Default \"default\". */\n namespace?: string;\n /** If set, a timer periodically purges expired keys (ms). Default: lazy-only. */\n sweepIntervalMs?: number;\n}\n\n/**\n * A synchronous, Redis-like key-value cache backed by SQLite. Values are any\n * JSON-serializable data; TTLs are in milliseconds.\n */\nexport interface KV {\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any, opts?: { ttl?: number }): void;\n has(key: string): boolean;\n delete(key: string): boolean;\n /** Atomically add `by` (default 1) to a numeric key; returns the new value. */\n incr(key: string, by?: number): number;\n decr(key: string, by?: number): number;\n mget<T = any>(keys: string[]): (T | undefined)[];\n /** Keys in this namespace (optionally by prefix), excluding expired ones. */\n keys(prefix?: string): string[];\n /** Set/refresh a key's TTL (ms). Returns false if the key is absent. */\n expire(key: string, ttl: number): boolean;\n /** Remaining TTL in ms; `-1` if no expiry, `-2` if absent (Redis convention). */\n ttl(key: string): number;\n /** Delete all keys in this namespace. */\n flush(): void;\n /** Number of live keys in this namespace. */\n size(): number;\n /** Stop the sweep timer (if any). */\n stop(): void;\n}\n\nconst ensured = new WeakSet<object>();\n\n/**\n * Create a cache over a monlite database.\n *\n * ```ts\n * const cache = kv(db);\n * cache.set(\"session:42\", { user: \"ali\" }, { ttl: 60_000 });\n * cache.get(\"session:42\"); // { user: \"ali\" } (synchronous)\n * ```\n */\nexport function kv(db: Monlite, options: KVOptions = {}): KV {\n const ns = options.namespace ?? \"default\";\n const driver = db.driver;\n\n if (!ensured.has(db)) {\n driver.exec(\n `CREATE TABLE IF NOT EXISTS _kv (\n ns TEXT NOT NULL, k TEXT NOT NULL, v TEXT NOT NULL,\n expires_at INTEGER, PRIMARY KEY (ns, k)\n )`,\n );\n ensured.add(db);\n }\n\n const now = () => Date.now();\n const fresh = (row: any): boolean =>\n !!row && !(row.expires_at != null && row.expires_at <= now());\n\n const getRow = (key: string) =>\n driver\n .prepare(`SELECT v, expires_at FROM _kv WHERE ns = ? AND k = ?`)\n .get(ns, key) as { v: string; expires_at: number | null } | undefined;\n\n const del = (key: string): boolean =>\n driver.prepare(`DELETE FROM _kv WHERE ns = ? AND k = ?`).run(ns, key)\n .changes > 0;\n\n const setRaw = (key: string, v: string, expires: number | null) =>\n driver\n .prepare(\n `INSERT INTO _kv (ns, k, v, expires_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(ns, k) DO UPDATE SET v = excluded.v, expires_at = excluded.expires_at`,\n )\n .run(ns, key, v, expires);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const api: KV = {\n get(key) {\n const row = getRow(key);\n if (!fresh(row)) {\n if (row) del(key);\n return undefined;\n }\n return JSON.parse(row!.v);\n },\n set(key, value, opts) {\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n },\n has(key) {\n return api.get(key) !== undefined;\n },\n delete(key) {\n return del(key);\n },\n incr(key, by = 1) {\n return driver.transaction(() => {\n const row = getRow(key);\n let n = 0;\n let expires: number | null = null;\n if (fresh(row)) {\n const cur = JSON.parse(row!.v);\n if (typeof cur !== \"number\") {\n throw new Error(`kv.incr: value at \"${key}\" is not a number`);\n }\n n = cur;\n expires = row!.expires_at;\n }\n const next = n + by;\n setRaw(key, JSON.stringify(next), expires);\n return next;\n });\n },\n decr(key, by = 1) {\n return api.incr(key, -by);\n },\n mget(keys) {\n return keys.map((k) => api.get(k));\n },\n keys(prefix) {\n const t = now();\n const rows = (\n prefix !== undefined\n ? driver\n .prepare(\n `SELECT k, expires_at FROM _kv WHERE ns = ? AND k LIKE ? ESCAPE '\\\\'`,\n )\n .all(ns, prefix.replace(/[%_\\\\]/g, \"\\\\$&\") + \"%\")\n : driver.prepare(`SELECT k, expires_at FROM _kv WHERE ns = ?`).all(ns)\n ) as Array<{ k: string; expires_at: number | null }>;\n return rows\n .filter((r) => r.expires_at == null || r.expires_at > t)\n .map((r) => r.k);\n },\n expire(key, ttl) {\n const row = getRow(key);\n if (!fresh(row)) return false;\n driver\n .prepare(`UPDATE _kv SET expires_at = ? WHERE ns = ? AND k = ?`)\n .run(now() + ttl, ns, key);\n return true;\n },\n ttl(key) {\n const row = getRow(key);\n if (!fresh(row)) return -2;\n if (row!.expires_at == null) return -1;\n return row!.expires_at - now();\n },\n flush() {\n driver.prepare(`DELETE FROM _kv WHERE ns = ?`).run(ns);\n },\n size() {\n return (\n driver\n .prepare(\n `SELECT COUNT(*) AS n FROM _kv WHERE ns = ? AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(ns, now()) as { n: number }\n ).n;\n },\n stop() {\n if (timer) clearInterval(timer);\n timer = undefined;\n },\n };\n\n if (options.sweepIntervalMs && options.sweepIntervalMs > 0) {\n timer = setInterval(() => {\n driver\n .prepare(\n `DELETE FROM _kv WHERE expires_at IS NOT NULL AND expires_at <= ?`,\n )\n .run(now());\n }, options.sweepIntervalMs);\n timer.unref?.();\n }\n\n return api;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Monlite } from \"@monlite/core\";\n\nexport interface KVOptions {\n /** Logical namespace so multiple caches can share one database. Default \"default\". */\n namespace?: string;\n /** If set, a timer periodically purges expired keys (ms). Default: lazy-only. */\n sweepIntervalMs?: number;\n}\n\n/**\n * A synchronous, Redis-like key-value cache backed by SQLite. Values are any\n * JSON-serializable data; TTLs are in milliseconds.\n */\nexport interface KV {\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any, opts?: { ttl?: number }): void;\n /**\n * Atomically set the key only if it isn't already present (Redis `SET NX`).\n * Returns `true` if set, `false` if a live key already existed. The lock\n * primitive for single-instance schedulers, nonces, and once-only work.\n */\n setNX(key: string, value: any, opts?: { ttl?: number }): boolean;\n has(key: string): boolean;\n delete(key: string): boolean;\n /** Atomically add `by` (default 1) to a numeric key; returns the new value. */\n incr(key: string, by?: number): number;\n decr(key: string, by?: number): number;\n mget<T = any>(keys: string[]): (T | undefined)[];\n /** Keys in this namespace (optionally by prefix), excluding expired ones. */\n keys(prefix?: string): string[];\n /** Set/refresh a key's TTL (ms). Returns false if the key is absent. */\n expire(key: string, ttl: number): boolean;\n /** Remaining TTL in ms; `-1` if no expiry, `-2` if absent (Redis convention). */\n ttl(key: string): number;\n /** Delete all keys in this namespace. */\n flush(): void;\n /** Number of live keys in this namespace. */\n size(): number;\n /** Stop the sweep timer (if any). */\n stop(): void;\n}\n\nconst ensured = new WeakSet<object>();\n\n/**\n * Create a cache over a monlite database.\n *\n * ```ts\n * const cache = kv(db);\n * cache.set(\"session:42\", { user: \"ali\" }, { ttl: 60_000 });\n * cache.get(\"session:42\"); // { user: \"ali\" } (synchronous)\n * ```\n */\nexport function kv(db: Monlite, options: KVOptions = {}): KV {\n const ns = options.namespace ?? \"default\";\n const driver = db.driver;\n\n if (!ensured.has(db)) {\n driver.exec(\n `CREATE TABLE IF NOT EXISTS _kv (\n ns TEXT NOT NULL, k TEXT NOT NULL, v TEXT NOT NULL,\n expires_at INTEGER, PRIMARY KEY (ns, k)\n )`,\n );\n ensured.add(db);\n }\n\n const now = () => Date.now();\n const fresh = (row: any): boolean =>\n !!row && !(row.expires_at != null && row.expires_at <= now());\n\n const getRow = (key: string) =>\n driver\n .prepare(`SELECT v, expires_at FROM _kv WHERE ns = ? AND k = ?`)\n .get(ns, key) as { v: string; expires_at: number | null } | undefined;\n\n const del = (key: string): boolean =>\n driver.prepare(`DELETE FROM _kv WHERE ns = ? AND k = ?`).run(ns, key)\n .changes > 0;\n\n const setRaw = (key: string, v: string, expires: number | null) =>\n driver\n .prepare(\n `INSERT INTO _kv (ns, k, v, expires_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(ns, k) DO UPDATE SET v = excluded.v, expires_at = excluded.expires_at`,\n )\n .run(ns, key, v, expires);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const api: KV = {\n get(key) {\n const row = getRow(key);\n if (!fresh(row)) {\n if (row) del(key);\n return undefined;\n }\n return JSON.parse(row!.v);\n },\n set(key, value, opts) {\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n },\n setNX(key, value, opts) {\n // IMMEDIATE: take the write lock up front so two processes racing the same\n // key can't deadlock on lock upgrade — the loser cleanly gets `false`.\n return driver.transaction(() => {\n const row = getRow(key);\n if (fresh(row)) return false; // a live key already exists\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n return true;\n }, true);\n },\n has(key) {\n return api.get(key) !== undefined;\n },\n delete(key) {\n return del(key);\n },\n incr(key, by = 1) {\n return driver.transaction(() => { // IMMEDIATE: read-modify-write needs the write lock up front\n const row = getRow(key);\n let n = 0;\n let expires: number | null = null;\n if (fresh(row)) {\n const cur = JSON.parse(row!.v);\n if (typeof cur !== \"number\") {\n throw new Error(`kv.incr: value at \"${key}\" is not a number`);\n }\n n = cur;\n expires = row!.expires_at;\n }\n const next = n + by;\n setRaw(key, JSON.stringify(next), expires);\n return next;\n }, true);\n },\n decr(key, by = 1) {\n return api.incr(key, -by);\n },\n mget(keys) {\n return keys.map((k) => api.get(k));\n },\n keys(prefix) {\n const t = now();\n const rows = (\n prefix !== undefined\n ? driver\n .prepare(\n `SELECT k, expires_at FROM _kv WHERE ns = ? AND k LIKE ? ESCAPE '\\\\'`,\n )\n .all(ns, prefix.replace(/[%_\\\\]/g, \"\\\\$&\") + \"%\")\n : driver.prepare(`SELECT k, expires_at FROM _kv WHERE ns = ?`).all(ns)\n ) as Array<{ k: string; expires_at: number | null }>;\n return rows\n .filter((r) => r.expires_at == null || r.expires_at > t)\n .map((r) => r.k);\n },\n expire(key, ttl) {\n const row = getRow(key);\n if (!fresh(row)) return false;\n driver\n .prepare(`UPDATE _kv SET expires_at = ? WHERE ns = ? AND k = ?`)\n .run(now() + ttl, ns, key);\n return true;\n },\n ttl(key) {\n const row = getRow(key);\n if (!fresh(row)) return -2;\n if (row!.expires_at == null) return -1;\n return row!.expires_at - now();\n },\n flush() {\n driver.prepare(`DELETE FROM _kv WHERE ns = ?`).run(ns);\n },\n size() {\n return (\n driver\n .prepare(\n `SELECT COUNT(*) AS n FROM _kv WHERE ns = ? AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(ns, now()) as { n: number }\n ).n;\n },\n stop() {\n if (timer) clearInterval(timer);\n timer = undefined;\n },\n };\n\n if (options.sweepIntervalMs && options.sweepIntervalMs > 0) {\n timer = setInterval(() => {\n driver\n .prepare(\n `DELETE FROM _kv WHERE expires_at IS NOT NULL AND expires_at <= ?`,\n )\n .run(now());\n }, options.sweepIntervalMs);\n timer.unref?.();\n }\n\n return api;\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -15,6 +15,14 @@ interface KV {
|
|
|
15
15
|
set(key: string, value: any, opts?: {
|
|
16
16
|
ttl?: number;
|
|
17
17
|
}): void;
|
|
18
|
+
/**
|
|
19
|
+
* Atomically set the key only if it isn't already present (Redis `SET NX`).
|
|
20
|
+
* Returns `true` if set, `false` if a live key already existed. The lock
|
|
21
|
+
* primitive for single-instance schedulers, nonces, and once-only work.
|
|
22
|
+
*/
|
|
23
|
+
setNX(key: string, value: any, opts?: {
|
|
24
|
+
ttl?: number;
|
|
25
|
+
}): boolean;
|
|
18
26
|
has(key: string): boolean;
|
|
19
27
|
delete(key: string): boolean;
|
|
20
28
|
/** Atomically add `by` (default 1) to a numeric key; returns the new value. */
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ interface KV {
|
|
|
15
15
|
set(key: string, value: any, opts?: {
|
|
16
16
|
ttl?: number;
|
|
17
17
|
}): void;
|
|
18
|
+
/**
|
|
19
|
+
* Atomically set the key only if it isn't already present (Redis `SET NX`).
|
|
20
|
+
* Returns `true` if set, `false` if a live key already existed. The lock
|
|
21
|
+
* primitive for single-instance schedulers, nonces, and once-only work.
|
|
22
|
+
*/
|
|
23
|
+
setNX(key: string, value: any, opts?: {
|
|
24
|
+
ttl?: number;
|
|
25
|
+
}): boolean;
|
|
18
26
|
has(key: string): boolean;
|
|
19
27
|
delete(key: string): boolean;
|
|
20
28
|
/** Atomically add `by` (default 1) to a numeric key; returns the new value. */
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,15 @@ function kv(db, options = {}) {
|
|
|
34
34
|
const expires = opts?.ttl != null ? now() + opts.ttl : null;
|
|
35
35
|
setRaw(key, JSON.stringify(value), expires);
|
|
36
36
|
},
|
|
37
|
+
setNX(key, value, opts) {
|
|
38
|
+
return driver.transaction(() => {
|
|
39
|
+
const row = getRow(key);
|
|
40
|
+
if (fresh(row)) return false;
|
|
41
|
+
const expires = opts?.ttl != null ? now() + opts.ttl : null;
|
|
42
|
+
setRaw(key, JSON.stringify(value), expires);
|
|
43
|
+
return true;
|
|
44
|
+
}, true);
|
|
45
|
+
},
|
|
37
46
|
has(key) {
|
|
38
47
|
return api.get(key) !== void 0;
|
|
39
48
|
},
|
|
@@ -56,7 +65,7 @@ function kv(db, options = {}) {
|
|
|
56
65
|
const next = n + by;
|
|
57
66
|
setRaw(key, JSON.stringify(next), expires);
|
|
58
67
|
return next;
|
|
59
|
-
});
|
|
68
|
+
}, true);
|
|
60
69
|
},
|
|
61
70
|
decr(key, by = 1) {
|
|
62
71
|
return api.incr(key, -by);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Monlite } from \"@monlite/core\";\n\nexport interface KVOptions {\n /** Logical namespace so multiple caches can share one database. Default \"default\". */\n namespace?: string;\n /** If set, a timer periodically purges expired keys (ms). Default: lazy-only. */\n sweepIntervalMs?: number;\n}\n\n/**\n * A synchronous, Redis-like key-value cache backed by SQLite. Values are any\n * JSON-serializable data; TTLs are in milliseconds.\n */\nexport interface KV {\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any, opts?: { ttl?: number }): void;\n has(key: string): boolean;\n delete(key: string): boolean;\n /** Atomically add `by` (default 1) to a numeric key; returns the new value. */\n incr(key: string, by?: number): number;\n decr(key: string, by?: number): number;\n mget<T = any>(keys: string[]): (T | undefined)[];\n /** Keys in this namespace (optionally by prefix), excluding expired ones. */\n keys(prefix?: string): string[];\n /** Set/refresh a key's TTL (ms). Returns false if the key is absent. */\n expire(key: string, ttl: number): boolean;\n /** Remaining TTL in ms; `-1` if no expiry, `-2` if absent (Redis convention). */\n ttl(key: string): number;\n /** Delete all keys in this namespace. */\n flush(): void;\n /** Number of live keys in this namespace. */\n size(): number;\n /** Stop the sweep timer (if any). */\n stop(): void;\n}\n\nconst ensured = new WeakSet<object>();\n\n/**\n * Create a cache over a monlite database.\n *\n * ```ts\n * const cache = kv(db);\n * cache.set(\"session:42\", { user: \"ali\" }, { ttl: 60_000 });\n * cache.get(\"session:42\"); // { user: \"ali\" } (synchronous)\n * ```\n */\nexport function kv(db: Monlite, options: KVOptions = {}): KV {\n const ns = options.namespace ?? \"default\";\n const driver = db.driver;\n\n if (!ensured.has(db)) {\n driver.exec(\n `CREATE TABLE IF NOT EXISTS _kv (\n ns TEXT NOT NULL, k TEXT NOT NULL, v TEXT NOT NULL,\n expires_at INTEGER, PRIMARY KEY (ns, k)\n )`,\n );\n ensured.add(db);\n }\n\n const now = () => Date.now();\n const fresh = (row: any): boolean =>\n !!row && !(row.expires_at != null && row.expires_at <= now());\n\n const getRow = (key: string) =>\n driver\n .prepare(`SELECT v, expires_at FROM _kv WHERE ns = ? AND k = ?`)\n .get(ns, key) as { v: string; expires_at: number | null } | undefined;\n\n const del = (key: string): boolean =>\n driver.prepare(`DELETE FROM _kv WHERE ns = ? AND k = ?`).run(ns, key)\n .changes > 0;\n\n const setRaw = (key: string, v: string, expires: number | null) =>\n driver\n .prepare(\n `INSERT INTO _kv (ns, k, v, expires_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(ns, k) DO UPDATE SET v = excluded.v, expires_at = excluded.expires_at`,\n )\n .run(ns, key, v, expires);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const api: KV = {\n get(key) {\n const row = getRow(key);\n if (!fresh(row)) {\n if (row) del(key);\n return undefined;\n }\n return JSON.parse(row!.v);\n },\n set(key, value, opts) {\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n },\n has(key) {\n return api.get(key) !== undefined;\n },\n delete(key) {\n return del(key);\n },\n incr(key, by = 1) {\n return driver.transaction(() => {\n const row = getRow(key);\n let n = 0;\n let expires: number | null = null;\n if (fresh(row)) {\n const cur = JSON.parse(row!.v);\n if (typeof cur !== \"number\") {\n throw new Error(`kv.incr: value at \"${key}\" is not a number`);\n }\n n = cur;\n expires = row!.expires_at;\n }\n const next = n + by;\n setRaw(key, JSON.stringify(next), expires);\n return next;\n });\n },\n decr(key, by = 1) {\n return api.incr(key, -by);\n },\n mget(keys) {\n return keys.map((k) => api.get(k));\n },\n keys(prefix) {\n const t = now();\n const rows = (\n prefix !== undefined\n ? driver\n .prepare(\n `SELECT k, expires_at FROM _kv WHERE ns = ? AND k LIKE ? ESCAPE '\\\\'`,\n )\n .all(ns, prefix.replace(/[%_\\\\]/g, \"\\\\$&\") + \"%\")\n : driver.prepare(`SELECT k, expires_at FROM _kv WHERE ns = ?`).all(ns)\n ) as Array<{ k: string; expires_at: number | null }>;\n return rows\n .filter((r) => r.expires_at == null || r.expires_at > t)\n .map((r) => r.k);\n },\n expire(key, ttl) {\n const row = getRow(key);\n if (!fresh(row)) return false;\n driver\n .prepare(`UPDATE _kv SET expires_at = ? WHERE ns = ? AND k = ?`)\n .run(now() + ttl, ns, key);\n return true;\n },\n ttl(key) {\n const row = getRow(key);\n if (!fresh(row)) return -2;\n if (row!.expires_at == null) return -1;\n return row!.expires_at - now();\n },\n flush() {\n driver.prepare(`DELETE FROM _kv WHERE ns = ?`).run(ns);\n },\n size() {\n return (\n driver\n .prepare(\n `SELECT COUNT(*) AS n FROM _kv WHERE ns = ? AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(ns, now()) as { n: number }\n ).n;\n },\n stop() {\n if (timer) clearInterval(timer);\n timer = undefined;\n },\n };\n\n if (options.sweepIntervalMs && options.sweepIntervalMs > 0) {\n timer = setInterval(() => {\n driver\n .prepare(\n `DELETE FROM _kv WHERE expires_at IS NOT NULL AND expires_at <= ?`,\n )\n .run(now());\n }, options.sweepIntervalMs);\n timer.unref?.();\n }\n\n return api;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Monlite } from \"@monlite/core\";\n\nexport interface KVOptions {\n /** Logical namespace so multiple caches can share one database. Default \"default\". */\n namespace?: string;\n /** If set, a timer periodically purges expired keys (ms). Default: lazy-only. */\n sweepIntervalMs?: number;\n}\n\n/**\n * A synchronous, Redis-like key-value cache backed by SQLite. Values are any\n * JSON-serializable data; TTLs are in milliseconds.\n */\nexport interface KV {\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any, opts?: { ttl?: number }): void;\n /**\n * Atomically set the key only if it isn't already present (Redis `SET NX`).\n * Returns `true` if set, `false` if a live key already existed. The lock\n * primitive for single-instance schedulers, nonces, and once-only work.\n */\n setNX(key: string, value: any, opts?: { ttl?: number }): boolean;\n has(key: string): boolean;\n delete(key: string): boolean;\n /** Atomically add `by` (default 1) to a numeric key; returns the new value. */\n incr(key: string, by?: number): number;\n decr(key: string, by?: number): number;\n mget<T = any>(keys: string[]): (T | undefined)[];\n /** Keys in this namespace (optionally by prefix), excluding expired ones. */\n keys(prefix?: string): string[];\n /** Set/refresh a key's TTL (ms). Returns false if the key is absent. */\n expire(key: string, ttl: number): boolean;\n /** Remaining TTL in ms; `-1` if no expiry, `-2` if absent (Redis convention). */\n ttl(key: string): number;\n /** Delete all keys in this namespace. */\n flush(): void;\n /** Number of live keys in this namespace. */\n size(): number;\n /** Stop the sweep timer (if any). */\n stop(): void;\n}\n\nconst ensured = new WeakSet<object>();\n\n/**\n * Create a cache over a monlite database.\n *\n * ```ts\n * const cache = kv(db);\n * cache.set(\"session:42\", { user: \"ali\" }, { ttl: 60_000 });\n * cache.get(\"session:42\"); // { user: \"ali\" } (synchronous)\n * ```\n */\nexport function kv(db: Monlite, options: KVOptions = {}): KV {\n const ns = options.namespace ?? \"default\";\n const driver = db.driver;\n\n if (!ensured.has(db)) {\n driver.exec(\n `CREATE TABLE IF NOT EXISTS _kv (\n ns TEXT NOT NULL, k TEXT NOT NULL, v TEXT NOT NULL,\n expires_at INTEGER, PRIMARY KEY (ns, k)\n )`,\n );\n ensured.add(db);\n }\n\n const now = () => Date.now();\n const fresh = (row: any): boolean =>\n !!row && !(row.expires_at != null && row.expires_at <= now());\n\n const getRow = (key: string) =>\n driver\n .prepare(`SELECT v, expires_at FROM _kv WHERE ns = ? AND k = ?`)\n .get(ns, key) as { v: string; expires_at: number | null } | undefined;\n\n const del = (key: string): boolean =>\n driver.prepare(`DELETE FROM _kv WHERE ns = ? AND k = ?`).run(ns, key)\n .changes > 0;\n\n const setRaw = (key: string, v: string, expires: number | null) =>\n driver\n .prepare(\n `INSERT INTO _kv (ns, k, v, expires_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(ns, k) DO UPDATE SET v = excluded.v, expires_at = excluded.expires_at`,\n )\n .run(ns, key, v, expires);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const api: KV = {\n get(key) {\n const row = getRow(key);\n if (!fresh(row)) {\n if (row) del(key);\n return undefined;\n }\n return JSON.parse(row!.v);\n },\n set(key, value, opts) {\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n },\n setNX(key, value, opts) {\n // IMMEDIATE: take the write lock up front so two processes racing the same\n // key can't deadlock on lock upgrade — the loser cleanly gets `false`.\n return driver.transaction(() => {\n const row = getRow(key);\n if (fresh(row)) return false; // a live key already exists\n const expires = opts?.ttl != null ? now() + opts.ttl : null;\n setRaw(key, JSON.stringify(value), expires);\n return true;\n }, true);\n },\n has(key) {\n return api.get(key) !== undefined;\n },\n delete(key) {\n return del(key);\n },\n incr(key, by = 1) {\n return driver.transaction(() => { // IMMEDIATE: read-modify-write needs the write lock up front\n const row = getRow(key);\n let n = 0;\n let expires: number | null = null;\n if (fresh(row)) {\n const cur = JSON.parse(row!.v);\n if (typeof cur !== \"number\") {\n throw new Error(`kv.incr: value at \"${key}\" is not a number`);\n }\n n = cur;\n expires = row!.expires_at;\n }\n const next = n + by;\n setRaw(key, JSON.stringify(next), expires);\n return next;\n }, true);\n },\n decr(key, by = 1) {\n return api.incr(key, -by);\n },\n mget(keys) {\n return keys.map((k) => api.get(k));\n },\n keys(prefix) {\n const t = now();\n const rows = (\n prefix !== undefined\n ? driver\n .prepare(\n `SELECT k, expires_at FROM _kv WHERE ns = ? AND k LIKE ? ESCAPE '\\\\'`,\n )\n .all(ns, prefix.replace(/[%_\\\\]/g, \"\\\\$&\") + \"%\")\n : driver.prepare(`SELECT k, expires_at FROM _kv WHERE ns = ?`).all(ns)\n ) as Array<{ k: string; expires_at: number | null }>;\n return rows\n .filter((r) => r.expires_at == null || r.expires_at > t)\n .map((r) => r.k);\n },\n expire(key, ttl) {\n const row = getRow(key);\n if (!fresh(row)) return false;\n driver\n .prepare(`UPDATE _kv SET expires_at = ? WHERE ns = ? AND k = ?`)\n .run(now() + ttl, ns, key);\n return true;\n },\n ttl(key) {\n const row = getRow(key);\n if (!fresh(row)) return -2;\n if (row!.expires_at == null) return -1;\n return row!.expires_at - now();\n },\n flush() {\n driver.prepare(`DELETE FROM _kv WHERE ns = ?`).run(ns);\n },\n size() {\n return (\n driver\n .prepare(\n `SELECT COUNT(*) AS n FROM _kv WHERE ns = ? AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(ns, now()) as { n: number }\n ).n;\n },\n stop() {\n if (timer) clearInterval(timer);\n timer = undefined;\n },\n };\n\n if (options.sweepIntervalMs && options.sweepIntervalMs > 0) {\n timer = setInterval(() => {\n driver\n .prepare(\n `DELETE FROM _kv WHERE expires_at IS NOT NULL AND expires_at <= ?`,\n )\n .run(now());\n }, options.sweepIntervalMs);\n timer.unref?.();\n }\n\n return api;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monlite/kv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Redis-like local key-value cache for @monlite/core: get/set/incr with TTL, on SQLite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"node": ">=18"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@monlite/core": "^2.
|
|
52
|
+
"@monlite/core": "^2.6.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^22.10.0",
|