keyv-dir-store 0.0.8 → 0.1.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.
Files changed (4) hide show
  1. package/README.md +191 -21
  2. package/dist/index.js +32 -9
  3. package/index.ts +35 -12
  4. package/package.json +27 -11
package/README.md CHANGED
@@ -1,41 +1,211 @@
1
1
  # Keyv Directory Store
2
2
 
3
- High performance Filesystem Keyv Store, caches each key-value pair into a $key.json. and more! *.JSON, *.YAML, *.CSV is also avaliable.
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
- ## Usages
5
+ [![npm version](https://badge.fury.io/js/keyv-dir-store.svg)](https://www.npmjs.com/package/keyv-dir-store)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
39
+
40
+ ```ts
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"),
47
+ });
48
+
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", { ext: ".json" }),
79
+ ...KeyvDirStoreAsJSON,
80
+ });
81
+ ```
82
+
83
+ #### Store with YAML (using provided helper)
6
84
 
7
85
  ```ts
8
- // Default: Store each value with JSON in format "{value: ..., expires: ...}"
9
- new Keyv({
10
- store: new KeyvDirStore(".cache/kv")
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", { ext: ".yaml" }),
92
+ ...KeyvDirStoreAsYaml,
11
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";
12
102
 
13
- // Store each object list with CSV
14
- new Keyv({
103
+ const keyv = new Keyv({
15
104
  store: new KeyvDirStore(".cache/kv", { ext: ".csv" }),
16
105
  serialize: ({ value }) => d3.csvFormat(value),
17
106
  deserialize: (str) => ({ value: d3.csvParse(str), expires: undefined }),
18
107
  });
108
+ ```
19
109
 
20
- // Store each object list with TSV
21
- new Keyv({
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({
22
118
  store: new KeyvDirStore(".cache/kv", { ext: ".tsv" }),
23
119
  serialize: ({ value }) => d3.tsvFormat(value),
24
120
  deserialize: (str) => ({ value: d3.tsvParse(str), expires: undefined }),
25
121
  });
122
+ ```
26
123
 
27
- // Store each value with YAML
28
- new Keyv({
29
- store: new KeyvDirStore(".cache/kv", { ext: ".json" }),
30
- serialize: ({ value }) => yaml.stringify(value),
31
- deserialize: (str) => ({ value: yaml.parse(str), expires: undefined }),
32
- });
124
+ ## API Reference
33
125
 
34
- // Store each value with JSON
35
- new Keyv({
36
- store: new KeyvDirStore(".cache/kv", { ext: ".json" }),
37
- serialize: ({ value }) => JSON.stringify(value, null, 2),
38
- deserialize: (str) => ({ value: JSON.parse(str), expires: undefined }),
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
+ - `ext` (string, optional): File extension to use (default: `.json`)
137
+ - `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)
139
+
140
+ #### Example with Custom Options
141
+
142
+ ```ts
143
+ import Keyv from "keyv";
144
+ import { KeyvDirStore } from "keyv-dir-store";
145
+
146
+ const keyv = new Keyv({
147
+ store: new KeyvDirStore(".cache/kv", {
148
+ // Custom file extension
149
+ ext: ".data",
150
+
151
+ // Custom filename generator
152
+ filename: (key) => `custom-prefix-${key}`,
153
+ }),
39
154
  });
155
+ ```
156
+
157
+ #### Mirror KeyvGithub paths (for use with keyv-nest)
158
+
159
+ Use the same prefix/suffix as KeyvGithub with `filename: (k) => k` for raw paths:
160
+
161
+ ```ts
162
+ import KeyvNest from "keyv-nest";
163
+ import { KeyvDirStore } from "keyv-dir-store";
164
+ import KeyvGithub from "keyv-github";
165
+
166
+ const prefix = "data/";
167
+ const suffix = ".json";
168
+
169
+ const store = KeyvNest(
170
+ new KeyvDirStore("./cache", {
171
+ prefix,
172
+ suffix,
173
+ filename: (k) => k, // use key as-is, no hashing
174
+ }),
175
+ new KeyvGithub("owner/repo", { client, prefix, suffix })
176
+ );
177
+
178
+ // key "foo" -> ./cache/data/foo.json (local) and data/foo.json (GitHub)
179
+ ```
180
+
181
+ ## How It Works
182
+
183
+ 1. Each key-value pair is stored in a separate file on disk
184
+ 2. The key is used to generate a filename (with sanitization)
185
+ 3. The value is serialized using the specified format
186
+ 4. TTL information is stored in the file's modification time
187
+ 5. An in-memory cache is used to improve performance
188
+
189
+ ## See Also
190
+
191
+ Other Keyv storage adapters by the same author:
192
+
193
+ - [keyv-github](https://github.com/snomiao/keyv-github) — GitHub repository adapter; each key is a file, commits are writes
194
+ - [keyv-sqlite](https://github.com/snomiao/keyv-sqlite) — SQLite storage adapter
195
+ - [keyv-mongodb-store](https://github.com/snomiao/keyv-mongodb-store) — MongoDB storage adapter
196
+ - [keyv-nedb-store](https://github.com/snomiao/keyv-nedb-store) — NeDB embedded file-based adapter
197
+ - [keyv-cache-proxy](https://github.com/snomiao/keyv-cache-proxy) — transparent caching proxy that wraps any object
198
+ - [keyv-nest](https://github.com/snomiao/keyv-nest) — hierarchical multi-layer caching adapter
199
+
200
+ ## License
201
+
202
+ MIT © [snomiao](https://github.com/snomiao)
203
+
204
+ ## Contributing
205
+
206
+ Contributions, issues, and feature requests are welcome!
207
+ Feel free to check [issues page](https://github.com/snomiao/keyv-dir-store/issues).
208
+
209
+ ## Acknowledgements
40
210
 
41
- ```
211
+ 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: () => mod[key],
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);
@@ -331,17 +345,22 @@ class KeyvDirStore {
331
345
  #ready;
332
346
  #filename;
333
347
  ext = ".json";
348
+ prefix;
349
+ suffix;
334
350
  constructor(dir, {
335
351
  cache = new Map,
336
352
  filename,
337
- ext
353
+ ext,
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
361
  this.ext = ext ?? this.ext;
362
+ this.prefix = prefix ?? "";
363
+ this.suffix = suffix ?? "";
345
364
  }
346
365
  #defaultFilename(key) {
347
366
  const readableName = import_sanitize_filename.default(key).slice(0, 16);
@@ -350,7 +369,11 @@ class KeyvDirStore {
350
369
  return name;
351
370
  }
352
371
  #path(key) {
353
- return path.join(this.#dir, import_sanitize_filename.default(this.#filename(key) + this.ext));
372
+ const filename = this.#filename(key);
373
+ const ext = this.suffix || this.ext;
374
+ const safeFilename = import_sanitize_filename.default(filename + ext);
375
+ const relativePath = this.prefix + safeFilename;
376
+ return path.join(this.#dir, relativePath);
354
377
  }
355
378
  async get(key) {
356
379
  const memCached = this.#cache.get(key);
@@ -383,10 +406,10 @@ class KeyvDirStore {
383
406
  const expires = ttl ? Date.now() + ttl : 0;
384
407
  this.#cache.set(key, { value, expires });
385
408
  await this.#ready;
386
- await mkdir(this.#dir, { recursive: true }).catch(() => {
387
- });
388
- await writeFile(this.#path(key), value);
389
- await utimes(this.#path(key), new Date, new Date(expires ?? 0));
409
+ const filePath = this.#path(key);
410
+ await mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
411
+ await writeFile(filePath, value);
412
+ await utimes(filePath, new Date, new Date(expires ?? 0));
390
413
  return true;
391
414
  }
392
415
  async delete(key) {
package/index.ts CHANGED
@@ -10,7 +10,7 @@ type CacheMap<Value> = Map<string, DeserializedData<Value>>;
10
10
  *
11
11
  * learn more [README](./README.md)
12
12
  *
13
- * @example
13
+ * @example Basic usage
14
14
  * const kv = new Keyv<number | string | { obj: boolean }>({
15
15
  * store: new KeyvDirStore("cache/test"),
16
16
  * deserialize: KeyvDirStore.deserialize,
@@ -21,6 +21,16 @@ type CacheMap<Value> = Map<string, DeserializedData<Value>>;
21
21
  * await kv.set("b", 1234); // never expired
22
22
  * expect(await kv.get("b")).toEqual(1234);
23
23
  *
24
+ * @example Mirror KeyvGithub paths (for use with keyv-nest)
25
+ * // Use same prefix/suffix as KeyvGithub, with filename: (k) => k for raw paths
26
+ * const dirStore = new KeyvDirStore("./cache", {
27
+ * prefix: "data/",
28
+ * suffix: ".json",
29
+ * filename: (k) => k, // use key as-is, no hashing
30
+ * });
31
+ * // Now dirStore uses same paths as KeyvGithub:
32
+ * // key "foo" -> ./cache/data/foo.json (local) and data/foo.json (GitHub)
33
+ *
24
34
  */
25
35
  export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
26
36
  #dir: string;
@@ -28,6 +38,10 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
28
38
  #ready: Promise<unknown>;
29
39
  #filename: (key: string) => string;
30
40
  ext = ".json";
41
+ /** Path prefix prepended to every key (e.g. 'data/'). Defaults to ''. */
42
+ readonly prefix: string;
43
+ /** Path suffix appended to every key (e.g. '.json'). Defaults to ''. Overrides ext when set. */
44
+ readonly suffix: string;
31
45
  constructor(
32
46
  /** dir to cache store
33
47
  * WARN: dont share this dir with other purpose
@@ -38,18 +52,25 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
38
52
  cache = new Map(),
39
53
  filename,
40
54
  ext,
55
+ prefix,
56
+ suffix,
41
57
  }: {
42
58
  cache?: CacheMap<Value>;
43
59
  filename?: (key: string) => string;
44
60
  ext?: string;
45
- } = {}
61
+ /** Path prefix prepended to every key (e.g. 'data/'). Use with filename: (k) => k for raw paths. */
62
+ prefix?: string;
63
+ /** Path suffix appended to every key (e.g. '.json'). Overrides ext when set. Use with filename: (k) => k for raw paths. */
64
+ suffix?: string;
65
+ } = {},
46
66
  ) {
47
67
  this.#ready = mkdir(dir, { recursive: true }).catch(() => {});
48
68
  this.#cache = cache;
49
-
50
69
  this.#dir = dir;
51
70
  this.#filename = filename ?? this.#defaultFilename;
52
71
  this.ext = ext ?? this.ext;
72
+ this.prefix = prefix ?? "";
73
+ this.suffix = suffix ?? "";
53
74
  }
54
75
  #defaultFilename(key: string) {
55
76
  // use dir as hash salt to avoid collisions
@@ -59,10 +80,12 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
59
80
  return name;
60
81
  }
61
82
  #path(key: string) {
62
- return path.join(
63
- this.#dir,
64
- sanitizeFilename(this.#filename(key) + this.ext)
65
- );
83
+ 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);
87
+ const relativePath = this.prefix + safeFilename;
88
+ return path.join(this.#dir, relativePath);
66
89
  }
67
90
  async get(key: string) {
68
91
  // read memory
@@ -98,10 +121,11 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
98
121
  this.#cache.set(key, { value, expires });
99
122
  // save to file
100
123
  await this.#ready;
101
- // console.log({ key, value, expires });
102
- await mkdir(this.#dir, { recursive: true }).catch(() => {});
103
- await writeFile(this.#path(key), value); // create a expired file
104
- await utimes(this.#path(key), new Date(), new Date(expires ?? 0)); // set expires time as mtime (0 as never expired)
124
+ const filePath = this.#path(key);
125
+ // create parent directories for nested paths (e.g. data/sub/key.json)
126
+ await mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
127
+ await writeFile(filePath, value); // create a expired file
128
+ await utimes(filePath, new Date(), new Date(expires ?? 0)); // set expires time as mtime (0 as never expired)
105
129
  return true;
106
130
  }
107
131
  async delete(key: string) {
@@ -116,7 +140,6 @@ export class KeyvDirStore<Value extends string> implements Keyv.Store<string> {
116
140
  async has(key: string) {
117
141
  return undefined !== (await this.get(key));
118
142
  }
119
-
120
143
  // Save expires into mtime, and value into file
121
144
  /** @deprecated use KeyvDirStoreJSON */
122
145
  static serialize({ value }: DeserializedData<any>): string {
package/package.json CHANGED
@@ -1,12 +1,28 @@
1
1
  {
2
2
  "name": "keyv-dir-store",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "keyv",
7
+ "keyv-store",
8
+ "dir"
9
+ ],
10
+ "homepage": "https://github.com/snomiao/keyv-dir-store#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/snomiao/keyv-dir-store/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/snomiao/keyv-dir-store.git"
17
+ },
18
+ "license": "MIT",
4
19
  "author": "snomiao <snomiao@gmail.com>",
5
20
  "type": "module",
6
21
  "exports": {
7
22
  "import": "./dist/index.js",
8
23
  "types": "./index.ts"
9
24
  },
25
+ "main": "index.js",
10
26
  "module": "index.ts",
11
27
  "types": "./index.ts",
12
28
  "files": [
@@ -15,26 +31,26 @@
15
31
  ],
16
32
  "scripts": {
17
33
  "build": "bun build index.ts --outdir=dist --target=bun",
34
+ "prepare": "husky",
18
35
  "prerelease": "bun run build && bun run test",
19
36
  "release": "bunx standard-version && git push --follow-tags && npm publish",
20
- "test": "bun test",
21
- "prepare": "husky"
37
+ "test": "bun test"
22
38
  },
23
39
  "dependencies": {
24
- "@types/node": "^20.14.2",
40
+ "@types/node": "^25.3.3",
25
41
  "md5": "^2.3.0",
26
42
  "sanitize-filename": "^1.6.3",
27
- "yaml": "^2.4.5"
43
+ "yaml": "^2.8.2"
28
44
  },
29
45
  "devDependencies": {
30
- "@types/bun": "^1.1.3",
31
- "@types/jest": "^29.5.12",
32
- "@types/md5": "^2.3.5",
46
+ "@types/bun": "^1.3.9",
47
+ "@types/jest": "^30.0.0",
48
+ "@types/md5": "^2.3.6",
33
49
  "husky": "^9.1.7",
34
- "semantic-release": "^24.2.1",
35
- "typescript": "^5.4.5"
50
+ "semantic-release": "^25.0.3",
51
+ "typescript": "^5.9.3"
36
52
  },
37
53
  "peerDependencies": {
38
- "keyv": "^4.5.4"
54
+ "keyv": "^5.6.0"
39
55
  }
40
56
  }