keyv-dir-store 0.1.0 → 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 +14 -8
- package/dist/index.js +7 -6
- package/index.ts +20 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ import { KeyvDirStore } from "keyv-dir-store";
|
|
|
75
75
|
import { KeyvDirStoreAsJSON } from "keyv-dir-store/KeyvDirStoreAsJSON";
|
|
76
76
|
|
|
77
77
|
const keyv = new Keyv({
|
|
78
|
-
store: new KeyvDirStore(".cache/kv", {
|
|
78
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".json" }),
|
|
79
79
|
...KeyvDirStoreAsJSON,
|
|
80
80
|
});
|
|
81
81
|
```
|
|
@@ -88,7 +88,7 @@ import { KeyvDirStore } from "keyv-dir-store";
|
|
|
88
88
|
import { KeyvDirStoreAsYaml } from "keyv-dir-store/KeyvDirStoreAsYaml";
|
|
89
89
|
|
|
90
90
|
const keyv = new Keyv({
|
|
91
|
-
store: new KeyvDirStore(".cache/kv", {
|
|
91
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".yaml" }),
|
|
92
92
|
...KeyvDirStoreAsYaml,
|
|
93
93
|
});
|
|
94
94
|
```
|
|
@@ -101,7 +101,7 @@ import { KeyvDirStore } from "keyv-dir-store";
|
|
|
101
101
|
import * as d3 from "d3";
|
|
102
102
|
|
|
103
103
|
const keyv = new Keyv({
|
|
104
|
-
store: new KeyvDirStore(".cache/kv", {
|
|
104
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".csv" }),
|
|
105
105
|
serialize: ({ value }) => d3.csvFormat(value),
|
|
106
106
|
deserialize: (str) => ({ value: d3.csvParse(str), expires: undefined }),
|
|
107
107
|
});
|
|
@@ -115,7 +115,7 @@ import { KeyvDirStore } from "keyv-dir-store";
|
|
|
115
115
|
import * as d3 from "d3";
|
|
116
116
|
|
|
117
117
|
const keyv = new Keyv({
|
|
118
|
-
store: new KeyvDirStore(".cache/kv", {
|
|
118
|
+
store: new KeyvDirStore(".cache/kv", { suffix: ".tsv" }),
|
|
119
119
|
serialize: ({ value }) => d3.tsvFormat(value),
|
|
120
120
|
deserialize: (str) => ({ value: d3.tsvParse(str), expires: undefined }),
|
|
121
121
|
});
|
|
@@ -133,9 +133,8 @@ Creates a new KeyvDirStore instance.
|
|
|
133
133
|
- `options` (object, optional):
|
|
134
134
|
- `cache` (Map, optional): Custom cache Map to use
|
|
135
135
|
- `filename` (function, optional): Custom filename generator function
|
|
136
|
-
- `ext` (string, optional): File extension to use (default: `.json`)
|
|
137
136
|
- `prefix` (string, optional): Path prefix prepended to every key (e.g. `'data/'`)
|
|
138
|
-
- `suffix` (string, optional): Path suffix appended to every key (
|
|
137
|
+
- `suffix` (string, optional): Path suffix appended to every key (default: `.json`)
|
|
139
138
|
|
|
140
139
|
#### Example with Custom Options
|
|
141
140
|
|
|
@@ -145,8 +144,8 @@ import { KeyvDirStore } from "keyv-dir-store";
|
|
|
145
144
|
|
|
146
145
|
const keyv = new Keyv({
|
|
147
146
|
store: new KeyvDirStore(".cache/kv", {
|
|
148
|
-
// Custom file extension
|
|
149
|
-
|
|
147
|
+
// Custom file suffix/extension
|
|
148
|
+
suffix: ".data",
|
|
150
149
|
|
|
151
150
|
// Custom filename generator
|
|
152
151
|
filename: (key) => `custom-prefix-${key}`,
|
|
@@ -186,6 +185,13 @@ const store = KeyvNest(
|
|
|
186
185
|
4. TTL information is stored in the file's modification time
|
|
187
186
|
5. An in-memory cache is used to improve performance
|
|
188
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
|
+
|
|
189
195
|
## See Also
|
|
190
196
|
|
|
191
197
|
Other Keyv storage adapters by the same author:
|
package/dist/index.js
CHANGED
|
@@ -344,13 +344,13 @@ class KeyvDirStore {
|
|
|
344
344
|
#cache;
|
|
345
345
|
#ready;
|
|
346
346
|
#filename;
|
|
347
|
-
|
|
347
|
+
opts = {};
|
|
348
|
+
namespace;
|
|
348
349
|
prefix;
|
|
349
350
|
suffix;
|
|
350
351
|
constructor(dir, {
|
|
351
352
|
cache = new Map,
|
|
352
353
|
filename,
|
|
353
|
-
ext,
|
|
354
354
|
prefix,
|
|
355
355
|
suffix
|
|
356
356
|
} = {}) {
|
|
@@ -358,9 +358,8 @@ class KeyvDirStore {
|
|
|
358
358
|
this.#cache = cache;
|
|
359
359
|
this.#dir = dir;
|
|
360
360
|
this.#filename = filename ?? this.#defaultFilename;
|
|
361
|
-
this.ext = ext ?? this.ext;
|
|
362
361
|
this.prefix = prefix ?? "";
|
|
363
|
-
this.suffix = suffix ?? "";
|
|
362
|
+
this.suffix = suffix ?? ".json";
|
|
364
363
|
}
|
|
365
364
|
#defaultFilename(key) {
|
|
366
365
|
const readableName = import_sanitize_filename.default(key).slice(0, 16);
|
|
@@ -370,8 +369,7 @@ class KeyvDirStore {
|
|
|
370
369
|
}
|
|
371
370
|
#path(key) {
|
|
372
371
|
const filename = this.#filename(key);
|
|
373
|
-
const
|
|
374
|
-
const safeFilename = import_sanitize_filename.default(filename + ext);
|
|
372
|
+
const safeFilename = import_sanitize_filename.default(filename + this.suffix);
|
|
375
373
|
const relativePath = this.prefix + safeFilename;
|
|
376
374
|
return path.join(this.#dir, relativePath);
|
|
377
375
|
}
|
|
@@ -431,6 +429,9 @@ class KeyvDirStore {
|
|
|
431
429
|
static deserialize(str) {
|
|
432
430
|
return { value: JSON.parse(str), expires: undefined };
|
|
433
431
|
}
|
|
432
|
+
on(_event, _listener) {
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
434
435
|
}
|
|
435
436
|
export {
|
|
436
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,6 +8,10 @@ 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
17
|
* @example Basic usage
|
|
@@ -32,15 +36,16 @@ type CacheMap<Value> = Map<string, DeserializedData<Value>>;
|
|
|
32
36
|
* // key "foo" -> ./cache/data/foo.json (local) and data/foo.json (GitHub)
|
|
33
37
|
*
|
|
34
38
|
*/
|
|
35
|
-
export class KeyvDirStore<Value extends string> implements
|
|
39
|
+
export class KeyvDirStore<Value extends string> implements KeyvStoreAdapter {
|
|
36
40
|
#dir: string;
|
|
37
41
|
#cache: CacheMap<Value>;
|
|
38
42
|
#ready: Promise<unknown>;
|
|
39
43
|
#filename: (key: string) => string;
|
|
40
|
-
|
|
44
|
+
opts: Record<string, unknown> = {};
|
|
45
|
+
namespace?: string;
|
|
41
46
|
/** Path prefix prepended to every key (e.g. 'data/'). Defaults to ''. */
|
|
42
47
|
readonly prefix: string;
|
|
43
|
-
/** Path suffix appended to every key (e.g. '.json'). Defaults to ''.
|
|
48
|
+
/** Path suffix appended to every key (e.g. '.json'). Defaults to '.json'. */
|
|
44
49
|
readonly suffix: string;
|
|
45
50
|
constructor(
|
|
46
51
|
/** dir to cache store
|
|
@@ -51,16 +56,14 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
51
56
|
{
|
|
52
57
|
cache = new Map(),
|
|
53
58
|
filename,
|
|
54
|
-
ext,
|
|
55
59
|
prefix,
|
|
56
60
|
suffix,
|
|
57
61
|
}: {
|
|
58
62
|
cache?: CacheMap<Value>;
|
|
59
63
|
filename?: (key: string) => string;
|
|
60
|
-
ext?: string;
|
|
61
64
|
/** Path prefix prepended to every key (e.g. 'data/'). Use with filename: (k) => k for raw paths. */
|
|
62
65
|
prefix?: string;
|
|
63
|
-
/** Path suffix appended to every key (e.g. '.json').
|
|
66
|
+
/** Path suffix appended to every key (e.g. '.json'). Defaults to '.json'. Use with filename: (k) => k for raw paths. */
|
|
64
67
|
suffix?: string;
|
|
65
68
|
} = {},
|
|
66
69
|
) {
|
|
@@ -68,9 +71,8 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
68
71
|
this.#cache = cache;
|
|
69
72
|
this.#dir = dir;
|
|
70
73
|
this.#filename = filename ?? this.#defaultFilename;
|
|
71
|
-
this.ext = ext ?? this.ext;
|
|
72
74
|
this.prefix = prefix ?? "";
|
|
73
|
-
this.suffix = suffix ?? "";
|
|
75
|
+
this.suffix = suffix ?? ".json";
|
|
74
76
|
}
|
|
75
77
|
#defaultFilename(key: string) {
|
|
76
78
|
// use dir as hash salt to avoid collisions
|
|
@@ -81,13 +83,12 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
81
83
|
}
|
|
82
84
|
#path(key: string) {
|
|
83
85
|
const filename = this.#filename(key);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const safeFilename = sanitizeFilename(filename + ext);
|
|
86
|
+
// Sanitize filename+suffix for safety; prefix can have slashes for nested paths
|
|
87
|
+
const safeFilename = sanitizeFilename(filename + this.suffix);
|
|
87
88
|
const relativePath = this.prefix + safeFilename;
|
|
88
89
|
return path.join(this.#dir, relativePath);
|
|
89
90
|
}
|
|
90
|
-
async get(key: string) {
|
|
91
|
+
async get<T = Value>(key: string): Promise<T | undefined> {
|
|
91
92
|
// read memory
|
|
92
93
|
const memCached = this.#cache.get(key);
|
|
93
94
|
if (memCached) {
|
|
@@ -95,7 +96,7 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
95
96
|
if (memCached.expires && memCached.expires < Date.now()) {
|
|
96
97
|
await this.delete(key);
|
|
97
98
|
} else {
|
|
98
|
-
return memCached.value;
|
|
99
|
+
return memCached.value as T;
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
// read file cache
|
|
@@ -111,7 +112,7 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
111
112
|
return undefined;
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
|
-
return await readFile(path, "utf8").catch(() => undefined);
|
|
115
|
+
return await readFile(path, "utf8").catch(() => undefined) as T | undefined;
|
|
115
116
|
}
|
|
116
117
|
async set(key: string, value: Value, ttl?: number) {
|
|
117
118
|
if (!value) return await this.delete(key);
|
|
@@ -149,4 +150,8 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
|
|
|
149
150
|
static deserialize(str: string): DeserializedData<any> {
|
|
150
151
|
return { value: JSON.parse(str), expires: undefined };
|
|
151
152
|
}
|
|
153
|
+
// IEventEmitter implementation (required by KeyvStoreAdapter)
|
|
154
|
+
on(_event: string, _listener: (...args: any[]) => void): this {
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
152
157
|
}
|
package/package.json
CHANGED