polystore 0.15.12 → 0.16.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.
@@ -1,7 +0,0 @@
1
- export default class Client {
2
- constructor(client) {
3
- this.client = client;
4
- }
5
- encode = (val) => JSON.stringify(val, null, 2);
6
- decode = (val) => (val ? JSON.parse(val) : null);
7
- }
@@ -1,30 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Handle an API endpoint with fetch()
4
- export default class Api extends Client {
5
- // Indicate that the file handler DOES handle expirations
6
- EXPIRES = true;
7
-
8
- static test = (client) =>
9
- typeof client === "string" && /^https?:\/\//.test(client);
10
-
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
- };
18
-
19
- get = (key) => this.#api(key);
20
- set = (key, value, { expires } = {}) =>
21
- this.#api(key, `?expires=${expires || ""}`, "PUT", this.encode(value));
22
- del = (key) => this.#api(key, "", "DELETE");
23
-
24
- async *iterate(prefix = "") {
25
- const data = await this.#api("", `?prefix=${encodeURIComponent(prefix)}`);
26
- for (let [key, value] of Object.entries(data || {})) {
27
- yield [prefix + key, value];
28
- }
29
- }
30
- }
@@ -1,56 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Use Cloudflare's KV store
4
- export default class Cloudflare extends Client {
5
- // Indicate that the file handler does NOT handle expirations
6
- EXPIRES = true;
7
-
8
- // Check whether the given store is a FILE-type
9
- static test = (client) =>
10
- client?.constructor?.name === "KvNamespace" ||
11
- client?.constructor?.name === "EdgeKVNamespace";
12
-
13
- get = async (key) => this.decode(await this.client.get(key));
14
- set = (key, value, { expires } = {}) => {
15
- const expirationTtl = expires ? Math.round(expires) : undefined;
16
- if (expirationTtl && expirationTtl < 60) {
17
- throw new Error("Cloudflare's min expiration is '60s'");
18
- }
19
- return this.client.put(key, this.encode(value), { expirationTtl });
20
- };
21
-
22
- del = (key) => this.client.delete(key);
23
-
24
- // Since we have pagination, we don't want to get all of the
25
- // keys at once if we can avoid it
26
- async *iterate(prefix = "") {
27
- let cursor;
28
- do {
29
- const raw = await this.client.list({ prefix, cursor });
30
- const keys = raw.keys.map((k) => k.name);
31
- for (let key of keys) {
32
- const value = await this.get(key);
33
- // By the time this value is read it could be gone!
34
- if (value) yield [key, value];
35
- }
36
- cursor = raw.list_complete ? null : raw.cursor;
37
- } while (cursor);
38
- }
39
-
40
- keys = async (prefix = "") => {
41
- const keys = [];
42
- let cursor;
43
- do {
44
- const raw = await this.client.list({ prefix, cursor });
45
- keys.push(...raw.keys.map((k) => k.name));
46
- cursor = raw.list_complete ? null : raw.cursor;
47
- } while (cursor);
48
- return keys;
49
- };
50
-
51
- entries = async (prefix = "") => {
52
- const keys = await this.keys(prefix);
53
- const values = await Promise.all(keys.map((k) => this.get(k)));
54
- return keys.map((k, i) => [k, values[i]]);
55
- };
56
- }
@@ -1,51 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // A client that uses a single file (JSON) as a store
4
- export default class Cookie extends Client {
5
- // Indicate if this client handles expirations (true = it does)
6
- EXPIRES = true;
7
-
8
- // Check if this is the right class for the given client
9
- static test = (client) => client === "cookie" || client === "cookies";
10
-
11
- // Group methods
12
- #read = () => {
13
- const all = {};
14
- for (let entry of document.cookie.split(";")) {
15
- try {
16
- const [rawKey, rawValue] = entry.split("=");
17
- const key = decodeURIComponent(rawKey.trim());
18
- const value = JSON.parse(decodeURIComponent(rawValue.trim()));
19
- all[key] = value;
20
- } catch (error) {
21
- // no-op (some 3rd party can set cookies independently)
22
- }
23
- }
24
- return all;
25
- };
26
-
27
- // For cookies, an empty value is the same as null, even `""`
28
- get = (key) => this.#read()[key] || null;
29
-
30
- set = (key, data = null, { expires } = {}) => {
31
- // Setting it to null deletes it
32
- let expireStr = "";
33
- // NOTE: 0 is already considered here!
34
- if (expires !== null) {
35
- const time = new Date(Date.now() + expires * 1000).toUTCString();
36
- expireStr = `; expires=${time}`;
37
- }
38
-
39
- const value = encodeURIComponent(this.encode(data || ""));
40
- document.cookie = encodeURIComponent(key) + "=" + value + expireStr;
41
- };
42
-
43
- del = (key) => this.set(key, "", { expires: -100 });
44
-
45
- async *iterate(prefix = "") {
46
- for (let [key, value] of Object.entries(this.#read())) {
47
- if (!key.startsWith(prefix)) continue;
48
- yield [key, value];
49
- }
50
- }
51
- }
@@ -1,29 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Use a redis client to back up the store
4
- export default class Etcd extends Client {
5
- // Check if this is the right class for the given client
6
- static test = (client) => client?.constructor?.name === "Etcd3";
7
-
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();
11
-
12
- async *iterate(prefix = "") {
13
- const keys = await this.client.getAll().prefix(prefix).keys();
14
- for (const key of keys) {
15
- yield [key, await this.get(key)];
16
- }
17
- }
18
-
19
- keys = (prefix = "") => this.client.getAll().prefix(prefix).keys();
20
- entries = async (prefix = "") => {
21
- const keys = await this.keys(prefix);
22
- const values = await Promise.all(keys.map((k) => this.get(k)));
23
- return keys.map((k, i) => [k, values[i]]);
24
- };
25
- clear = async (prefix = "") => {
26
- if (!prefix) return this.client.delete().all();
27
- return this.client.delete().prefix(prefix);
28
- };
29
- }
@@ -1,70 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // A client that uses a single file (JSON) as a store
4
- export default class File extends Client {
5
- // Check if this is the right class for the given client
6
- static test = (client) => {
7
- if (client instanceof URL) client = client.href;
8
- return (
9
- typeof client === "string" &&
10
- client.startsWith("file://") &&
11
- client.includes(".")
12
- );
13
- };
14
-
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
- })();
25
-
26
- // Internal
27
- #read = async () => {
28
- const text = await this.fsp.readFile(this.file, "utf8");
29
- return text ? JSON.parse(text) : {};
30
- };
31
-
32
- #write = async (data) => {
33
- return this.fsp.writeFile(this.file, this.encode(data));
34
- };
35
-
36
- get = async (key) => {
37
- const data = await this.#read();
38
- return data[key] ?? null;
39
- };
40
-
41
- set = async (key, value) => {
42
- const data = await this.#read();
43
- if (value === null) {
44
- delete data[key];
45
- } else {
46
- data[key] = value;
47
- }
48
- await this.#write(data);
49
- };
50
-
51
- async *iterate(prefix = "") {
52
- const data = await this.#read();
53
- const entries = Object.entries(data).filter((p) => p[0].startsWith(prefix));
54
- for (const entry of entries) {
55
- yield entry;
56
- }
57
- }
58
-
59
- // Bulk updates are worth creating a custom method here
60
- clearAll = () => this.#write({});
61
- clear = async (prefix = "") => {
62
- const data = await this.#read();
63
- for (let key in data) {
64
- if (key.startsWith(prefix)) {
65
- delete data[key];
66
- }
67
- }
68
- await this.#write(data);
69
- };
70
- }
@@ -1,49 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- const noFileOk = (error) => {
4
- if (error.code === "ENOENT") return null;
5
- throw error;
6
- };
7
-
8
- // A client that uses a single file (JSON) as a store
9
- export default class Folder extends Client {
10
- // Check if this is the right class for the given client
11
- static test = (client) => {
12
- if (client instanceof URL) client = client.href;
13
- return (
14
- typeof client === "string" &&
15
- client.startsWith("file://") &&
16
- client.endsWith("/")
17
- );
18
- };
19
-
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
- })();
27
-
28
- file = (key) => this.folder + key + ".json";
29
-
30
- get = (key) => {
31
- return this.fsp
32
- .readFile(this.file(key), "utf8")
33
- .then(this.decode, noFileOk);
34
- };
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);
39
-
40
- async *iterate(prefix = "") {
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);
45
- const data = await this.get(key);
46
- yield [key, data];
47
- }
48
- }
49
- }
@@ -1,29 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Use localForage for managing the KV
4
- export default class Forage extends Client {
5
- // Check if this is the right class for the given client
6
- static test = (client) =>
7
- client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
8
-
9
- get = (key) => this.client.getItem(key);
10
- set = (key, value) => this.client.setItem(key, value);
11
- del = (key) => this.client.removeItem(key);
12
-
13
- async *iterate(prefix = "") {
14
- const keys = await this.client.keys();
15
- const list = keys.filter((k) => k.startsWith(prefix));
16
- for (const key of list) {
17
- yield [key, await this.get(key)];
18
- }
19
- }
20
-
21
- entries = async (prefix = "") => {
22
- const all = await this.client.keys();
23
- const keys = all.filter((k) => k.startsWith(prefix));
24
- const values = await Promise.all(keys.map((key) => this.get(key)));
25
- return keys.map((key, i) => [key, values[i]]);
26
- };
27
-
28
- clearAll = () => this.client.clear();
29
- }
@@ -1,25 +0,0 @@
1
- import api from "./api.js";
2
- import cloudflare from "./cloudflare.js";
3
- import cookie from "./cookie.js";
4
- import etcd from "./etcd.js";
5
- import file from "./file.js";
6
- import folder from "./folder.js";
7
- import forage from "./forage.js";
8
- import level from "./level.js";
9
- import memory from "./memory.js";
10
- import redis from "./redis.js";
11
- import storage from "./storage.js";
12
-
13
- export default {
14
- api,
15
- cloudflare,
16
- cookie,
17
- etcd,
18
- file,
19
- folder,
20
- forage,
21
- level,
22
- memory,
23
- redis,
24
- storage,
25
- };
@@ -1,40 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- const valueEncoding = "json";
4
- const notFound = (error) => {
5
- if (error?.code === "LEVEL_NOT_FOUND") return null;
6
- throw error;
7
- };
8
-
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";
13
-
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);
17
-
18
- async *iterate(prefix = "") {
19
- const keys = await this.client.keys().all();
20
- const list = keys.filter((k) => k.startsWith(prefix));
21
- for (const key of list) {
22
- yield [key, await this.get(key)];
23
- }
24
- }
25
-
26
- entries = async (prefix = "") => {
27
- const keys = await this.client.keys().all();
28
- const list = keys.filter((k) => k.startsWith(prefix));
29
- return Promise.all(list.map(async (k) => [k, await this.get(k)]));
30
- };
31
-
32
- clearAll = () => this.client.clear();
33
- clear = async (prefix = "") => {
34
- const keys = await this.client.keys().all();
35
- const list = keys.filter((k) => k.startsWith(prefix));
36
- return this.client.batch(list.map((key) => ({ type: "del", key })));
37
- };
38
-
39
- close = () => this.client.close();
40
- }
@@ -1,19 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Use a Map() as an in-memory client
4
- export default class Memory extends Client {
5
- // Check if this is the right class for the given client
6
- static test = (client) => client instanceof Map;
7
-
8
- get = (key) => this.client.get(key) ?? null;
9
- set = (key, data) => this.client.set(key, data);
10
- del = (key) => this.client.delete(key);
11
-
12
- *iterate(prefix = "") {
13
- for (const entry of this.client.entries()) {
14
- if (entry[0].startsWith(prefix)) yield entry;
15
- }
16
- }
17
-
18
- clearAll = () => this.client.clear();
19
- }
@@ -1,51 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // Use a redis client to back up the store
4
- export default class Redis extends Client {
5
- // Indicate if this client handles expirations (true = it does)
6
- EXPIRES = true;
7
-
8
- // Check if this is the right class for the given client
9
- static test = (client) => client && client.pSubscribe && client.sSubscribe;
10
-
11
- get = async (key) => this.decode(await this.client.get(key));
12
- set = async (key, value, { expires } = {}) => {
13
- const EX = expires ? Math.round(expires) : undefined;
14
- return this.client.set(key, this.encode(value), { EX });
15
- };
16
- del = (key) => this.client.del(key);
17
-
18
- has = async (key) => Boolean(await this.client.exists(key));
19
-
20
- // Go through each of the [key, value] in the set
21
- async *iterate(prefix = "") {
22
- const MATCH = prefix + "*";
23
- for await (const key of this.client.scanIterator({ MATCH })) {
24
- const value = await this.get(key);
25
- // By the time this specific value is read, it could be gone!
26
- if (!value) continue;
27
- yield [key, value];
28
- }
29
- }
30
-
31
- // Optimizing the retrieval of them by not getting their values
32
- keys = async (prefix = "") => {
33
- const MATCH = prefix + "*";
34
- const keys = [];
35
- for await (const key of this.client.scanIterator({ MATCH })) {
36
- keys.push(key);
37
- }
38
- return keys;
39
- };
40
-
41
- // Optimizing the retrieval of them all in bulk by loading the values
42
- // in parallel
43
- entries = async (prefix = "") => {
44
- const keys = await this.keys(prefix);
45
- const values = await Promise.all(keys.map((k) => this.get(k)));
46
- return keys.map((k, i) => [k, values[i]]);
47
- };
48
-
49
- clearAll = () => this.client.flushAll();
50
- close = () => this.client.quit();
51
- }
@@ -1,25 +0,0 @@
1
- import Client from "./Client.js";
2
-
3
- // A client that uses a single file (JSON) as a store
4
- export default class WebStorage extends Client {
5
- // Check if this is the right class for the given client
6
- static test(client) {
7
- if (typeof Storage === "undefined") return false;
8
- return client instanceof Storage;
9
- }
10
-
11
- // Item methods
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);
15
-
16
- *iterate(prefix = "") {
17
- for (const key of Object.keys(this.client)) {
18
- if (!key.startsWith(prefix)) continue;
19
- const value = this.get(key);
20
- if (value) yield [key, value];
21
- }
22
- }
23
-
24
- clearAll = () => this.client.clear();
25
- }