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 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", { ext: ".json" }),
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", { ext: ".yaml" }),
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", { ext: ".csv" }),
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", { ext: ".tsv" }),
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 (overrides `ext` when set)
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
- ext: ".data",
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
- ext = ".json";
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 ext = this.suffix || this.ext;
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, default as Keyv } from "keyv";
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 Keyv.Store<string> {
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
- ext = ".json";
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 ''. Overrides ext when set. */
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'). Overrides ext when set. Use with filename: (k) => k for raw paths. */
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
- const ext = this.suffix || this.ext; // suffix overrides ext when set
85
- // Sanitize filename+ext for safety; prefix can have slashes for nested paths
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keyv-dir-store",
3
- "version": "0.1.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",