polystore 0.15.6 → 0.15.8
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/package.json +2 -1
- package/src/clients/Client.js +7 -0
- package/src/clients/api.js +13 -22
- package/src/clients/cloudflare.js +15 -32
- package/src/clients/cookie.js +11 -15
- package/src/clients/etcd.js +12 -28
- package/src/clients/file.js +18 -27
- package/src/clients/folder.js +29 -42
- package/src/clients/forage.js +11 -29
- package/src/clients/level.js +19 -32
- package/src/clients/memory.js +9 -30
- package/src/clients/redis.js +6 -19
- package/src/clients/storage.js +8 -28
- package/src/index.js +10 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.8",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
5
|
"homepage": "https://polystore.dev/",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"src/"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
+
"analyze": "esbuild ./ --bundle --packages=external --format=esm --minify --outfile=index.min.js && gzip-size index.min.js && rm index.min.js",
|
|
18
19
|
"lint": "check-dts test/index.types.ts",
|
|
19
20
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
|
20
21
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --ci --watchAll=false --detectOpenHandles",
|
package/src/clients/api.js
CHANGED
|
@@ -1,37 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import Client from "./Client";
|
|
2
2
|
|
|
3
3
|
// Handle an API endpoint with fetch()
|
|
4
|
-
export default class Api {
|
|
4
|
+
export default class Api extends Client {
|
|
5
5
|
// Indicate that the file handler DOES handle expirations
|
|
6
6
|
EXPIRES = true;
|
|
7
7
|
|
|
8
8
|
static test = (client) =>
|
|
9
9
|
typeof client === "string" && /^https?:\/\//.test(client);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return res.ok ? res.json() : null;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
get = (key) => this.client(`/${enc(key)}`);
|
|
11
|
+
#api = async (key, opts = "", method = "GET", body) => {
|
|
12
|
+
const url = `${this.client.replace(/\/$/, "")}/${encodeURIComponent(key)}${opts}`;
|
|
13
|
+
const headers = { accept: "application/json" };
|
|
14
|
+
if (body) headers["content-type"] = "application/json";
|
|
15
|
+
const res = await fetch(url, { method, headers, body });
|
|
16
|
+
return res.ok ? res.json() : null;
|
|
17
|
+
};
|
|
23
18
|
|
|
19
|
+
get = (key) => this.#api(key);
|
|
24
20
|
set = (key, value, { expires } = {}) =>
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
"PUT",
|
|
28
|
-
JSON.stringify(value),
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
del = (key) => this.client(`/${enc(key)}`, "DELETE");
|
|
21
|
+
this.#api(key, `?expires=${expires || ""}`, "PUT", this.encode(value));
|
|
22
|
+
del = (key) => this.#api(key, "", "DELETE");
|
|
32
23
|
|
|
33
24
|
async *iterate(prefix = "") {
|
|
34
|
-
const data = await this
|
|
25
|
+
const data = await this.#api("", `?prefix=${encodeURIComponent(prefix)}`);
|
|
35
26
|
for (let [key, value] of Object.entries(data || {})) {
|
|
36
27
|
yield [prefix + key, value];
|
|
37
28
|
}
|
|
@@ -1,37 +1,25 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// Use Cloudflare's KV store
|
|
2
|
-
export default class Cloudflare {
|
|
4
|
+
export default class Cloudflare extends Client {
|
|
3
5
|
// Indicate that the file handler does NOT handle expirations
|
|
4
6
|
EXPIRES = true;
|
|
5
7
|
|
|
6
8
|
// Check whether the given store is a FILE-type
|
|
7
|
-
static test(client)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
client?.constructor?.name === "EdgeKVNamespace"
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
constructor(client) {
|
|
15
|
-
this.client = client;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async get(key) {
|
|
19
|
-
const data = await this.client.get(key);
|
|
20
|
-
if (!data) return null;
|
|
21
|
-
return JSON.parse(data);
|
|
22
|
-
}
|
|
9
|
+
static test = (client) =>
|
|
10
|
+
client?.constructor?.name === "KvNamespace" ||
|
|
11
|
+
client?.constructor?.name === "EdgeKVNamespace";
|
|
23
12
|
|
|
24
|
-
async
|
|
13
|
+
get = async (key) => this.decode(await this.client.get(key));
|
|
14
|
+
set = (key, value, { expires } = {}) => {
|
|
25
15
|
const expirationTtl = expires ? Math.round(expires) : undefined;
|
|
26
16
|
if (expirationTtl && expirationTtl < 60) {
|
|
27
17
|
throw new Error("Cloudflare's min expiration is '60s'");
|
|
28
18
|
}
|
|
29
|
-
return this.client.put(key,
|
|
30
|
-
}
|
|
19
|
+
return this.client.put(key, this.encode(value), { expirationTtl });
|
|
20
|
+
};
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
return this.client.delete(key);
|
|
34
|
-
}
|
|
22
|
+
del = (key) => this.client.delete(key);
|
|
35
23
|
|
|
36
24
|
// Since we have pagination, we don't want to get all of the
|
|
37
25
|
// keys at once if we can avoid it
|
|
@@ -50,7 +38,7 @@ export default class Cloudflare {
|
|
|
50
38
|
} while (cursor);
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
async
|
|
41
|
+
keys = async (prefix = "") => {
|
|
54
42
|
const keys = [];
|
|
55
43
|
let cursor;
|
|
56
44
|
do {
|
|
@@ -59,16 +47,11 @@ export default class Cloudflare {
|
|
|
59
47
|
cursor = raw.list_complete ? null : raw.cursor;
|
|
60
48
|
} while (cursor);
|
|
61
49
|
return keys;
|
|
62
|
-
}
|
|
50
|
+
};
|
|
63
51
|
|
|
64
|
-
async
|
|
52
|
+
entries = async (prefix = "") => {
|
|
65
53
|
const keys = await this.keys(prefix);
|
|
66
54
|
const values = await Promise.all(keys.map((k) => this.get(k)));
|
|
67
55
|
return keys.map((k, i) => [k, values[i]]);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async clear(prefix = "") {
|
|
71
|
-
const list = await this.keys(prefix);
|
|
72
|
-
return Promise.all(list.map((k) => this.del(k)));
|
|
73
|
-
}
|
|
56
|
+
};
|
|
74
57
|
}
|
package/src/clients/cookie.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// A client that uses a single file (JSON) as a store
|
|
2
|
-
export default class Cookie {
|
|
4
|
+
export default class Cookie extends Client {
|
|
3
5
|
// Indicate if this client handles expirations (true = it does)
|
|
4
6
|
EXPIRES = true;
|
|
5
7
|
|
|
6
8
|
// Check if this is the right class for the given client
|
|
7
|
-
static test(client)
|
|
8
|
-
return client === "cookie" || client === "cookies";
|
|
9
|
-
}
|
|
9
|
+
static test = (client) => client === "cookie" || client === "cookies";
|
|
10
10
|
|
|
11
11
|
// Group methods
|
|
12
|
-
#read() {
|
|
12
|
+
#read = () => {
|
|
13
13
|
const all = {};
|
|
14
14
|
for (let entry of document.cookie.split(";")) {
|
|
15
15
|
try {
|
|
@@ -22,14 +22,12 @@ export default class Cookie {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
return all;
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
// For cookies, an empty value is the same as null, even `""`
|
|
28
|
-
get(key)
|
|
29
|
-
return this.#read()[key] || null;
|
|
30
|
-
}
|
|
28
|
+
get = (key) => this.#read()[key] || null;
|
|
31
29
|
|
|
32
|
-
set(key, data = null, { expires } = {}) {
|
|
30
|
+
set = (key, data = null, { expires } = {}) => {
|
|
33
31
|
// Setting it to null deletes it
|
|
34
32
|
let expireStr = "";
|
|
35
33
|
// NOTE: 0 is already considered here!
|
|
@@ -38,13 +36,11 @@ export default class Cookie {
|
|
|
38
36
|
expireStr = `; expires=${time}`;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const value = encodeURIComponent(
|
|
39
|
+
const value = encodeURIComponent(this.encode(data || ""));
|
|
42
40
|
document.cookie = encodeURIComponent(key) + "=" + value + expireStr;
|
|
43
|
-
}
|
|
41
|
+
};
|
|
44
42
|
|
|
45
|
-
del(key) {
|
|
46
|
-
this.set(key, "", { expires: -100 });
|
|
47
|
-
}
|
|
43
|
+
del = (key) => this.set(key, "", { expires: -100 });
|
|
48
44
|
|
|
49
45
|
async *iterate(prefix = "") {
|
|
50
46
|
for (let [key, value] of Object.entries(this.#read())) {
|
package/src/clients/etcd.js
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// Use a redis client to back up the store
|
|
2
|
-
export default class Etcd {
|
|
4
|
+
export default class Etcd extends Client {
|
|
3
5
|
// Check if this is the right class for the given client
|
|
4
|
-
static test(client)
|
|
5
|
-
return client?.constructor?.name === "Etcd3";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
constructor(client) {
|
|
9
|
-
this.client = client;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async get(key) {
|
|
13
|
-
return this.client.get(key).json();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async set(key, value) {
|
|
17
|
-
return this.client.put(key).value(JSON.stringify(value));
|
|
18
|
-
}
|
|
6
|
+
static test = (client) => client?.constructor?.name === "Etcd3";
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
get = (key) => this.client.get(key).json();
|
|
9
|
+
set = (key, value) => this.client.put(key).value(this.encode(value));
|
|
10
|
+
del = (key) => this.client.delete().key(key).exec();
|
|
23
11
|
|
|
24
12
|
async *iterate(prefix = "") {
|
|
25
13
|
const keys = await this.client.getAll().prefix(prefix).keys();
|
|
@@ -28,18 +16,14 @@ export default class Etcd {
|
|
|
28
16
|
}
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async entries(prefix = "") {
|
|
19
|
+
keys = (prefix = "") => this.client.getAll().prefix(prefix).keys();
|
|
20
|
+
entries = async (prefix = "") => {
|
|
36
21
|
const keys = await this.keys(prefix);
|
|
37
22
|
const values = await Promise.all(keys.map((k) => this.get(k)));
|
|
38
23
|
return keys.map((k, i) => [k, values[i]]);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async clear(prefix = "") {
|
|
24
|
+
};
|
|
25
|
+
clear = async (prefix = "") => {
|
|
42
26
|
if (!prefix) return this.client.delete().all();
|
|
43
27
|
return this.client.delete().prefix(prefix);
|
|
44
|
-
}
|
|
28
|
+
};
|
|
45
29
|
}
|
package/src/clients/file.js
CHANGED
|
@@ -1,44 +1,36 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// A client that uses a single file (JSON) as a store
|
|
2
|
-
export default class File {
|
|
4
|
+
export default class File extends Client {
|
|
3
5
|
// Check if this is the right class for the given client
|
|
4
|
-
static test(client) {
|
|
6
|
+
static test = (client) => {
|
|
5
7
|
if (client instanceof URL) client = client.href;
|
|
6
8
|
return (
|
|
7
9
|
typeof client === "string" &&
|
|
8
10
|
client.startsWith("file://") &&
|
|
9
11
|
client.includes(".")
|
|
10
12
|
);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
constructor(file) {
|
|
14
|
-
if (file instanceof URL) file = file.href;
|
|
15
|
-
this.file = file.replace(/^file:\/\//, "");
|
|
13
|
+
};
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
return fsp;
|
|
29
|
-
})();
|
|
30
|
-
}
|
|
15
|
+
// We want to make sure the file already exists, so attempt to
|
|
16
|
+
// create the folders and the file (but not OVERWRITE it, that's why the x flag)
|
|
17
|
+
// It fails if it already exists, hence the catch case
|
|
18
|
+
#promise = (async () => {
|
|
19
|
+
this.fsp = await import("node:fs/promises");
|
|
20
|
+
this.file = (this.client?.href || this.client).replace(/^file:\/\//, "");
|
|
21
|
+
const folder = this.file.split("/").slice(0, -1).join("/");
|
|
22
|
+
await this.fsp.mkdir(folder, { recursive: true }).catch(() => {});
|
|
23
|
+
await this.fsp.writeFile(this.file, "{}", { flag: "wx" }).catch(() => {});
|
|
24
|
+
})();
|
|
31
25
|
|
|
32
26
|
// Internal
|
|
33
27
|
#read = async () => {
|
|
34
|
-
const
|
|
35
|
-
const text = await fsp.readFile(this.file, "utf8");
|
|
28
|
+
const text = await this.fsp.readFile(this.file, "utf8");
|
|
36
29
|
return text ? JSON.parse(text) : {};
|
|
37
30
|
};
|
|
38
31
|
|
|
39
32
|
#write = async (data) => {
|
|
40
|
-
|
|
41
|
-
return fsp.writeFile(this.file, JSON.stringify(data, null, 2));
|
|
33
|
+
return this.fsp.writeFile(this.file, this.encode(data));
|
|
42
34
|
};
|
|
43
35
|
|
|
44
36
|
get = async (key) => {
|
|
@@ -65,9 +57,8 @@ export default class File {
|
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
// Bulk updates are worth creating a custom method here
|
|
60
|
+
clearAll = () => this.#write({});
|
|
68
61
|
clear = async (prefix = "") => {
|
|
69
|
-
if (!prefix) return this.#write({});
|
|
70
|
-
|
|
71
62
|
const data = await this.#read();
|
|
72
63
|
for (let key in data) {
|
|
73
64
|
if (key.startsWith(prefix)) {
|
package/src/clients/folder.js
CHANGED
|
@@ -1,60 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
3
|
+
const noFileOk = (error) => {
|
|
4
|
+
if (error.code === "ENOENT") return null;
|
|
5
|
+
throw error;
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
// A client that uses a single file (JSON) as a store
|
|
4
|
-
export default class Folder {
|
|
9
|
+
export default class Folder extends Client {
|
|
5
10
|
// Check if this is the right class for the given client
|
|
6
|
-
static test(client) {
|
|
11
|
+
static test = (client) => {
|
|
7
12
|
if (client instanceof URL) client = client.href;
|
|
8
13
|
return (
|
|
9
14
|
typeof client === "string" &&
|
|
10
15
|
client.startsWith("file://") &&
|
|
11
16
|
client.endsWith("/")
|
|
12
17
|
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
constructor(folder) {
|
|
16
|
-
if (folder instanceof URL) folder = folder.href;
|
|
17
|
-
folder = folder.replace(/^file:\/\//, "");
|
|
18
|
+
};
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
);
|
|
27
|
-
});
|
|
20
|
+
// Make sure the folder already exists, so attempt to create it
|
|
21
|
+
// It fails if it already exists, hence the catch case
|
|
22
|
+
#promise = (async () => {
|
|
23
|
+
this.fsp = await import("node:fs/promises");
|
|
24
|
+
this.folder = (this.client?.href || this.client).replace(/^file:\/\//, "");
|
|
25
|
+
await this.fsp.mkdir(this.folder, { recursive: true }).catch(() => {});
|
|
26
|
+
})();
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
return async (key, ...props) => {
|
|
31
|
-
const file = folder + (key ? key + ".json" : "");
|
|
32
|
-
const method = (await prom)[name];
|
|
33
|
-
return method(file, ...props).catch((error) => {
|
|
34
|
-
if (error.code === "ENOENT") return null;
|
|
35
|
-
throw error;
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
};
|
|
28
|
+
file = (key) => this.folder + key + ".json";
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const text = await this.fs.readFile(key, "utf8");
|
|
45
|
-
return text ? JSON.parse(text) : null;
|
|
30
|
+
get = (key) => {
|
|
31
|
+
return this.fsp
|
|
32
|
+
.readFile(this.file(key), "utf8")
|
|
33
|
+
.then(this.decode, noFileOk);
|
|
46
34
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
del = (key) => this.
|
|
35
|
+
set = (key, value) => {
|
|
36
|
+
return this.fsp.writeFile(this.file(key), this.encode(value), "utf8");
|
|
37
|
+
};
|
|
38
|
+
del = (key) => this.fsp.unlink(this.file(key)).catch(noFileOk);
|
|
51
39
|
|
|
52
40
|
async *iterate(prefix = "") {
|
|
53
|
-
const all = await this.
|
|
54
|
-
const keys = all
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const key of keys) {
|
|
41
|
+
const all = await this.fsp.readdir(this.folder);
|
|
42
|
+
const keys = all.filter((f) => f.startsWith(prefix) && f.endsWith(".json"));
|
|
43
|
+
for (const name of keys) {
|
|
44
|
+
const key = name.slice(0, -".json".length);
|
|
58
45
|
const data = await this.get(key);
|
|
59
46
|
yield [key, data];
|
|
60
47
|
}
|
package/src/clients/forage.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// Use localForage for managing the KV
|
|
2
|
-
export default class Forage {
|
|
4
|
+
export default class Forage extends Client {
|
|
3
5
|
// Check if this is the right class for the given client
|
|
4
|
-
static test(client)
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
constructor(client) {
|
|
9
|
-
this.client = client;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async get(key) {
|
|
13
|
-
return this.client.getItem(key);
|
|
14
|
-
}
|
|
6
|
+
static test = (client) =>
|
|
7
|
+
client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
async del(key) {
|
|
21
|
-
return this.client.removeItem(key);
|
|
22
|
-
}
|
|
9
|
+
get = (key) => this.client.getItem(key);
|
|
10
|
+
set = (key, value) => this.client.setItem(key, value);
|
|
11
|
+
del = (key) => this.client.removeItem(key);
|
|
23
12
|
|
|
24
13
|
async *iterate(prefix = "") {
|
|
25
14
|
const keys = await this.client.keys();
|
|
@@ -29,19 +18,12 @@ export default class Forage {
|
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
20
|
|
|
32
|
-
async
|
|
21
|
+
entries = async (prefix = "") => {
|
|
33
22
|
const all = await this.client.keys();
|
|
34
23
|
const keys = all.filter((k) => k.startsWith(prefix));
|
|
35
24
|
const values = await Promise.all(keys.map((key) => this.get(key)));
|
|
36
25
|
return keys.map((key, i) => [key, values[i]]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async clear(prefix = "") {
|
|
40
|
-
// Delete the whole dataset
|
|
41
|
-
if (!prefix) return this.client.clear();
|
|
26
|
+
};
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
const list = await this.entries(prefix);
|
|
45
|
-
return Promise.all(list.map((e) => this.set(e[0], null)));
|
|
46
|
-
}
|
|
28
|
+
clearAll = () => this.client.clear();
|
|
47
29
|
}
|
package/src/clients/level.js
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
export default class Level {
|
|
3
|
-
// Check if this is the right class for the given client
|
|
4
|
-
static test(client) {
|
|
5
|
-
return client?.constructor?.name === "ClassicLevel";
|
|
6
|
-
}
|
|
1
|
+
import Client from "./Client";
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
return await this.client.get(key, { valueEncoding: "json" });
|
|
15
|
-
} catch (error) {
|
|
16
|
-
if (error?.code === "LEVEL_NOT_FOUND") return null;
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
3
|
+
const valueEncoding = "json";
|
|
4
|
+
const notFound = (error) => {
|
|
5
|
+
if (error?.code === "LEVEL_NOT_FOUND") return null;
|
|
6
|
+
throw error;
|
|
7
|
+
};
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
// Level KV DB - https://github.com/Level/level
|
|
10
|
+
export default class Level extends Client {
|
|
11
|
+
// Check if this is the right class for the given client
|
|
12
|
+
static test = (client) => client?.constructor?.name === "ClassicLevel";
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
get = (key) => this.client.get(key, { valueEncoding }).catch(notFound);
|
|
15
|
+
set = (key, value) => this.client.put(key, value, { valueEncoding });
|
|
16
|
+
del = (key) => this.client.del(key);
|
|
28
17
|
|
|
29
18
|
async *iterate(prefix = "") {
|
|
30
19
|
const keys = await this.client.keys().all();
|
|
@@ -34,20 +23,18 @@ export default class Level {
|
|
|
34
23
|
}
|
|
35
24
|
}
|
|
36
25
|
|
|
37
|
-
async
|
|
26
|
+
entries = async (prefix = "") => {
|
|
38
27
|
const keys = await this.client.keys().all();
|
|
39
28
|
const list = keys.filter((k) => k.startsWith(prefix));
|
|
40
29
|
return Promise.all(list.map(async (k) => [k, await this.get(k)]));
|
|
41
|
-
}
|
|
30
|
+
};
|
|
42
31
|
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
clearAll = () => this.client.clear();
|
|
33
|
+
clear = async (prefix = "") => {
|
|
45
34
|
const keys = await this.client.keys().all();
|
|
46
35
|
const list = keys.filter((k) => k.startsWith(prefix));
|
|
47
36
|
return this.client.batch(list.map((key) => ({ type: "del", key })));
|
|
48
|
-
}
|
|
37
|
+
};
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
return await this.client.close();
|
|
52
|
-
}
|
|
39
|
+
close = () => this.client.close();
|
|
53
40
|
}
|
package/src/clients/memory.js
CHANGED
|
@@ -1,40 +1,19 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// Use a Map() as an in-memory client
|
|
2
|
-
export default class Memory {
|
|
4
|
+
export default class Memory extends Client {
|
|
3
5
|
// Check if this is the right class for the given client
|
|
4
|
-
static test(client)
|
|
5
|
-
return client instanceof Map;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
constructor(client) {
|
|
9
|
-
this.client = client;
|
|
10
|
-
}
|
|
6
|
+
static test = (client) => client instanceof Map;
|
|
11
7
|
|
|
12
|
-
get(key)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
set(key, data) {
|
|
17
|
-
return this.client.set(key, data);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
del(key) {
|
|
21
|
-
return this.client.delete(key);
|
|
22
|
-
}
|
|
8
|
+
get = (key) => this.client.get(key) ?? null;
|
|
9
|
+
set = (key, data) => this.client.set(key, data);
|
|
10
|
+
del = (key) => this.client.delete(key);
|
|
23
11
|
|
|
24
12
|
*iterate(prefix = "") {
|
|
25
13
|
for (const entry of this.client.entries()) {
|
|
26
|
-
if (
|
|
27
|
-
yield entry;
|
|
14
|
+
if (entry[0].startsWith(prefix)) yield entry;
|
|
28
15
|
}
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
// Delete the whole dataset
|
|
33
|
-
if (!prefix) return this.client.clear();
|
|
34
|
-
|
|
35
|
-
// Delete them in a map
|
|
36
|
-
return [...this.client.keys()]
|
|
37
|
-
.filter((k) => k.startsWith(prefix))
|
|
38
|
-
.map((k) => this.del(k));
|
|
39
|
-
}
|
|
18
|
+
clearAll = () => this.client.clear();
|
|
40
19
|
}
|
package/src/clients/redis.js
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// Use a redis client to back up the store
|
|
2
|
-
export default class Redis {
|
|
4
|
+
export default class Redis extends Client {
|
|
3
5
|
// Indicate if this client handles expirations (true = it does)
|
|
4
6
|
EXPIRES = true;
|
|
5
7
|
|
|
6
8
|
// Check if this is the right class for the given client
|
|
7
9
|
static test = (client) => client && client.pSubscribe && client.sSubscribe;
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
this.client = client;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
get = async (key) => {
|
|
14
|
-
const text = await this.client.get(key);
|
|
15
|
-
return text ? JSON.parse(text) : null;
|
|
16
|
-
};
|
|
17
|
-
|
|
11
|
+
get = async (key) => this.decode(await this.client.get(key));
|
|
18
12
|
set = async (key, value, { expires } = {}) => {
|
|
19
13
|
const EX = expires ? Math.round(expires) : undefined;
|
|
20
|
-
return this.client.set(key,
|
|
14
|
+
return this.client.set(key, this.encode(value), { EX });
|
|
21
15
|
};
|
|
22
|
-
|
|
23
16
|
del = (key) => this.client.del(key);
|
|
24
17
|
|
|
25
18
|
has = async (key) => Boolean(await this.client.exists(key));
|
|
@@ -53,12 +46,6 @@ export default class Redis {
|
|
|
53
46
|
return keys.map((k, i) => [k, values[i]]);
|
|
54
47
|
};
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
if (!prefix) return this.client.flushAll();
|
|
58
|
-
|
|
59
|
-
const list = await this.keys(prefix);
|
|
60
|
-
return Promise.all(list.map((k) => this.client.del(k)));
|
|
61
|
-
};
|
|
62
|
-
|
|
49
|
+
clearAll = () => this.client.flushAll();
|
|
63
50
|
close = () => this.client.quit();
|
|
64
51
|
}
|
package/src/clients/storage.js
CHANGED
|
@@ -1,45 +1,25 @@
|
|
|
1
|
+
import Client from "./Client";
|
|
2
|
+
|
|
1
3
|
// A client that uses a single file (JSON) as a store
|
|
2
|
-
export default class WebStorage {
|
|
4
|
+
export default class WebStorage extends Client {
|
|
3
5
|
// Check if this is the right class for the given client
|
|
4
6
|
static test(client) {
|
|
5
7
|
if (typeof Storage === "undefined") return false;
|
|
6
8
|
return client instanceof Storage;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
constructor(client) {
|
|
10
|
-
this.client = client;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
11
|
// Item methods
|
|
14
|
-
get(key)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
set(key, data) {
|
|
20
|
-
return this.client.setItem(key, JSON.stringify(data));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
del(key) {
|
|
24
|
-
return this.client.removeItem(key);
|
|
25
|
-
}
|
|
12
|
+
get = (key) => this.decode(this.client[key]);
|
|
13
|
+
set = (key, data) => this.client.setItem(key, this.encode(data));
|
|
14
|
+
del = (key) => this.client.removeItem(key);
|
|
26
15
|
|
|
27
16
|
*iterate(prefix = "") {
|
|
28
17
|
for (const key of Object.keys(this.client)) {
|
|
29
18
|
if (!key.startsWith(prefix)) continue;
|
|
30
19
|
const value = this.get(key);
|
|
31
|
-
if (
|
|
32
|
-
yield [key, value];
|
|
20
|
+
if (value) yield [key, value];
|
|
33
21
|
}
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
// Delete the whole store
|
|
38
|
-
if (!prefix) return this.client.clear();
|
|
39
|
-
|
|
40
|
-
// Delete them in a map
|
|
41
|
-
return Object.keys(this.client)
|
|
42
|
-
.filter((k) => k.startsWith(prefix))
|
|
43
|
-
.map((k) => this.del(k));
|
|
44
|
-
}
|
|
24
|
+
clearAll = () => this.client.clear();
|
|
45
25
|
}
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ class Store {
|
|
|
9
9
|
this.client = this.#find(client);
|
|
10
10
|
this.#validate(this.client);
|
|
11
11
|
this.promise = null;
|
|
12
|
+
await this.client.promise;
|
|
12
13
|
return client;
|
|
13
14
|
});
|
|
14
15
|
}
|
|
@@ -47,7 +48,7 @@ class Store {
|
|
|
47
48
|
for (let method of ["has", "keys", "values"]) {
|
|
48
49
|
if (client[method]) {
|
|
49
50
|
throw new Error(
|
|
50
|
-
`You can only define client.${method}() when the client manages the expiration
|
|
51
|
+
`You can only define client.${method}() when the client manages the expiration.`,
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
@@ -91,8 +92,7 @@ class Store {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
const key = createId();
|
|
94
|
-
|
|
95
|
-
return key; // The plain one without the prefix
|
|
95
|
+
return this.set(key, value, { expires });
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
async set(key, value, options = {}) {
|
|
@@ -102,8 +102,7 @@ class Store {
|
|
|
102
102
|
|
|
103
103
|
// Quick delete
|
|
104
104
|
if (value === null || (typeof expires === "number" && expires <= 0)) {
|
|
105
|
-
|
|
106
|
-
return key;
|
|
105
|
+
return this.del(id);
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
// The client manages the expiration, so let it manage it
|
|
@@ -143,8 +142,7 @@ class Store {
|
|
|
143
142
|
return this.client.has(id);
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
return value !== null;
|
|
145
|
+
return (await this.get(key)) !== null;
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
async del(key) {
|
|
@@ -247,6 +245,10 @@ class Store {
|
|
|
247
245
|
async clear() {
|
|
248
246
|
await this.promise;
|
|
249
247
|
|
|
248
|
+
if (!this.PREFIX && this.client.clearAll) {
|
|
249
|
+
return this.client.clearAll();
|
|
250
|
+
}
|
|
251
|
+
|
|
250
252
|
if (this.client.clear) {
|
|
251
253
|
return this.client.clear(this.PREFIX);
|
|
252
254
|
}
|
|
@@ -258,7 +260,7 @@ class Store {
|
|
|
258
260
|
|
|
259
261
|
prefix(prefix = "") {
|
|
260
262
|
const store = new Store(
|
|
261
|
-
Promise.resolve(this.promise).then((
|
|
263
|
+
Promise.resolve(this.promise).then(() => this.client),
|
|
262
264
|
);
|
|
263
265
|
store.PREFIX = this.PREFIX + prefix;
|
|
264
266
|
return store;
|