keyv-dir-store 0.0.9 → 1.0.0
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 +199 -23
- package/dist/index.js +35 -11
- package/index.ts +49 -19
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,41 +1,217 @@
|
|
|
1
1
|
# Keyv Directory Store
|
|
2
2
|
|
|
3
|
-
High performance Filesystem Keyv Store, caches each key-value pair into a $key.json. and more!
|
|
3
|
+
High performance Filesystem Keyv Store, caches each key-value pair into a $key.json. and more! _.JSON, _.YAML, \*.CSV is also avaliable.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/keyv-dir-store)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
This package provides a filesystem-based storage adapter for [Keyv](https://github.com/jaredwray/keyv), storing each key-value pair in individual files with support for various formats.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- ✅ Persistent storage using the filesystem
|
|
13
|
+
- ✅ Individual files per key-value pair
|
|
14
|
+
- ✅ Support customize serialize/deserialize, you can store your data into JSON, YAML, CSV, and TSV formats
|
|
15
|
+
- ✅ TTL (Time-To-Live) support using file modification times
|
|
16
|
+
- ✅ In-memory caching for improved performance
|
|
17
|
+
- ✅ Full compatibility with Keyv API
|
|
18
|
+
- ✅ Customizable file naming and extensions
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Using npm
|
|
24
|
+
npm install keyv keyv-dir-store
|
|
25
|
+
|
|
26
|
+
# Using yarn
|
|
27
|
+
yarn add keyv keyv-dir-store
|
|
28
|
+
|
|
29
|
+
# Using pnpm
|
|
30
|
+
pnpm add keyv keyv-dir-store
|
|
31
|
+
|
|
32
|
+
# Using bun
|
|
33
|
+
bun add keyv keyv-dir-store
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage Examples
|
|
37
|
+
|
|
38
|
+
### Basic Usage
|
|
6
39
|
|
|
7
40
|
```ts
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
41
|
+
import Keyv from "keyv";
|
|
42
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
43
|
+
|
|
44
|
+
// Default: Store each value with JSON
|
|
45
|
+
const keyv = new Keyv({
|
|
46
|
+
store: new KeyvDirStore(".cache/kv"),
|
|
11
47
|
});
|
|
12
48
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
49
|
+
// Set a value (never expires)
|
|
50
|
+
await keyv.set("key1", "value1");
|
|
51
|
+
|
|
52
|
+
// Set a value with TTL (expires after 1 day)
|
|
53
|
+
await keyv.set("key2", "value2", 86400000);
|
|
54
|
+
|
|
55
|
+
// Get a value
|
|
56
|
+
const value = await keyv.get("key1");
|
|
57
|
+
|
|
58
|
+
// Check if a key exists
|
|
59
|
+
const exists = await keyv.has("key1");
|
|
60
|
+
|
|
61
|
+
// Delete a key
|
|
62
|
+
await keyv.delete("key1");
|
|
63
|
+
|
|
64
|
+
// Clear all keys
|
|
65
|
+
await keyv.clear();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Format-Specific Examples
|
|
69
|
+
|
|
70
|
+
#### Store with JSON (using provided helper)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import Keyv from "keyv";
|
|
74
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
75
|
+
import { KeyvDirStoreAsJSON } from "keyv-dir-store/KeyvDirStoreAsJSON";
|
|
76
|
+
|
|
77
|
+
const keyv = new Keyv({
|
|
78
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".json" }),
|
|
79
|
+
...KeyvDirStoreAsJSON,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Store with YAML (using provided helper)
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import Keyv from "keyv";
|
|
87
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
88
|
+
import { KeyvDirStoreAsYaml } from "keyv-dir-store/KeyvDirStoreAsYaml";
|
|
89
|
+
|
|
90
|
+
const keyv = new Keyv({
|
|
91
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".yaml" }),
|
|
92
|
+
...KeyvDirStoreAsYaml,
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Store Object Lists with CSV
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import Keyv from "keyv";
|
|
100
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
101
|
+
import * as d3 from "d3";
|
|
102
|
+
|
|
103
|
+
const keyv = new Keyv({
|
|
104
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".csv" }),
|
|
16
105
|
serialize: ({ value }) => d3.csvFormat(value),
|
|
17
106
|
deserialize: (str) => ({ value: d3.csvParse(str), expires: undefined }),
|
|
18
107
|
});
|
|
108
|
+
```
|
|
19
109
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
110
|
+
#### Store Object Lists with TSV
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import Keyv from "keyv";
|
|
114
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
115
|
+
import * as d3 from "d3";
|
|
116
|
+
|
|
117
|
+
const keyv = new Keyv({
|
|
118
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".tsv" }),
|
|
23
119
|
serialize: ({ value }) => d3.tsvFormat(value),
|
|
24
120
|
deserialize: (str) => ({ value: d3.tsvParse(str), expires: undefined }),
|
|
25
121
|
});
|
|
122
|
+
```
|
|
26
123
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
124
|
+
## API Reference
|
|
125
|
+
|
|
126
|
+
### `new KeyvDirStore(directory, options?)`
|
|
127
|
+
|
|
128
|
+
Creates a new KeyvDirStore instance.
|
|
129
|
+
|
|
130
|
+
#### Parameters
|
|
131
|
+
|
|
132
|
+
- `directory` (string): The directory path where files will be stored
|
|
133
|
+
- `options` (object, optional):
|
|
134
|
+
- `cache` (Map, optional): Custom cache Map to use
|
|
135
|
+
- `filename` (function, optional): Custom filename generator function
|
|
136
|
+
- `prefix` (string, optional): Path prefix prepended to every key (e.g. `'data/'`)
|
|
137
|
+
- `suffix` (string, optional): Path suffix appended to every key (default: `.json`)
|
|
33
138
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
139
|
+
#### Example with Custom Options
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import Keyv from "keyv";
|
|
143
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
144
|
+
|
|
145
|
+
const keyv = new Keyv({
|
|
146
|
+
store: new KeyvDirStore(".cache/kv", {
|
|
147
|
+
// Custom file suffix/extension
|
|
148
|
+
suffix: ".data",
|
|
149
|
+
|
|
150
|
+
// Custom filename generator
|
|
151
|
+
filename: (key) => `custom-prefix-${key}`,
|
|
152
|
+
}),
|
|
39
153
|
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Mirror KeyvGithub paths (for use with keyv-nest)
|
|
157
|
+
|
|
158
|
+
Use the same prefix/suffix as KeyvGithub with `filename: (k) => k` for raw paths:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import KeyvNest from "keyv-nest";
|
|
162
|
+
import { KeyvDirStore } from "keyv-dir-store";
|
|
163
|
+
import KeyvGithub from "keyv-github";
|
|
164
|
+
|
|
165
|
+
const prefix = "data/";
|
|
166
|
+
const suffix = ".json";
|
|
167
|
+
|
|
168
|
+
const store = KeyvNest(
|
|
169
|
+
new KeyvDirStore("./cache", {
|
|
170
|
+
prefix,
|
|
171
|
+
suffix,
|
|
172
|
+
filename: (k) => k, // use key as-is, no hashing
|
|
173
|
+
}),
|
|
174
|
+
new KeyvGithub("owner/repo", { client, prefix, suffix })
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// key "foo" -> ./cache/data/foo.json (local) and data/foo.json (GitHub)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## How It Works
|
|
181
|
+
|
|
182
|
+
1. Each key-value pair is stored in a separate file on disk
|
|
183
|
+
2. The key is used to generate a filename (with sanitization)
|
|
184
|
+
3. The value is serialized using the specified format
|
|
185
|
+
4. TTL information is stored in the file's modification time
|
|
186
|
+
5. An in-memory cache is used to improve performance
|
|
187
|
+
|
|
188
|
+
> **Warning**: TTL is stored using file mtime, which may be modified by other programs (backup tools, sync services, file explorers, etc.). For reliable TTL behavior, use the standard Keyv wrapper which stores expiry in the serialized value itself:
|
|
189
|
+
> ```ts
|
|
190
|
+
> // Keyv handles TTL internally - safe from mtime changes
|
|
191
|
+
> const keyv = new Keyv({ store: new KeyvDirStore("./cache") });
|
|
192
|
+
> await keyv.set("key", "value", 60000); // TTL stored in value, not mtime
|
|
193
|
+
> ```
|
|
194
|
+
|
|
195
|
+
## See Also
|
|
196
|
+
|
|
197
|
+
Other Keyv storage adapters by the same author:
|
|
198
|
+
|
|
199
|
+
- [keyv-github](https://github.com/snomiao/keyv-github) — GitHub repository adapter; each key is a file, commits are writes
|
|
200
|
+
- [keyv-sqlite](https://github.com/snomiao/keyv-sqlite) — SQLite storage adapter
|
|
201
|
+
- [keyv-mongodb-store](https://github.com/snomiao/keyv-mongodb-store) — MongoDB storage adapter
|
|
202
|
+
- [keyv-nedb-store](https://github.com/snomiao/keyv-nedb-store) — NeDB embedded file-based adapter
|
|
203
|
+
- [keyv-cache-proxy](https://github.com/snomiao/keyv-cache-proxy) — transparent caching proxy that wraps any object
|
|
204
|
+
- [keyv-nest](https://github.com/snomiao/keyv-nest) — hierarchical multi-layer caching adapter
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT © [snomiao](https://github.com/snomiao)
|
|
209
|
+
|
|
210
|
+
## Contributing
|
|
211
|
+
|
|
212
|
+
Contributions, issues, and feature requests are welcome!
|
|
213
|
+
Feel free to check [issues page](https://github.com/snomiao/keyv-dir-store/issues).
|
|
214
|
+
|
|
215
|
+
## Acknowledgements
|
|
40
216
|
|
|
41
|
-
|
|
217
|
+
This package is built on top of the excellent [Keyv](https://github.com/jaredwray/keyv) library.
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,29 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -330,18 +344,22 @@ class KeyvDirStore {
|
|
|
330
344
|
#cache;
|
|
331
345
|
#ready;
|
|
332
346
|
#filename;
|
|
333
|
-
|
|
347
|
+
opts = {};
|
|
348
|
+
namespace;
|
|
349
|
+
prefix;
|
|
350
|
+
suffix;
|
|
334
351
|
constructor(dir, {
|
|
335
352
|
cache = new Map,
|
|
336
353
|
filename,
|
|
337
|
-
|
|
354
|
+
prefix,
|
|
355
|
+
suffix
|
|
338
356
|
} = {}) {
|
|
339
|
-
this.#ready = mkdir(dir, { recursive: true }).catch(() => {
|
|
340
|
-
});
|
|
357
|
+
this.#ready = mkdir(dir, { recursive: true }).catch(() => {});
|
|
341
358
|
this.#cache = cache;
|
|
342
359
|
this.#dir = dir;
|
|
343
360
|
this.#filename = filename ?? this.#defaultFilename;
|
|
344
|
-
this.
|
|
361
|
+
this.prefix = prefix ?? "";
|
|
362
|
+
this.suffix = suffix ?? ".json";
|
|
345
363
|
}
|
|
346
364
|
#defaultFilename(key) {
|
|
347
365
|
const readableName = import_sanitize_filename.default(key).slice(0, 16);
|
|
@@ -350,7 +368,10 @@ class KeyvDirStore {
|
|
|
350
368
|
return name;
|
|
351
369
|
}
|
|
352
370
|
#path(key) {
|
|
353
|
-
|
|
371
|
+
const filename = this.#filename(key);
|
|
372
|
+
const safeFilename = import_sanitize_filename.default(filename + this.suffix);
|
|
373
|
+
const relativePath = this.prefix + safeFilename;
|
|
374
|
+
return path.join(this.#dir, relativePath);
|
|
354
375
|
}
|
|
355
376
|
async get(key) {
|
|
356
377
|
const memCached = this.#cache.get(key);
|
|
@@ -383,10 +404,10 @@ class KeyvDirStore {
|
|
|
383
404
|
const expires = ttl ? Date.now() + ttl : 0;
|
|
384
405
|
this.#cache.set(key, { value, expires });
|
|
385
406
|
await this.#ready;
|
|
386
|
-
|
|
387
|
-
});
|
|
388
|
-
await writeFile(
|
|
389
|
-
await utimes(
|
|
407
|
+
const filePath = this.#path(key);
|
|
408
|
+
await mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
|
|
409
|
+
await writeFile(filePath, value);
|
|
410
|
+
await utimes(filePath, new Date, new Date(expires ?? 0));
|
|
390
411
|
return true;
|
|
391
412
|
}
|
|
392
413
|
async delete(key) {
|
|
@@ -408,6 +429,9 @@ class KeyvDirStore {
|
|
|
408
429
|
static deserialize(str) {
|
|
409
430
|
return { value: JSON.parse(str), expires: undefined };
|
|
410
431
|
}
|
|
432
|
+
on(_event, _listener) {
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
411
435
|
}
|
|
412
436
|
export {
|
|
413
437
|
KeyvDirStore
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DeserializedData,
|
|
1
|
+
import type { DeserializedData, KeyvStoreAdapter } from "keyv";
|
|
2
2
|
import md5 from "md5";
|
|
3
3
|
import { mkdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "path";
|
|
@@ -8,9 +8,13 @@ type CacheMap<Value> = Map<string, DeserializedData<Value>>;
|
|
|
8
8
|
/**
|
|
9
9
|
* KeyvDirStore is a Keyv.Store<string> implementation that stores data in files.
|
|
10
10
|
*
|
|
11
|
+
* **Warning**: TTL is stored in file mtime, which may be modified by other programs
|
|
12
|
+
* (backup tools, sync services, etc.). For reliable TTL, wrap this store with Keyv:
|
|
13
|
+
* `new Keyv({ store: new KeyvDirStore(...) })` - Keyv stores expiry in the value itself.
|
|
14
|
+
*
|
|
11
15
|
* learn more [README](./README.md)
|
|
12
16
|
*
|
|
13
|
-
* @example
|
|
17
|
+
* @example Basic usage
|
|
14
18
|
* const kv = new Keyv<number | string | { obj: boolean }>({
|
|
15
19
|
* store: new KeyvDirStore("cache/test"),
|
|
16
20
|
* deserialize: KeyvDirStore.deserialize,
|
|
@@ -21,13 +25,28 @@ type CacheMap<Value> = Map<string, DeserializedData<Value>>;
|
|
|
21
25
|
* await kv.set("b", 1234); // never expired
|
|
22
26
|
* expect(await kv.get("b")).toEqual(1234);
|
|
23
27
|
*
|
|
28
|
+
* @example Mirror KeyvGithub paths (for use with keyv-nest)
|
|
29
|
+
* // Use same prefix/suffix as KeyvGithub, with filename: (k) => k for raw paths
|
|
30
|
+
* const dirStore = new KeyvDirStore("./cache", {
|
|
31
|
+
* prefix: "data/",
|
|
32
|
+
* suffix: ".json",
|
|
33
|
+
* filename: (k) => k, // use key as-is, no hashing
|
|
34
|
+
* });
|
|
35
|
+
* // Now dirStore uses same paths as KeyvGithub:
|
|
36
|
+
* // key "foo" -> ./cache/data/foo.json (local) and data/foo.json (GitHub)
|
|
37
|
+
*
|
|
24
38
|
*/
|
|
25
|
-
export class KeyvDirStore<Value extends string> implements
|
|
39
|
+
export class KeyvDirStore<Value extends string> implements KeyvStoreAdapter {
|
|
26
40
|
#dir: string;
|
|
27
41
|
#cache: CacheMap<Value>;
|
|
28
42
|
#ready: Promise<unknown>;
|
|
29
43
|
#filename: (key: string) => string;
|
|
30
|
-
|
|
44
|
+
opts: Record<string, unknown> = {};
|
|
45
|
+
namespace?: string;
|
|
46
|
+
/** Path prefix prepended to every key (e.g. 'data/'). Defaults to ''. */
|
|
47
|
+
readonly prefix: string;
|
|
48
|
+
/** Path suffix appended to every key (e.g. '.json'). Defaults to '.json'. */
|
|
49
|
+
readonly suffix: string;
|
|
31
50
|
constructor(
|
|
32
51
|
/** dir to cache store
|
|
33
52
|
* WARN: dont share this dir with other purpose
|
|
@@ -37,18 +56,23 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
37
56
|
{
|
|
38
57
|
cache = new Map(),
|
|
39
58
|
filename,
|
|
40
|
-
|
|
59
|
+
prefix,
|
|
60
|
+
suffix,
|
|
41
61
|
}: {
|
|
42
62
|
cache?: CacheMap<Value>;
|
|
43
63
|
filename?: (key: string) => string;
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
/** Path prefix prepended to every key (e.g. 'data/'). Use with filename: (k) => k for raw paths. */
|
|
65
|
+
prefix?: string;
|
|
66
|
+
/** Path suffix appended to every key (e.g. '.json'). Defaults to '.json'. Use with filename: (k) => k for raw paths. */
|
|
67
|
+
suffix?: string;
|
|
68
|
+
} = {},
|
|
46
69
|
) {
|
|
47
70
|
this.#ready = mkdir(dir, { recursive: true }).catch(() => {});
|
|
48
71
|
this.#cache = cache;
|
|
49
72
|
this.#dir = dir;
|
|
50
73
|
this.#filename = filename ?? this.#defaultFilename;
|
|
51
|
-
this.
|
|
74
|
+
this.prefix = prefix ?? "";
|
|
75
|
+
this.suffix = suffix ?? ".json";
|
|
52
76
|
}
|
|
53
77
|
#defaultFilename(key: string) {
|
|
54
78
|
// use dir as hash salt to avoid collisions
|
|
@@ -58,12 +82,13 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
58
82
|
return name;
|
|
59
83
|
}
|
|
60
84
|
#path(key: string) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
const filename = this.#filename(key);
|
|
86
|
+
// Sanitize filename+suffix for safety; prefix can have slashes for nested paths
|
|
87
|
+
const safeFilename = sanitizeFilename(filename + this.suffix);
|
|
88
|
+
const relativePath = this.prefix + safeFilename;
|
|
89
|
+
return path.join(this.#dir, relativePath);
|
|
65
90
|
}
|
|
66
|
-
async get(key: string) {
|
|
91
|
+
async get<T = Value>(key: string): Promise<T | undefined> {
|
|
67
92
|
// read memory
|
|
68
93
|
const memCached = this.#cache.get(key);
|
|
69
94
|
if (memCached) {
|
|
@@ -71,7 +96,7 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
71
96
|
if (memCached.expires && memCached.expires < Date.now()) {
|
|
72
97
|
await this.delete(key);
|
|
73
98
|
} else {
|
|
74
|
-
return memCached.value;
|
|
99
|
+
return memCached.value as T;
|
|
75
100
|
}
|
|
76
101
|
}
|
|
77
102
|
// read file cache
|
|
@@ -87,7 +112,7 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
87
112
|
return undefined;
|
|
88
113
|
}
|
|
89
114
|
}
|
|
90
|
-
return await readFile(path, "utf8").catch(() => undefined);
|
|
115
|
+
return await readFile(path, "utf8").catch(() => undefined) as T | undefined;
|
|
91
116
|
}
|
|
92
117
|
async set(key: string, value: Value, ttl?: number) {
|
|
93
118
|
if (!value) return await this.delete(key);
|
|
@@ -97,10 +122,11 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
97
122
|
this.#cache.set(key, { value, expires });
|
|
98
123
|
// save to file
|
|
99
124
|
await this.#ready;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await
|
|
103
|
-
await
|
|
125
|
+
const filePath = this.#path(key);
|
|
126
|
+
// create parent directories for nested paths (e.g. data/sub/key.json)
|
|
127
|
+
await mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
|
|
128
|
+
await writeFile(filePath, value); // create a expired file
|
|
129
|
+
await utimes(filePath, new Date(), new Date(expires ?? 0)); // set expires time as mtime (0 as never expired)
|
|
104
130
|
return true;
|
|
105
131
|
}
|
|
106
132
|
async delete(key: string) {
|
|
@@ -124,4 +150,8 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
124
150
|
static deserialize(str: string): DeserializedData<any> {
|
|
125
151
|
return { value: JSON.parse(str), expires: undefined };
|
|
126
152
|
}
|
|
153
|
+
// IEventEmitter implementation (required by KeyvStoreAdapter)
|
|
154
|
+
on(_event: string, _listener: (...args: any[]) => void): this {
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
127
157
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keyv-dir-store",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "High performance Filesystem Keyv Store, caches each key-value pair into a $key.json. and more! *.JSON, *.YAML, *.CSV is also avaliable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"keyv",
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
"test": "bun test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@types/node": "^
|
|
40
|
+
"@types/node": "^25.3.3",
|
|
41
41
|
"md5": "^2.3.0",
|
|
42
42
|
"sanitize-filename": "^1.6.3",
|
|
43
|
-
"yaml": "^2.
|
|
43
|
+
"yaml": "^2.8.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@types/bun": "^1.
|
|
47
|
-
"@types/jest": "^
|
|
48
|
-
"@types/md5": "^2.3.
|
|
46
|
+
"@types/bun": "^1.3.9",
|
|
47
|
+
"@types/jest": "^30.0.0",
|
|
48
|
+
"@types/md5": "^2.3.6",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
|
-
"semantic-release": "^
|
|
51
|
-
"typescript": "^5.
|
|
50
|
+
"semantic-release": "^25.0.3",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"keyv": "^
|
|
54
|
+
"keyv": "^5.6.0"
|
|
55
55
|
}
|
|
56
56
|
}
|