polystore 0.7.0 → 0.9.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/assets/autocomplete.png +0 -0
- package/assets/autocomplete.webp +0 -0
- package/assets/favicon.png +0 -0
- package/assets/home.html +379 -0
- package/assets/splash.png +0 -0
- package/documentation.page.json +12 -0
- package/package.json +8 -3
- package/readme.md +297 -81
- package/src/clients/cloudflare.js +49 -0
- package/src/clients/cookie.js +55 -0
- package/src/clients/etcd.js +39 -0
- package/src/clients/file.js +75 -0
- package/src/clients/forage.js +42 -0
- package/src/clients/index.js +21 -0
- package/src/clients/level.js +45 -0
- package/src/clients/memory.js +38 -0
- package/src/clients/redis.js +56 -0
- package/src/clients/storage.js +43 -0
- package/src/index.js +269 -358
- package/src/index.test.js +314 -22
- package/src/index.types.ts +1 -0
- package/src/{index.d.ts → indexa.d.ts} +5 -3
- package/src/test/customFull.js +38 -0
- package/src/test/customSimple.js +23 -0
- package/src/test/setup.js +12 -0
- package/src/utils.js +44 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Use a redis client to back up the store
|
|
2
|
+
export default class Etcd {
|
|
3
|
+
// 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
|
+
if (value === null) {
|
|
18
|
+
return this.client.delete().key(key).exec();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await this.client.put(key).value(JSON.stringify(value));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async entries(prefix = "") {
|
|
25
|
+
const keys = await this.client.getAll().prefix(prefix).keys();
|
|
26
|
+
const values = await Promise.all(keys.map((k) => this.get(k)));
|
|
27
|
+
return keys.map((k, i) => [k, values[i]]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async clear(prefix = "") {
|
|
31
|
+
if (!prefix) return this.client.delete().all();
|
|
32
|
+
|
|
33
|
+
return this.client.delete().prefix(prefix);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async close() {
|
|
37
|
+
// return this.client.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// A client that uses a single file (JSON) as a store
|
|
2
|
+
export default class File {
|
|
3
|
+
// Check if this is the right class for the given client
|
|
4
|
+
static test(client) {
|
|
5
|
+
if (typeof client === "string" && client.startsWith("file:")) return true;
|
|
6
|
+
return client instanceof URL && client.protocol === "file:";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
constructor(file) {
|
|
10
|
+
this.file =
|
|
11
|
+
typeof file === "string" ? file.slice("file://".length) : file.pathname;
|
|
12
|
+
|
|
13
|
+
// Run this once on launch; import the FS module and reset the file
|
|
14
|
+
this.promise = (async () => {
|
|
15
|
+
const fsp = await import("node:fs/promises");
|
|
16
|
+
|
|
17
|
+
// We want to make sure the file already exists, so attempt to
|
|
18
|
+
// create it (but not OVERWRITE it, that's why the x flag) and
|
|
19
|
+
// it fails if it already exists
|
|
20
|
+
await fsp.writeFile(this.file, "{}", { flag: "wx" }).catch((err) => {
|
|
21
|
+
if (err.code !== "EEXIST") throw err;
|
|
22
|
+
});
|
|
23
|
+
return fsp;
|
|
24
|
+
})();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Internal
|
|
28
|
+
async #read() {
|
|
29
|
+
const fsp = await this.promise;
|
|
30
|
+
const text = await fsp.readFile(this.file, "utf8");
|
|
31
|
+
if (!text) return {};
|
|
32
|
+
return JSON.parse(text);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async #write(data) {
|
|
36
|
+
const fsp = await this.promise;
|
|
37
|
+
await fsp.writeFile(this.file, JSON.stringify(data, null, 2));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async get(key) {
|
|
41
|
+
const data = await this.#read();
|
|
42
|
+
return data[key] ?? null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async set(key, value) {
|
|
46
|
+
const data = await this.#read();
|
|
47
|
+
if (value === null) {
|
|
48
|
+
delete data[key];
|
|
49
|
+
} else {
|
|
50
|
+
data[key] = value;
|
|
51
|
+
}
|
|
52
|
+
await this.#write(data);
|
|
53
|
+
return key;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Group methods
|
|
57
|
+
async entries(prefix = "") {
|
|
58
|
+
const data = await this.#read();
|
|
59
|
+
return Object.entries(data).filter((p) => p[0].startsWith(prefix));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async clear(prefix = "") {
|
|
63
|
+
if (!prefix) {
|
|
64
|
+
return this.#write({});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data = await this.#read();
|
|
68
|
+
for (let key in data) {
|
|
69
|
+
if (key.startsWith(prefix)) {
|
|
70
|
+
delete data[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await this.#write(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Use localForage for managing the KV
|
|
2
|
+
export default class Forage {
|
|
3
|
+
// Check if this is the right class for the given client
|
|
4
|
+
static test(client) {
|
|
5
|
+
return client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async get(key) {
|
|
13
|
+
return this.client.getItem(key);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async set(key, value) {
|
|
17
|
+
if (value === null) {
|
|
18
|
+
await this.client.removeItem(key);
|
|
19
|
+
} else {
|
|
20
|
+
await this.client.setItem(key, value);
|
|
21
|
+
}
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async entries(prefix = "") {
|
|
26
|
+
const all = await this.client.keys();
|
|
27
|
+
const keys = all.filter((k) => k.startsWith(prefix));
|
|
28
|
+
const values = await Promise.all(
|
|
29
|
+
keys.map((key) => this.client.getItem(key))
|
|
30
|
+
);
|
|
31
|
+
return keys.map((key, i) => [key, values[i]]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async clear(prefix = "") {
|
|
35
|
+
// Delete the whole dataset
|
|
36
|
+
if (!prefix) return this.client.clear();
|
|
37
|
+
|
|
38
|
+
// Delete them in a map
|
|
39
|
+
const list = await this.entries(prefix);
|
|
40
|
+
return Promise.all(list.map((e) => this.set(e[0], null)));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import cloudflare from "./cloudflare.js";
|
|
2
|
+
import cookie from "./cookie.js";
|
|
3
|
+
import etcd from "./etcd.js";
|
|
4
|
+
import file from "./file.js";
|
|
5
|
+
import forage from "./forage.js";
|
|
6
|
+
import level from "./level.js";
|
|
7
|
+
import memory from "./memory.js";
|
|
8
|
+
import redis from "./redis.js";
|
|
9
|
+
import storage from "./storage.js";
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
cloudflare,
|
|
13
|
+
cookie,
|
|
14
|
+
etcd,
|
|
15
|
+
file,
|
|
16
|
+
forage,
|
|
17
|
+
level,
|
|
18
|
+
memory,
|
|
19
|
+
redis,
|
|
20
|
+
storage,
|
|
21
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Level KV DB - https://github.com/Level/level
|
|
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
|
+
}
|
|
7
|
+
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async get(key) {
|
|
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
|
+
}
|
|
20
|
+
|
|
21
|
+
async set(key, value) {
|
|
22
|
+
return this.client.put(key, value, { valueEncoding: "json" });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async del(key) {
|
|
26
|
+
return this.client.del(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async entries(prefix = "") {
|
|
30
|
+
const keys = await this.client.keys().all();
|
|
31
|
+
const list = keys.filter((k) => k.startsWith(prefix));
|
|
32
|
+
return Promise.all(list.map(async (k) => [k, await this.get(k)]));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async clear(prefix = "") {
|
|
36
|
+
if (!prefix) return this.client.clear();
|
|
37
|
+
const keys = await this.client.keys().all();
|
|
38
|
+
const list = keys.filter((k) => k.startsWith(prefix));
|
|
39
|
+
return this.client.batch(list.map((key) => ({ type: "del", key })));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async close() {
|
|
43
|
+
return await this.client.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Use a Map() as an in-memory client
|
|
2
|
+
export default class Memory {
|
|
3
|
+
// 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
|
+
}
|
|
11
|
+
|
|
12
|
+
get(key) {
|
|
13
|
+
return this.client.get(key) ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
set(key, data) {
|
|
17
|
+
this.client.set(key, data);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
del(key) {
|
|
21
|
+
this.client.delete(key);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Group methods
|
|
25
|
+
entries(prefix = "") {
|
|
26
|
+
const entries = [...this.client.entries()];
|
|
27
|
+
return entries.filter((p) => p[0].startsWith(prefix));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clear(prefix = "") {
|
|
31
|
+
// Delete the whole dataset
|
|
32
|
+
if (!prefix) return this.client.clear();
|
|
33
|
+
|
|
34
|
+
// Delete them in a map
|
|
35
|
+
const list = this.entries(prefix);
|
|
36
|
+
return Promise.all(list.map((e) => e[0]).map((k) => this.del(k)));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Use a redis client to back up the store
|
|
2
|
+
export default class Redis {
|
|
3
|
+
// Indicate if this client handles expirations (true = it does)
|
|
4
|
+
EXPIRES = true;
|
|
5
|
+
|
|
6
|
+
// Check if this is the right class for the given client
|
|
7
|
+
static test(client) {
|
|
8
|
+
return client && client.pSubscribe && client.sSubscribe;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor(client) {
|
|
12
|
+
this.client = client;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async get(key) {
|
|
16
|
+
const value = await this.client.get(key);
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
return JSON.parse(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async set(key, value, { expires } = {}) {
|
|
22
|
+
if (value === null || expires === 0) {
|
|
23
|
+
return this.client.del(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const EX = expires ? Math.round(expires) : undefined;
|
|
27
|
+
await this.client.set(key, JSON.stringify(value), { EX });
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async has(key) {
|
|
32
|
+
return Boolean(await this.client.exists(key));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Group methods
|
|
36
|
+
async keys(prefix = "") {
|
|
37
|
+
return this.client.keys(prefix + "*");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async entries(prefix = "") {
|
|
41
|
+
const keys = await this.client.keys(prefix + "*");
|
|
42
|
+
const values = await Promise.all(keys.map((k) => this.get(k)));
|
|
43
|
+
return keys.map((k, i) => [k, values[i]]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async clear(prefix = "") {
|
|
47
|
+
if (!prefix) return this.client.flushAll();
|
|
48
|
+
|
|
49
|
+
const list = await this.keys(prefix);
|
|
50
|
+
return Promise.all(list.map((k) => this.set(k, null)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async close() {
|
|
54
|
+
return this.client.quit();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// A client that uses a single file (JSON) as a store
|
|
2
|
+
export default class WebStorage {
|
|
3
|
+
// Check if this is the right class for the given client
|
|
4
|
+
static test(client) {
|
|
5
|
+
if (typeof Storage === "undefined") return false;
|
|
6
|
+
return client instanceof Storage;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
constructor(client) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Item methods
|
|
14
|
+
get(key) {
|
|
15
|
+
const data = this.client[key];
|
|
16
|
+
return data ? JSON.parse(data) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set(key, data) {
|
|
20
|
+
if (data === null) {
|
|
21
|
+
this.client.removeItem(key);
|
|
22
|
+
} else {
|
|
23
|
+
this.client.setItem(key, JSON.stringify(data));
|
|
24
|
+
}
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Group methods
|
|
29
|
+
entries(prefix = "") {
|
|
30
|
+
const entries = Object.entries(this.client);
|
|
31
|
+
return entries
|
|
32
|
+
.map((p) => [p[0], p[1] ? JSON.parse(p[1]) : null])
|
|
33
|
+
.filter((p) => p[0].startsWith(prefix));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clear(prefix = "") {
|
|
37
|
+
// Delete the whole store
|
|
38
|
+
if (!prefix) return this.client.clear();
|
|
39
|
+
|
|
40
|
+
// Delete them in a map
|
|
41
|
+
return this.entries(prefix).map((e) => this.set(e[0], null));
|
|
42
|
+
}
|
|
43
|
+
}
|