fastkv 1.0.0 → 2.0.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 +36 -19
- package/dist/index.d.mts +25 -36
- package/dist/index.d.ts +25 -36
- package/dist/index.js +218 -1
- package/dist/index.mjs +183 -1
- package/package.json +27 -7
package/README.md
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
FastKV is a key-value store which offers the following features:
|
|
8
8
|
|
|
9
|
-
- Supports
|
|
10
|
-
-
|
|
11
|
-
- Lightweight with no dependencies ⚡
|
|
9
|
+
- Supports storage in memory and in JSON files
|
|
10
|
+
- Lightweight with zero dependencies
|
|
12
11
|
|
|
13
12
|
## Installation
|
|
14
13
|
|
|
15
|
-
Get FastKV via
|
|
14
|
+
Get FastKV via your package manager.
|
|
16
15
|
|
|
17
16
|
```sh
|
|
18
17
|
npm install fastkv
|
|
@@ -20,29 +19,47 @@ npm install fastkv
|
|
|
20
19
|
yarn add fastkv
|
|
21
20
|
# or
|
|
22
21
|
pnpm add fastkv
|
|
22
|
+
# or
|
|
23
|
+
bun add fastkv
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Example
|
|
26
27
|
|
|
27
|
-
```
|
|
28
|
-
import { KV } from
|
|
28
|
+
```ts
|
|
29
|
+
import { KV } from "fastkv";
|
|
30
|
+
|
|
31
|
+
// Type safety (optional; for TypeScript)
|
|
32
|
+
interface User {
|
|
33
|
+
name: string;
|
|
34
|
+
age?: number;
|
|
35
|
+
settings?: { theme: string };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const kv = new KV<User>({ path: "./db.json" }); // Persistent storage
|
|
39
|
+
const cache = new KV<User>(); // In-memory store (no path provided)
|
|
40
|
+
|
|
41
|
+
// Set or overwrite data
|
|
42
|
+
await kv.set("user1", { name: "Alice", age: 25 });
|
|
29
43
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// Or: example.get<string>('my-key-name');
|
|
44
|
+
// Get data by key
|
|
45
|
+
const user = await kv.get("user1"); // -> { __id: ..., key: 'user1', value: { name: 'Alice', age: 25 } }
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
const
|
|
47
|
+
// Get nested values
|
|
48
|
+
const theme = await kv.get("user1.settings.theme"); // -> 'dark'
|
|
36
49
|
|
|
37
|
-
//
|
|
38
|
-
kv.
|
|
50
|
+
// Upsert (update or create)
|
|
51
|
+
await kv.upsert({
|
|
52
|
+
where: { name: "Alice" }, // filter
|
|
53
|
+
create: { name: "Alice", age: 25, settings: { theme: "dark" } }, // if not exists
|
|
54
|
+
update: { age: 26 }, // partial update if exists
|
|
55
|
+
});
|
|
39
56
|
|
|
40
|
-
//
|
|
41
|
-
kv.
|
|
57
|
+
// Find entries by key or value
|
|
58
|
+
const usersNamedAlice = await kv.find({ where: "Alice", in: "value" });
|
|
42
59
|
|
|
43
|
-
//
|
|
44
|
-
kv.all();
|
|
60
|
+
// Get all entries
|
|
61
|
+
const allEntries = await kv.all();
|
|
45
62
|
|
|
46
|
-
// Clear
|
|
47
|
-
kv.clear();
|
|
63
|
+
// Clear all entries
|
|
64
|
+
await kv.clear();
|
|
48
65
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,43 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
interface KVOptions {
|
|
2
|
+
path?: string;
|
|
3
|
+
prettify?: boolean;
|
|
4
|
+
}
|
|
5
|
+
type Entry<T = any> = {
|
|
6
|
+
__id: string;
|
|
2
7
|
key: string;
|
|
3
8
|
value: T;
|
|
4
9
|
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
10
|
+
type DeepPartial<T> = {
|
|
11
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
12
|
+
};
|
|
9
13
|
declare class KV<T = any> {
|
|
10
14
|
#private;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* @param key - The existing key's name.
|
|
27
|
-
* @returns The found data, or null.
|
|
28
|
-
*/
|
|
29
|
-
get<U = T>(key: string): U | null;
|
|
30
|
-
/**
|
|
31
|
-
* Gets all data in the store.
|
|
32
|
-
* @returns An array of Entry objects [{ key: string, value: T }].
|
|
33
|
-
*/
|
|
34
|
-
all(): Entry<T>[];
|
|
35
|
-
/**
|
|
36
|
-
* Clears all data in the store.
|
|
37
|
-
* @returns The KV instance.
|
|
38
|
-
*/
|
|
39
|
-
clear(): this;
|
|
15
|
+
path?: string;
|
|
16
|
+
constructor(options?: KVOptions);
|
|
17
|
+
set(key: string, value: T): Promise<Entry<T>>;
|
|
18
|
+
upsert(options: {
|
|
19
|
+
where: DeepPartial<T> | string;
|
|
20
|
+
create?: DeepPartial<T>;
|
|
21
|
+
update?: DeepPartial<T>;
|
|
22
|
+
}): Promise<Entry<T>>;
|
|
23
|
+
find(options: {
|
|
24
|
+
in?: "key" | "value";
|
|
25
|
+
where: T | string | number | boolean;
|
|
26
|
+
}): Promise<Entry<T>[]>;
|
|
27
|
+
get(query: string): Promise<Entry<T> | T | null>;
|
|
28
|
+
all(): Promise<Entry<T>[]>;
|
|
29
|
+
clear(): Promise<void>;
|
|
40
30
|
}
|
|
41
|
-
declare const _default: KV<any>;
|
|
42
31
|
|
|
43
|
-
export { KV,
|
|
32
|
+
export { KV, KV as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,43 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
interface KVOptions {
|
|
2
|
+
path?: string;
|
|
3
|
+
prettify?: boolean;
|
|
4
|
+
}
|
|
5
|
+
type Entry<T = any> = {
|
|
6
|
+
__id: string;
|
|
2
7
|
key: string;
|
|
3
8
|
value: T;
|
|
4
9
|
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
10
|
+
type DeepPartial<T> = {
|
|
11
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
12
|
+
};
|
|
9
13
|
declare class KV<T = any> {
|
|
10
14
|
#private;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* @param key - The existing key's name.
|
|
27
|
-
* @returns The found data, or null.
|
|
28
|
-
*/
|
|
29
|
-
get<U = T>(key: string): U | null;
|
|
30
|
-
/**
|
|
31
|
-
* Gets all data in the store.
|
|
32
|
-
* @returns An array of Entry objects [{ key: string, value: T }].
|
|
33
|
-
*/
|
|
34
|
-
all(): Entry<T>[];
|
|
35
|
-
/**
|
|
36
|
-
* Clears all data in the store.
|
|
37
|
-
* @returns The KV instance.
|
|
38
|
-
*/
|
|
39
|
-
clear(): this;
|
|
15
|
+
path?: string;
|
|
16
|
+
constructor(options?: KVOptions);
|
|
17
|
+
set(key: string, value: T): Promise<Entry<T>>;
|
|
18
|
+
upsert(options: {
|
|
19
|
+
where: DeepPartial<T> | string;
|
|
20
|
+
create?: DeepPartial<T>;
|
|
21
|
+
update?: DeepPartial<T>;
|
|
22
|
+
}): Promise<Entry<T>>;
|
|
23
|
+
find(options: {
|
|
24
|
+
in?: "key" | "value";
|
|
25
|
+
where: T | string | number | boolean;
|
|
26
|
+
}): Promise<Entry<T>[]>;
|
|
27
|
+
get(query: string): Promise<Entry<T> | T | null>;
|
|
28
|
+
all(): Promise<Entry<T>[]>;
|
|
29
|
+
clear(): Promise<void>;
|
|
40
30
|
}
|
|
41
|
-
declare const _default: KV<any>;
|
|
42
31
|
|
|
43
|
-
export { KV,
|
|
32
|
+
export { KV, KV as default };
|
package/dist/index.js
CHANGED
|
@@ -1 +1,218 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
KV: () => KV,
|
|
34
|
+
default: () => index_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_node_crypto = require("crypto");
|
|
38
|
+
var import_node_path = __toESM(require("path"));
|
|
39
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
40
|
+
var KV = class {
|
|
41
|
+
path;
|
|
42
|
+
#data = [];
|
|
43
|
+
#ready;
|
|
44
|
+
#prettify;
|
|
45
|
+
/**
|
|
46
|
+
* Create a new KV instance
|
|
47
|
+
* @param options Configuration options
|
|
48
|
+
*/
|
|
49
|
+
constructor(options) {
|
|
50
|
+
if (options?.prettify) {
|
|
51
|
+
this.#prettify = options?.prettify;
|
|
52
|
+
} else {
|
|
53
|
+
this.#prettify = false;
|
|
54
|
+
}
|
|
55
|
+
if (options?.path) {
|
|
56
|
+
this.path = import_node_path.default.resolve(process.cwd(), options.path);
|
|
57
|
+
this.#ready = this.#loadFromFile();
|
|
58
|
+
} else {
|
|
59
|
+
this.#ready = Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load stored data from disk into memory, create a new empty store if file doesn't exist
|
|
64
|
+
*/
|
|
65
|
+
async #loadFromFile() {
|
|
66
|
+
try {
|
|
67
|
+
const content = await import_promises.default.readFile(this.path, "utf8");
|
|
68
|
+
this.#data = JSON.parse(content);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error.code === "ENOENT") {
|
|
71
|
+
this.#data = [];
|
|
72
|
+
await this.#saveToFile();
|
|
73
|
+
} else {
|
|
74
|
+
throw new Error("Failed to load database file: " + error.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Save in-memory data to disk if file path is provided
|
|
80
|
+
*/
|
|
81
|
+
async #saveToFile() {
|
|
82
|
+
if (!this.path) return;
|
|
83
|
+
await import_promises.default.writeFile(
|
|
84
|
+
this.path,
|
|
85
|
+
JSON.stringify(this.#data, null, this.#prettify ? 2 : 0),
|
|
86
|
+
"utf8"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves dot-notation paths within objects
|
|
91
|
+
* @param object Base object
|
|
92
|
+
* @param path Dot-separated path string
|
|
93
|
+
* @returns Resolved value, or undefined
|
|
94
|
+
*/
|
|
95
|
+
#resolvePath(object, path2) {
|
|
96
|
+
return path2.split(".").reduce((acc, part) => acc ? acc[part] : void 0, object);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compare a value against a partial filter object
|
|
100
|
+
* @param value Actual stored value
|
|
101
|
+
* @param where Partial value to match against
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
104
|
+
#matches(value, where) {
|
|
105
|
+
if (where === null) return value === null;
|
|
106
|
+
if (typeof value !== typeof where) return false;
|
|
107
|
+
if (typeof value === "object" && value !== null && where !== null) {
|
|
108
|
+
return Object.entries(where).every(([k, v]) => this.#matches(value[k], v));
|
|
109
|
+
}
|
|
110
|
+
return value === where;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set a value by key, creates a new entry or overrides existing one
|
|
114
|
+
* @param key Unique identifier
|
|
115
|
+
* @param value Data to store
|
|
116
|
+
* @returns
|
|
117
|
+
*/
|
|
118
|
+
async set(key, value) {
|
|
119
|
+
if (!key) throw new Error("Key cannot be empty");
|
|
120
|
+
await this.#ready;
|
|
121
|
+
const entry = this.#data.find((entry2) => entry2.key === key);
|
|
122
|
+
if (entry) {
|
|
123
|
+
entry.value = value;
|
|
124
|
+
await this.#saveToFile();
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
const newEntry = { __id: (0, import_node_crypto.randomUUID)(), key, value };
|
|
128
|
+
this.#data.push(newEntry);
|
|
129
|
+
await this.#saveToFile();
|
|
130
|
+
return newEntry;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Update an existing entry or create a new one if not found
|
|
134
|
+
* @param options Upserting options
|
|
135
|
+
*/
|
|
136
|
+
async upsert(options) {
|
|
137
|
+
if (!options.create && !options.update) {
|
|
138
|
+
throw new Error("Upsert requires at least create or update to be provided");
|
|
139
|
+
}
|
|
140
|
+
if (typeof options.where === "string" && !options.where) {
|
|
141
|
+
throw new Error("Upsert key cannot be empty");
|
|
142
|
+
}
|
|
143
|
+
await this.#ready;
|
|
144
|
+
let entry;
|
|
145
|
+
if (typeof options.where === "string") {
|
|
146
|
+
entry = this.#data.find((entry2) => entry2.key === options.where);
|
|
147
|
+
} else {
|
|
148
|
+
entry = this.#data.find((entry2) => this.#matches(entry2.value, options.where));
|
|
149
|
+
}
|
|
150
|
+
if (entry) {
|
|
151
|
+
if (options.update) {
|
|
152
|
+
entry.value = { ...entry.value, ...options.update };
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
const key = typeof options.where === "string" ? options.where : (0, import_node_crypto.randomUUID)();
|
|
156
|
+
const baseValue = options.create ?? options.update ?? {};
|
|
157
|
+
entry = {
|
|
158
|
+
__id: (0, import_node_crypto.randomUUID)(),
|
|
159
|
+
key,
|
|
160
|
+
value: { ...baseValue }
|
|
161
|
+
};
|
|
162
|
+
this.#data.push(entry);
|
|
163
|
+
}
|
|
164
|
+
await this.#saveToFile();
|
|
165
|
+
return entry;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Find entries by matching key or value
|
|
169
|
+
* @param options Search options
|
|
170
|
+
*/
|
|
171
|
+
async find(options) {
|
|
172
|
+
if (options.in && options.in !== "key" && options.in !== "value") {
|
|
173
|
+
throw new Error(`Invalid search field: ${options.in}`);
|
|
174
|
+
}
|
|
175
|
+
await this.#ready;
|
|
176
|
+
const field = options.in ?? "value";
|
|
177
|
+
const where = options.where;
|
|
178
|
+
return this.#data.filter((entry) => {
|
|
179
|
+
if (field === "key") {
|
|
180
|
+
return typeof where === "string" && entry.key.includes(where);
|
|
181
|
+
}
|
|
182
|
+
return this.#matches(entry.value, where);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get an entry, supports nested values with dot notation
|
|
187
|
+
* @param query Key or path via dot notation
|
|
188
|
+
*/
|
|
189
|
+
async get(query) {
|
|
190
|
+
if (!query) throw new Error("Query string cannot be empty");
|
|
191
|
+
await this.#ready;
|
|
192
|
+
const [baseKey, ...rest] = query.split(".");
|
|
193
|
+
const entry = this.#data.find((entry2) => entry2.key === baseKey);
|
|
194
|
+
if (!entry) return null;
|
|
195
|
+
if (rest.length === 0) return entry;
|
|
196
|
+
return this.#resolvePath({ value: entry.value }, rest.join(".")) ?? null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get all stored entries
|
|
200
|
+
*/
|
|
201
|
+
async all() {
|
|
202
|
+
await this.#ready;
|
|
203
|
+
return [...this.#data];
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Remove all entries
|
|
207
|
+
*/
|
|
208
|
+
async clear() {
|
|
209
|
+
await this.#ready;
|
|
210
|
+
this.#data = [];
|
|
211
|
+
await this.#saveToFile();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var index_default = KV;
|
|
215
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
216
|
+
0 && (module.exports = {
|
|
217
|
+
KV
|
|
218
|
+
});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1,183 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
var KV = class {
|
|
6
|
+
path;
|
|
7
|
+
#data = [];
|
|
8
|
+
#ready;
|
|
9
|
+
#prettify;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new KV instance
|
|
12
|
+
* @param options Configuration options
|
|
13
|
+
*/
|
|
14
|
+
constructor(options) {
|
|
15
|
+
if (options?.prettify) {
|
|
16
|
+
this.#prettify = options?.prettify;
|
|
17
|
+
} else {
|
|
18
|
+
this.#prettify = false;
|
|
19
|
+
}
|
|
20
|
+
if (options?.path) {
|
|
21
|
+
this.path = path.resolve(process.cwd(), options.path);
|
|
22
|
+
this.#ready = this.#loadFromFile();
|
|
23
|
+
} else {
|
|
24
|
+
this.#ready = Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load stored data from disk into memory, create a new empty store if file doesn't exist
|
|
29
|
+
*/
|
|
30
|
+
async #loadFromFile() {
|
|
31
|
+
try {
|
|
32
|
+
const content = await fs.readFile(this.path, "utf8");
|
|
33
|
+
this.#data = JSON.parse(content);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code === "ENOENT") {
|
|
36
|
+
this.#data = [];
|
|
37
|
+
await this.#saveToFile();
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error("Failed to load database file: " + error.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Save in-memory data to disk if file path is provided
|
|
45
|
+
*/
|
|
46
|
+
async #saveToFile() {
|
|
47
|
+
if (!this.path) return;
|
|
48
|
+
await fs.writeFile(
|
|
49
|
+
this.path,
|
|
50
|
+
JSON.stringify(this.#data, null, this.#prettify ? 2 : 0),
|
|
51
|
+
"utf8"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves dot-notation paths within objects
|
|
56
|
+
* @param object Base object
|
|
57
|
+
* @param path Dot-separated path string
|
|
58
|
+
* @returns Resolved value, or undefined
|
|
59
|
+
*/
|
|
60
|
+
#resolvePath(object, path2) {
|
|
61
|
+
return path2.split(".").reduce((acc, part) => acc ? acc[part] : void 0, object);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Compare a value against a partial filter object
|
|
65
|
+
* @param value Actual stored value
|
|
66
|
+
* @param where Partial value to match against
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
#matches(value, where) {
|
|
70
|
+
if (where === null) return value === null;
|
|
71
|
+
if (typeof value !== typeof where) return false;
|
|
72
|
+
if (typeof value === "object" && value !== null && where !== null) {
|
|
73
|
+
return Object.entries(where).every(([k, v]) => this.#matches(value[k], v));
|
|
74
|
+
}
|
|
75
|
+
return value === where;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Set a value by key, creates a new entry or overrides existing one
|
|
79
|
+
* @param key Unique identifier
|
|
80
|
+
* @param value Data to store
|
|
81
|
+
* @returns
|
|
82
|
+
*/
|
|
83
|
+
async set(key, value) {
|
|
84
|
+
if (!key) throw new Error("Key cannot be empty");
|
|
85
|
+
await this.#ready;
|
|
86
|
+
const entry = this.#data.find((entry2) => entry2.key === key);
|
|
87
|
+
if (entry) {
|
|
88
|
+
entry.value = value;
|
|
89
|
+
await this.#saveToFile();
|
|
90
|
+
return entry;
|
|
91
|
+
}
|
|
92
|
+
const newEntry = { __id: randomUUID(), key, value };
|
|
93
|
+
this.#data.push(newEntry);
|
|
94
|
+
await this.#saveToFile();
|
|
95
|
+
return newEntry;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Update an existing entry or create a new one if not found
|
|
99
|
+
* @param options Upserting options
|
|
100
|
+
*/
|
|
101
|
+
async upsert(options) {
|
|
102
|
+
if (!options.create && !options.update) {
|
|
103
|
+
throw new Error("Upsert requires at least create or update to be provided");
|
|
104
|
+
}
|
|
105
|
+
if (typeof options.where === "string" && !options.where) {
|
|
106
|
+
throw new Error("Upsert key cannot be empty");
|
|
107
|
+
}
|
|
108
|
+
await this.#ready;
|
|
109
|
+
let entry;
|
|
110
|
+
if (typeof options.where === "string") {
|
|
111
|
+
entry = this.#data.find((entry2) => entry2.key === options.where);
|
|
112
|
+
} else {
|
|
113
|
+
entry = this.#data.find((entry2) => this.#matches(entry2.value, options.where));
|
|
114
|
+
}
|
|
115
|
+
if (entry) {
|
|
116
|
+
if (options.update) {
|
|
117
|
+
entry.value = { ...entry.value, ...options.update };
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const key = typeof options.where === "string" ? options.where : randomUUID();
|
|
121
|
+
const baseValue = options.create ?? options.update ?? {};
|
|
122
|
+
entry = {
|
|
123
|
+
__id: randomUUID(),
|
|
124
|
+
key,
|
|
125
|
+
value: { ...baseValue }
|
|
126
|
+
};
|
|
127
|
+
this.#data.push(entry);
|
|
128
|
+
}
|
|
129
|
+
await this.#saveToFile();
|
|
130
|
+
return entry;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Find entries by matching key or value
|
|
134
|
+
* @param options Search options
|
|
135
|
+
*/
|
|
136
|
+
async find(options) {
|
|
137
|
+
if (options.in && options.in !== "key" && options.in !== "value") {
|
|
138
|
+
throw new Error(`Invalid search field: ${options.in}`);
|
|
139
|
+
}
|
|
140
|
+
await this.#ready;
|
|
141
|
+
const field = options.in ?? "value";
|
|
142
|
+
const where = options.where;
|
|
143
|
+
return this.#data.filter((entry) => {
|
|
144
|
+
if (field === "key") {
|
|
145
|
+
return typeof where === "string" && entry.key.includes(where);
|
|
146
|
+
}
|
|
147
|
+
return this.#matches(entry.value, where);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get an entry, supports nested values with dot notation
|
|
152
|
+
* @param query Key or path via dot notation
|
|
153
|
+
*/
|
|
154
|
+
async get(query) {
|
|
155
|
+
if (!query) throw new Error("Query string cannot be empty");
|
|
156
|
+
await this.#ready;
|
|
157
|
+
const [baseKey, ...rest] = query.split(".");
|
|
158
|
+
const entry = this.#data.find((entry2) => entry2.key === baseKey);
|
|
159
|
+
if (!entry) return null;
|
|
160
|
+
if (rest.length === 0) return entry;
|
|
161
|
+
return this.#resolvePath({ value: entry.value }, rest.join(".")) ?? null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get all stored entries
|
|
165
|
+
*/
|
|
166
|
+
async all() {
|
|
167
|
+
await this.#ready;
|
|
168
|
+
return [...this.#data];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Remove all entries
|
|
172
|
+
*/
|
|
173
|
+
async clear() {
|
|
174
|
+
await this.#ready;
|
|
175
|
+
this.#data = [];
|
|
176
|
+
await this.#saveToFile();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var index_default = KV;
|
|
180
|
+
export {
|
|
181
|
+
KV,
|
|
182
|
+
index_default as default
|
|
183
|
+
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastkv",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"
|
|
8
|
-
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"package.json",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"description": "A simple key-value store with optional JSON file storing",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
9
16
|
"repository": {
|
|
10
17
|
"url": "https://github.com/m1-dev/fastkv"
|
|
11
18
|
},
|
|
@@ -14,16 +21,29 @@
|
|
|
14
21
|
"database",
|
|
15
22
|
"json"
|
|
16
23
|
],
|
|
24
|
+
"prettier": {
|
|
25
|
+
"tabWidth": 4,
|
|
26
|
+
"printWidth": 95,
|
|
27
|
+
"arrowParens": "always",
|
|
28
|
+
"trailingComma": "es5",
|
|
29
|
+
"singleQuote": false,
|
|
30
|
+
"jsxSingleQuote": false,
|
|
31
|
+
"useTabs": false,
|
|
32
|
+
"semi": true
|
|
33
|
+
},
|
|
17
34
|
"license": "MIT",
|
|
18
35
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^20.
|
|
20
|
-
"
|
|
36
|
+
"@types/node": "^20.11.16",
|
|
37
|
+
"prettier": "^3.2.5",
|
|
21
38
|
"tsup": "^8.0.1",
|
|
22
39
|
"typescript": "^5.3.3"
|
|
23
40
|
},
|
|
24
41
|
"scripts": {
|
|
25
42
|
"build": "tsup",
|
|
26
|
-
"lint": "tsc --noEmit
|
|
27
|
-
"prepublish": "pnpm run build"
|
|
43
|
+
"lint": "tsc --noEmit; prettier . --check --ignore-path=.prettierignore",
|
|
44
|
+
"prepublish": "pnpm run build",
|
|
45
|
+
"format": "prettier . --write --ignore-path=.prettierignore",
|
|
46
|
+
"deploy:fastkv:beta": "pnpm publish --no-git-checks --access public --tag beta",
|
|
47
|
+
"deploy:fastkv:stable": "pnpm publish --no-git-checks --access public"
|
|
28
48
|
}
|
|
29
49
|
}
|